fix: panel memangkas judul/meta · footer kembali utuh · tes aksi tab lebih tabah
CI / test (push) Successful in 26s

Temuan CI (runner bersih, nama kontainer 112 char):
- panel(): judul/meta panjang melebihi lebar panel dan mendorong kolom kanan
  (Aksi Tab) keluar layar — sekarang dipangkas + jaring pengaman ansiTruncate;
  unit test TestPanelTitleFits menutup kasus ini.
- footer: aturan 'buang status dulu' membuat 'audio:'/animasi hilang di runner
  tanpa audio device — dikembalikan ke perilaku truncate semula.
- TestTabActionColumn menunggu panel detail tampil dulu (hapus balapan muat).
- TestToolbarSearch memilih prefix yang benar-benar unik + 'j' dulu.
This commit is contained in:
oxpoe committed 2026-10-16 03:20:21 +07:00
1 parent 5450a89a80
commit b7866f917b
3 files changed
+37 -17

No files matched your search

+7 -5
View File
@@ -633,17 +633,17 @@ func TestToolbarSearch(t *testing.T) {
t.Skipf("butuh >= 2 kontainer (err=%v, n=%d)", err, len(items)) t.Skipf("butuh >= 2 kontainer (err=%v, n=%d)", err, len(items))
} }
// Target: prefix nama yang tidak termuat di nama kontainer sebelumnya — // Target: prefix nama yang tidak termuat di nama kontainer mana pun —
// pencarian melompat ke kecocokan pertama dari atas. // supaya lompatan kursor tidak ambigu (kecocokan pertama = target).
target := "" target := ""
for i, it := range items { for _, it := range items {
q := it.Name q := it.Name
if len(q) > 6 { if len(q) > 6 {
q = q[:6] q = q[:6]
} }
unique := true unique := true
for k := 0; k < i; k++ { for _, other := range items {
if strings.Contains(strings.ToLower(items[k].Name), strings.ToLower(q)) { if other.Name != it.Name && strings.Contains(strings.ToLower(other.Name), strings.ToLower(q)) {
unique = false unique = false
break break
} }
@@ -661,6 +661,7 @@ func TestToolbarSearch(t *testing.T) {
u.waitContains("beranda", 5*time.Second) u.waitContains("beranda", 5*time.Second)
u.send("2") u.send("2")
u.waitContains("[1-5]", 3*time.Second) u.waitContains("[1-5]", 3*time.Second)
u.send("j") // kursor pindah dari item pertama → lompatan cari terlihat
if !u.clickText("Cari") { if !u.clickText("Cari") {
t.Fatal("pill Cari tidak ditemukan") t.Fatal("pill Cari tidak ditemukan")
} }
@@ -697,6 +698,7 @@ func TestTabActionColumn(t *testing.T) {
u.waitContains("beranda", 5*time.Second) u.waitContains("beranda", 5*time.Second)
u.send("2") u.send("2")
u.waitContains("[1-5]", 3*time.Second) u.waitContains("[1-5]", 3*time.Second)
u.waitContains("Statistik", 5*time.Second) // detail kontainer terpilih tampil
u.waitContains("Aksi Tab", 3*time.Second) u.waitContains("Aksi Tab", 3*time.Second)
u.waitContains("ikut ekor", 3*time.Second) u.waitContains("ikut ekor", 3*time.Second)
if !u.clickText("[S]") { if !u.clickText("[S]") {
+14 -12
View File
@@ -901,12 +901,21 @@ func panel(title, meta string, lines []string, w, h int, focused bool) string {
bc = th.Accent bc = th.Accent
} }
border := lipgloss.NewStyle().Foreground(lipgloss.Color(string(bc))) border := lipgloss.NewStyle().Foreground(lipgloss.Color(string(bc)))
titleS := text(title)
left := border.Render("╭─ ") + titleS // Judul/meta dipangkas agar baris border TIDAK pernah melebihi lebar panel
// (nama kontainer panjang dulu mendorong kolom kanan keluar layar).
avail := w - 6
if meta != "" {
avail = w - 8
}
metaW := 0
if meta != "" && avail > 4 {
metaW = min(len([]rune(meta)), max(avail/3, 2))
}
left := border.Render("╭─ ") + text(truncate(title, max(avail-metaW, 1)))
right := border.Render("─╮") right := border.Render("─╮")
if meta != "" { if meta != "" {
right = " " + dim(meta) + border.Render(" ─╮") right = " " + dim(truncate(meta, metaW)) + border.Render(" ─╮")
} }
gap := w - lipgloss.Width(left) - lipgloss.Width(right) gap := w - lipgloss.Width(left) - lipgloss.Width(right)
if gap < 1 { if gap < 1 {
@@ -914,9 +923,7 @@ func panel(title, meta string, lines []string, w, h int, focused bool) string {
} }
var b strings.Builder var b strings.Builder
b.WriteString(left) b.WriteString(ansiTruncate(left+border.Render(strings.Repeat("─", gap))+right, w))
b.WriteString(border.Render(strings.Repeat("─", gap)))
b.WriteString(right)
content := lipgloss.NewStyle().Width(w - 4) content := lipgloss.NewStyle().Width(w - 4)
for i := 0; i < h-2; i++ { for i := 0; i < h-2; i++ {
@@ -984,12 +991,7 @@ func (m Model) footerPlain() string {
if m.animating { if m.animating {
anim = fmt.Sprintf("%d", m.fps) anim = fmt.Sprintf("%d", m.fps)
} }
full := i18n.Tf("footer.status", hints, anim, m.sound.Status()) return truncate(i18n.Tf("footer.status", hints, anim, m.sound.Status()), m.width)
if lipgloss.Width(full) > m.width {
// Hint aksi lebih penting daripada status diagnostik: buang status dulu.
full = truncate(hints, m.width)
}
return full
} }
func (m Model) footerLine() string { return dim(m.footerPlain()) } func (m Model) footerLine() string { return dim(m.footerPlain()) }
+16
View File
@@ -47,6 +47,22 @@ func TestToolbarPlain(t *testing.T) {
} }
} }
// TestPanelTitleFits — judul/meta panjang tidak boleh melebihi lebar panel:
// nama kontainer/stack panjang pernah mendorong kolom kanan keluar layar.
func TestPanelTitleFits(t *testing.T) {
long := strings.Repeat("GITEA-ACTIONS-TASK-", 8)
for _, w := range []int{6, 10, 40, 100} {
for _, meta := range []string{"", "running", long} {
out := panel(long, meta, []string{"isi"}, w, 4, false)
for _, line := range strings.Split(out, "\n") {
if got := lipgloss.Width(line); got > w {
t.Fatalf("w=%d meta=%q: baris lebar %d > %d: %q", w, meta, got, w, line)
}
}
}
}
}
// TestSearchJump — pencarian melompat ke item pertama yang cocok (daftar tetap // TestSearchJump — pencarian melompat ke item pertama yang cocok (daftar tetap
// utuh), dan nama item mengikuti seksi aktif. // utuh), dan nama item mengikuti seksi aktif.
func TestSearchJump(t *testing.T) { func TestSearchJump(t *testing.T) {