Hello world
Release / Build and publish (push) Successful in 22s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kebandClaude Opus 5.5 committed 2026-10-12 11:35:25 +02:00
commit 45fd80b418
130 files changed
+26739

No files matched your search

+203
View File
@@ -0,0 +1,203 @@
package panes
import (
"fmt"
"reflect"
"strings"
"testing"
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
"github.com/charmbracelet/x/ansi"
"git.kebcloud.no/keb/lazysecrets/internal/ui/styles"
)
func newStringList(n int) List[string] {
l := NewList("Items", Item[string]{
ID: func(s string) string { return s },
Filter: func(s string) string { return s },
Render: func(_ styles.Styles, s string, _ int) string { return s },
})
var items []string
for i := range n {
items = append(items, fmt.Sprintf("item-%02d", i))
}
l.SetItems(items)
l.SetSize(20, 7) // 5 rows
return l
}
func TestListScrollKeepsCursorVisible(t *testing.T) {
l := newStringList(20)
l.Move(9)
view := ansi.Strip(l.View(styles.Default(), true, ""))
if !strings.Contains(view, "item-09") || strings.Contains(view, "item-04") {
t.Fatalf("cursor row not scrolled into view:\n%s", view)
}
// Moving up within the window must not scroll.
l.Move(-2)
view = ansi.Strip(l.View(styles.Default(), true, ""))
if !strings.Contains(view, "item-05") {
t.Fatalf("window jumped when moving up inside it:\n%s", view)
}
l.Top()
if l.SelectedID() != "item-00" || l.offset != 0 {
t.Fatalf("Top: selected %q offset %d", l.SelectedID(), l.offset)
}
l.Bottom()
if l.SelectedID() != "item-19" || l.offset != 15 {
t.Fatalf("Bottom: selected %q offset %d", l.SelectedID(), l.offset)
}
}
func TestListSetItemsKeepsSelection(t *testing.T) {
l := newStringList(5)
l.Move(3)
l.SetItems([]string{"new", "item-03", "item-04"})
if l.SelectedID() != "item-03" {
t.Fatalf("selected = %q", l.SelectedID())
}
l.SetItems([]string{"other"})
if l.SelectedID() != "other" {
t.Fatalf("selected = %q after item vanished", l.SelectedID())
}
}
func TestBoxExactSize(t *testing.T) {
out := Box(styles.Default(), "a very long title that will not fit", "footer", []string{"x", strings.Repeat("y", 50)}, 12, 5, true)
lines := strings.Split(out, "\n")
if len(lines) != 5 {
t.Fatalf("height = %d", len(lines))
}
for _, line := range lines {
if w := ansi.StringWidth(line); w != 12 {
t.Errorf("width %d: %q", w, ansi.Strip(line))
}
}
}
func filterList(t *testing.T, items []string, query string) List[string] {
t.Helper()
l := NewList("Items", Item[string]{
ID: func(s string) string { return s },
Filter: func(s string) string { return s },
Render: func(_ styles.Styles, s string, _ int) string { return s },
})
l.SetItems(items)
l.SetSize(40, 10)
l.StartFilter()
for _, r := range query {
l.UpdateFilter(tea.KeyPressMsg{Code: r, Text: string(r)})
}
return l
}
func visibleNames(l List[string]) []string {
var out []string
for _, i := range l.visible {
out = append(out, l.items[i])
}
return out
}
func TestFuzzyFilterRanksAndSkips(t *testing.T) {
l := filterList(t, []string{"api-key", "db-password", "storage-key", "dbx-pat"}, "dbpw")
if got := visibleNames(l); !reflect.DeepEqual(got, []string{"db-password"}) {
t.Errorf("dbpw matched %v", got)
}
l = filterList(t, []string{"monkey", "key-vault", "api-key"}, "key")
if got := visibleNames(l); got[0] != "key-vault" || len(got) != 3 {
t.Errorf("prefix match not ranked first: %v", got)
}
}
func TestFilterHighlightsMatches(t *testing.T) {
l := filterList(t, []string{"db-password", "api-key"}, "dpw")
l.AcceptFilter()
view := l.View(styles.Default(), false, "")
// d, p and w are styled; with ANSI stripped the text is unchanged.
if !strings.Contains(ansi.Strip(view), "db-password") {
t.Fatalf("row missing:\n%s", ansi.Strip(view))
}
if hits := l.highlight(0, "db-password"); !reflect.DeepEqual(hits, []int{0, 3, 7}) {
t.Errorf("hits = %v", hits)
}
// Rows that show the Filter text at an offset get shifted hits.
if hits := l.highlight(0, " db-password (disabled)"); !reflect.DeepEqual(hits, []int{2, 5, 9}) {
t.Errorf("shifted hits = %v", hits)
}
}
func TestFilterKeepsRowColours(t *testing.T) {
blue := lipgloss.NewStyle().Foreground(lipgloss.Blue)
match := styles.Default().Match
if got := matchRanges("platform-dev", []int{9, 10, 11}, match); len(got) != 1 || got[0].Start != 9 || got[0].End != 12 {
t.Errorf("ranges = %+v, want one range 9-12", got)
}
// Only the matches change style; the rest keeps the row's colour.
styled := " " + blue.Render("platform-dev")
out := lipgloss.StyleRanges(styled, matchRanges(" platform-dev", []int{11, 12, 13}, match)...)
if ansi.Strip(out) != " platform-dev" {
t.Fatalf("text changed: %q", ansi.Strip(out))
}
if !strings.Contains(out, "\x1b[34m") {
t.Errorf("unmatched text lost its colour: %q", out)
}
}
func TestCounterInBottomRight(t *testing.T) {
out := BoxCounted(styles.Default(), "T", "footer", "3 of 12", []string{"x"}, 30, 3, true)
bottom := ansi.Strip(strings.Split(out, "\n")[2])
if !strings.HasSuffix(bottom, " 3 of 12 ─╯") || !strings.HasPrefix(bottom, "╰─ footer ") || ansi.StringWidth(bottom) != 30 {
t.Errorf("bottom edge = %q", bottom)
}
}
func TestHighlightKeepsColours(t *testing.T) {
blue := lipgloss.NewStyle().Foreground(lipgloss.Blue)
sel := lipgloss.NewStyle().Background(lipgloss.BrightBlack).Bold(true)
row := " " + blue.Render("platform-prod") + lipgloss.NewStyle().Faint(true).Render(" note")
out := Highlight(row, sel, 30)
if ansi.StringWidth(out) != 30 || ansi.Strip(out) != ansi.Strip(row)+strings.Repeat(" ", 30-ansi.StringWidth(row)) {
t.Fatalf("text or width changed: %q", ansi.Strip(out))
}
if !strings.Contains(out, "\x1b[34m") {
t.Error("row lost its blue")
}
// The background is re-applied after every reset, so it spans the row.
resets := strings.Count(out, "\x1b[m") + strings.Count(out, "\x1b[0m")
if bgs := strings.Count(out, "\x1b[100;1m"); bgs < resets {
t.Errorf("background not re-applied after each reset: %d resets, %d backgrounds\n%q", resets, bgs, out)
}
// Reverse styles fall back to plain text.
if got := Highlight(row, lipgloss.NewStyle().Reverse(true), 30); strings.Contains(got, "34m") {
t.Error("reverse kept the colours")
}
}
func TestListHeader(t *testing.T) {
l := NewList("t", Item[string]{
ID: func(s string) string { return s },
Filter: func(s string) string { return s },
Render: func(_ styles.Styles, s string, _ int) string { return s },
})
l.Header, l.HeaderShort = []string{"ART1", "ART2"}, "short"
l.SetItems([]string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"})
l.SetSize(30, 2+2+1+6) // the art, a blank line and 6 rows
st := styles.Default()
if v := l.View(st, true, ""); !strings.Contains(v, "ART1") || strings.Contains(v, "short") {
t.Errorf("at the top, the art:\n%s", v)
}
l.Move(6) // below the rows under the art
if v := l.View(st, true, ""); strings.Contains(v, "ART1") || !strings.Contains(v, "short") || !strings.Contains(v, " g") {
t.Errorf("further down, the text:\n%s", v)
}
l.Top()
l.SetSize(30, 8) // too short for the art and 5 rows
if v := l.View(st, true, ""); strings.Contains(v, "ART1") || !strings.Contains(v, "short") {
t.Errorf("no room, the text:\n%s", v)
}
}