CI / test (push) Successful in 28s
Nama seksi di subjudul (stack, kontainer, image, volume, jaringan) kini bergaya aksen dan bisa diklik → pindah seksi, paritas tombol 1-5. Rentang kolom tiap nama dihitung dari satu sumber (workspaceSubsections) sehingga render & hit-test tidak bisa melenceng; layar sempit memotong nama yang tidak muat. Tes: unit (span → seksi, batas sempit) + e2e (klik 'image').
134 lines
4.7 KiB
Go
134 lines
4.7 KiB
Go
package app
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"charm.land/lipgloss/v2"
|
|
|
|
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/docker"
|
|
"gitea.oxpoe.qzz.io/oxpoe/tia-love/internal/i18n"
|
|
)
|
|
|
|
// TestDetailWidths — kolom aksi tab tampil hanya bila layar cukup lebar
|
|
// (tab bar detail tetap utuh di layar sempit).
|
|
func TestDetailWidths(t *testing.T) {
|
|
m := Model{width: 120, section: sectionContainers}
|
|
wDetail, wAct := m.detailWidths()
|
|
if wAct == 0 {
|
|
t.Fatalf("layar 120: kolom aksi harus tampil (wDetail=%d)", wDetail)
|
|
}
|
|
if wDetail+wAct != 120-m.leftWidth() {
|
|
t.Fatalf("layar 120: lebar tidak menjumlah (wDetail=%d wAct=%d)", wDetail, wAct)
|
|
}
|
|
m.width = 80
|
|
if _, wAct = m.detailWidths(); wAct != 0 {
|
|
t.Fatalf("layar 80: kolom aksi harus disembunyikan, dapat %d", wAct)
|
|
}
|
|
}
|
|
|
|
// TestWorkspaceBar — bar workspace memuat subjudul seksi + tombol stack,
|
|
// menampilkan query saat mode cari, dan tidak melebihi lebar dalam panel.
|
|
func TestWorkspaceBar(t *testing.T) {
|
|
for _, width := range []int{40, 80, 120} {
|
|
m := Model{width: width, view: viewList}
|
|
subLine, searchLine := m.workspaceBarPlain()
|
|
if !strings.Contains(subLine, i18n.T("toolbar.addStack")) || !strings.Contains(subLine, i18n.T("toolbar.convert")) {
|
|
t.Fatalf("w=%d: tombol stack/konversi tidak ada: %q", width, subLine)
|
|
}
|
|
if !strings.Contains(searchLine, "[ / "+i18n.T("toolbar.search")+" ]") {
|
|
t.Fatalf("w=%d: pill cari tidak ada: %q", width, searchLine)
|
|
}
|
|
inner := width - 4
|
|
if w := lipgloss.Width(subLine); w > inner {
|
|
t.Fatalf("w=%d: subjudul+tombol lebar %d > %d", width, w, inner)
|
|
}
|
|
if w := lipgloss.Width(searchLine); w > inner {
|
|
t.Fatalf("w=%d: pill cari lebar %d > %d", width, w, inner)
|
|
}
|
|
m.searching, m.query = true, "abc"
|
|
if _, searchLine = m.workspaceBarPlain(); !strings.Contains(searchLine, "/ abc") {
|
|
t.Fatalf("w=%d: query tidak tampil di pill: %q", width, searchLine)
|
|
}
|
|
}
|
|
// Subjudul = daftar seksi (mock): "stack, kontainer, image, volume, jaringan".
|
|
m := Model{width: 120, view: viewList}
|
|
subLine, _ := m.workspaceBarPlain()
|
|
want := strings.ToLower(i18n.T("section.stacks")) + ", " +
|
|
strings.ToLower(i18n.T("section.containers")) + ", " +
|
|
strings.ToLower(i18n.T("section.images")) + ", " +
|
|
strings.ToLower(i18n.T("section.volumes")) + ", " +
|
|
strings.ToLower(i18n.T("section.networks"))
|
|
if !strings.HasPrefix(subLine, want) {
|
|
t.Fatalf("subjudul bar salah: %q (mau %q)", subLine, want)
|
|
}
|
|
|
|
// Tautan seksi: tiap span menunjuk nama seksi yang benar & bisa diklik.
|
|
keys := []string{"stacks", "containers", "images", "volumes", "networks"}
|
|
sub, spans := m.workspaceSubsections()
|
|
runes := []rune(sub)
|
|
if len(spans) != len(keys) {
|
|
t.Fatalf("jumlah span = %d, mau %d", len(spans), len(keys))
|
|
}
|
|
for i, sp := range spans {
|
|
got, wantName := string(runes[sp[0]:sp[1]]), strings.ToLower(i18n.T("section."+keys[i]))
|
|
if got != wantName {
|
|
t.Fatalf("span %d = %q, mau %q", i, got, wantName)
|
|
}
|
|
if s, ok := m.workspaceSectionAt(sp[0]); !ok || int(s) != i {
|
|
t.Fatalf("klik kolom %d harus → seksi %d (ok=%v s=%d)", sp[0], i, ok, s)
|
|
}
|
|
}
|
|
if _, ok := m.workspaceSectionAt(len(runes) + 5); ok {
|
|
t.Fatal("kolom di luar subjudul tidak boleh memetakan seksi")
|
|
}
|
|
|
|
// Layar sempit: span tetap di dalam subjudul yang tampak.
|
|
narrow := Model{width: 40, view: viewList}
|
|
subNarrow, spansNarrow, _, _, _ := narrow.workspaceBarParts()
|
|
limit := len([]rune(subNarrow))
|
|
for _, sp := range spansNarrow {
|
|
if sp[0] < 0 || sp[1] > limit {
|
|
t.Fatalf("span %v di luar subjudul tampak (%d kolom)", sp, limit)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
// utuh), dan nama item mengikuti seksi aktif.
|
|
func TestSearchJump(t *testing.T) {
|
|
m := Model{
|
|
section: sectionContainers,
|
|
containers: []docker.Container{{Name: "alfa"}, {Name: "beta"}, {Name: "betawi"}},
|
|
}
|
|
m.query = "beta"
|
|
m.searchJumpCmd()
|
|
if got := m.cur(); got != 1 {
|
|
t.Fatalf("kursor = %d, mau 1 (kecocokan pertama)", got)
|
|
}
|
|
if m.itemName(2) != "betawi" {
|
|
t.Fatalf("itemName(2) = %q", m.itemName(2))
|
|
}
|
|
m.query = "tidak-ada"
|
|
m.searchJumpCmd()
|
|
if got := m.cur(); got != 1 {
|
|
t.Fatalf("tanpa kecocokan kursor harus tetap: %d", got)
|
|
}
|
|
}
|