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) } }