feat(stack): converter docker run→compose (port REA modul 1) — 19 golden vektor + CLI tia-love convert
CI / test (push) Successful in 24s

This commit is contained in:
oxpoe committed 2026-10-16 01:42:02 +07:00
1 parent f555f27467
commit b84afdb494
11 files changed
+1519 -159

No files matched your search

+124
View File
@@ -451,6 +451,130 @@ func TestEventsActivity(t *testing.T) {
}
}
// clickText — temukan substr di snapshot layar, kirim klik SGR ke tengahnya.
// Dipakai untuk membuktikan elemen UI mana pun bisa diklik.
func (u *tui) clickText(substr string) bool {
lines := strings.Split(u.screen(), "\n")
for y, line := range lines {
i := strings.Index(line, substr)
if i < 0 {
continue
}
x := len([]rune(line[:i])) + len([]rune(substr))/2
u.send(fmt.Sprintf("\x1b[<0;%d;%dM\x1b[<0;%d;%dm", x+1, y+1, x+1, y+1))
return true
}
return false
}
// TestMouseFooterHint — hint di status bar dapat diklik (klik [T] → ganti tema).
func TestMouseFooterHint(t *testing.T) {
u := startTUI(t)
u.waitContains("beranda", 5*time.Second)
if !u.clickText("[T]") {
t.Fatal("hint [T] tidak ditemukan di footer")
}
u.waitContains("Mode tema", 3*time.Second)
u.send("q")
if code := u.waitExit(5 * time.Second); code != 0 {
t.Fatalf("exit code %d, mau 0", code)
}
}
// TestMouseTabBar — tab detail dapat diklik langsung (Statistik & Proses).
func TestMouseTabBar(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)
}
name := items[0].Name
if len(name) > 12 {
name = name[:12]
}
u := startTUI(t)
u.waitContains("beranda", 5*time.Second)
u.send("1")
u.waitContains("[1-4]", 3*time.Second) // footer workspace = sudah pindah view
u.waitContains(name, 5*time.Second)
if !u.clickText("Statistik") {
t.Fatal("tab Statistik tidak ditemukan")
}
u.waitContains("[Statistik]", 3*time.Second) // tab aktif ditandai kurung
if !u.clickText("Proses") {
t.Fatal("tab Proses tidak ditemukan")
}
u.waitContains("PID", 5*time.Second)
u.send("q")
if code := u.waitExit(5 * time.Second); code != 0 {
t.Fatalf("exit code %d, mau 0", code)
}
}
// TestMouseHomeContainer — klik baris kontainer di beranda → masuk workspace
// pada kontainer itu.
func TestMouseHomeContainer(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)
}
name := items[0].Name
if len(name) > 12 {
name = name[:12]
}
u := startTUI(t)
u.waitContains("beranda", 5*time.Second)
u.waitContains(name, 5*time.Second) // tampil di panel Kontainer beranda
if !u.clickText(name) {
t.Fatal("baris kontainer tidak ditemukan")
}
u.waitContains("[1-4]", 3*time.Second) // footer workspace = bukti pindah view
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()
if err != nil {
t.Fatalf("convert gagal: %v (%s)", err, out)
}
s := string(out)
for _, want := range []string{"services:", "container_name: web", "80:80", "image: nginx"} {
if !strings.Contains(s, want) {
t.Fatalf("output convert tidak memuat %q:\n%s", want, s)
}
}
err = exec.Command(binPath, "convert", "bukan perintah docker").Run()
ee, ok := err.(*exec.ExitError)
if !ok || ee.ExitCode() != 1 {
t.Fatalf("perintah invalid harus exit 1, dapat %v", err)
}
}
func TestThemeCycle(t *testing.T) {
u := startTUI(t)
u.waitContains("beranda", 5*time.Second)