layout: ikuti mock SVG — kolom tengah dashboard, toolbar workspace, kolom Aksi Tab
CI / test (push) Failing after 38s

- Beranda: kolom tengah CTA 'masuk workspace' (klikable), strip Disclaimer
  dan Info aplikasi (versi/repo/config); geometri satu sumber (homeGeom).
- Workspace: baris toolbar — pill Cari (klik atau '/' → cari-lompat daftar)
  + tombol [+ stack]/[konversi] klikable (sementara keduanya jalur konverter).
- Detail kontainer: kolom kanan 'Aksi Tab' (mock TAB ACTION) berisi aksi tab
  aktif yang bisa diklik; disembunyikan di layar sempit agar tab bar utuh.
- Footer: hint aksi diprioritaskan; status diagnostik dibuang bila tak muat.
- internal/version: versi build dipakai CLI & strip Info aplikasi.
- audio: Play/Status aman untuk Engine nil (Model zero-value di tes).
- Tes: 3 skenario E2E baru (CTA, cari toolbar, kolom aksi) + tes layout.
This commit is contained in:
oxpoe committed 2026-10-16 03:16:32 +07:00
1 parent 8880660ce4
commit 5450a89a80
9 files changed
+638 -112

No files matched your search

+113 -2
View File
@@ -599,6 +599,116 @@ func TestMouseHomeContainer(t *testing.T) {
}
}
// TestHomeCtaWorkspace — klik kotak CTA "masuk workspace" di kolom tengah
// beranda → pindah ke workspace bagian Stack.
func TestHomeCtaWorkspace(t *testing.T) {
u := startTUI(t)
u.waitContains("beranda", 5*time.Second)
u.waitContains("masuk workspace", 5*time.Second)
if !u.clickText("masuk workspace") {
t.Fatal("kotak CTA tidak ditemukan di beranda")
}
u.waitContains("[u] jalankan", 3*time.Second) // footer Stack = bukti pindah view
u.send("q")
if code := u.waitExit(5 * time.Second); code != 0 {
t.Fatalf("exit code %d, mau 0", code)
}
}
// TestToolbarSearch — pill "Cari" di toolbar workspace: klik, ketik → kursor
// melompat ke kontainer yang cocok; Enter keluar mode cari (paritas "/").
func TestToolbarSearch(t *testing.T) {
if _, err := os.Stat("/var/run/docker.sock"); err != nil {
t.Skip("docker socket tidak ada")
}
cl, err := docker.New()
if err != nil {
t.Skipf("docker: %v", err)
}
defer cl.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
items, err := cl.ListContainers(ctx)
if err != nil || len(items) < 2 {
t.Skipf("butuh >= 2 kontainer (err=%v, n=%d)", err, len(items))
}
// Target: prefix nama yang tidak termuat di nama kontainer sebelumnya —
// pencarian melompat ke kecocokan pertama dari atas.
target := ""
for i, it := range items {
q := it.Name
if len(q) > 6 {
q = q[:6]
}
unique := true
for k := 0; k < i; k++ {
if strings.Contains(strings.ToLower(items[k].Name), strings.ToLower(q)) {
unique = false
break
}
}
if unique {
target = q
break
}
}
if target == "" {
t.Skip("nama kontainer tidak unik untuk uji cari")
}
u := startTUI(t)
u.waitContains("beranda", 5*time.Second)
u.send("2")
u.waitContains("[1-5]", 3*time.Second)
if !u.clickText("Cari") {
t.Fatal("pill Cari tidak ditemukan")
}
u.send(target)
u.waitContains("[ / "+target, 3*time.Second) // query masuk ke pill
u.waitHighlighted(target, 3*time.Second) // kursor melompat ke item cocok
u.send("\r") // Enter: keluar mode cari
u.waitContains("[ / Cari ]", 3*time.Second)
u.send("q") // bukti sudah keluar mode cari: q keluar, bukan mengetik
if code := u.waitExit(5 * time.Second); code != 0 {
t.Fatalf("exit code %d, mau 0", code)
}
}
// TestTabActionColumn — kolom kanan "Aksi Tab" (mock TAB ACTION) di detail
// kontainer: item tampil per tab dan bisa diklik (klik [S] → toggle timestamp).
func TestTabActionColumn(t *testing.T) {
if _, err := os.Stat("/var/run/docker.sock"); err != nil {
t.Skip("docker socket tidak ada")
}
cl, err := docker.New()
if err != nil {
t.Skipf("docker: %v", err)
}
defer cl.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
items, err := cl.ListContainers(ctx)
if err != nil || len(items) == 0 {
t.Skipf("tidak ada kontainer (err=%v)", err)
}
u := startTUI(t)
u.waitContains("beranda", 5*time.Second)
u.send("2")
u.waitContains("[1-5]", 3*time.Second)
u.waitContains("Aksi Tab", 3*time.Second)
u.waitContains("ikut ekor", 3*time.Second)
if !u.clickText("[S]") {
t.Fatal("aksi [S] tidak ditemukan di kolom aksi tab")
}
u.waitContains("Timestamp log", 3*time.Second) // toast = aksi benar-benar jalan
u.send("q")
if code := u.waitExit(5 * time.Second); code != 0 {
t.Fatalf("exit code %d, mau 0", code)
}
}
// TestConvertCommand — CLI `tia-love convert`: hasil di stdout, exit code jelas.
func TestConvertCommand(t *testing.T) {
out, err := exec.Command(binPath, "convert", "docker run -p 80:80 --name web nginx").CombinedOutput()
@@ -804,8 +914,9 @@ func TestClickSelectsContainer(t *testing.T) {
u.waitContains(short(items[0].Name), 5*time.Second)
u.waitHighlighted(short(items[0].Name), 3*time.Second) // seleksi awal (kursor 0)
// klik baris item ke-2: xterm y=4 (1-based) → app row 3 → indeks 1
u.send("\x1b[<0;10;4M\x1b[<0;10;4m")
// klik baris item ke-2: xterm y=5 (1-based) → app row 4 → indeks 1
// (header + toolbar + border panel di atasnya).
u.send("\x1b[<0;10;5M\x1b[<0;10;5m")
u.waitHighlighted(short(items[1].Name), 3*time.Second)
u.send("q")