feat: layar Pengaturan (PRD §5.1.5) + tombol Setting beranda + toggle suara
CI / test (push) Successful in 39s

- Layar Pengaturan (',' atau klik chip Pengaturan beranda): bahasa, mode tema,
  efek suara, volume, animasi, timestamp — semua berlaku langsung & tersimpan
  ke config (config.Save), plus path config + catatan.
- Audio live: Engine.SetEnabled/SetVolume/Enabled/Volume; init idempoten
  (readyOnce/started) sehingga engine yang lahir mati bisa dinyalakan tanpa
  restart — komentar 'shortcut' F-024 lama dihapus.
- Beranda: kolom tengah kini CTA + hint '[m] suara: …' (klik = toggle) +
  chip Pengaturan — draf dashboard lengkap.
- Tombol lama 'T'/'a'/'S' kini lewat applySetting yang sama (DRY + persist).
- TIA_LANG dihapus (tak perlu lagi): e2e memakai XDG_CONFIG_HOME temp
  (config bersih per tes) → tes tidak tergantung config pengguna.
- Toast: klik di luar kotak toast tidak lagi menutupnya (hanya klik pada toast)
  — tombol lain tetap bisa diklik saat notifikasi tampil.
- Tes: unit settings (apply/persist/jepit volume, baris plain==styled tanpa
  ANSI) + e2e TestSettingsScreen (klik chip, toggle, bahasa→Inggris,
  verifikasi config.yaml).
This commit is contained in:
oxpoe committed 2026-10-16 04:07:33 +07:00
1 parent e903912029
commit 2d07935f83
10 files changed
+663 -62

No files matched your search

+99 -4
View File
@@ -64,9 +64,26 @@ type tui struct {
waited bool
}
// startTUI memakai TIA_LANG=id eksplisit — deterministik walau mesin
// pengembang/testing punya config.yaml sendiri.
func startTUI(t *testing.T) *tui { return startTUIEnv(t, "TIA_LANG=id") }
// startTUI memakai config bersih di XDG temp (bahasa id) — hasil tes tidak
// bergantung config pengguna yang sebenarnya.
func startTUI(t *testing.T) *tui {
return startTUIEnv(t, "XDG_CONFIG_HOME="+writeTestConfig(t, "id"))
}
// writeTestConfig — config.yaml minimal di XDG temp; kembalikan direktori XDG.
func writeTestConfig(t *testing.T, lang string) string {
t.Helper()
xdg := t.TempDir()
dir := filepath.Join(xdg, "tia")
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
cfg := "version: 1\nui:\n language: " + lang + "\n"
if err := os.WriteFile(filepath.Join(dir, "config.yaml"), []byte(cfg), 0o644); err != nil {
t.Fatal(err)
}
return xdg
}
// envWith — gabung env proses + override tanpa duplikat key
// (Linux getenv memakai kemunculan pertama, jadi duplikat berbahaya).
@@ -166,6 +183,37 @@ func (u *tui) waitContains(substr string, d time.Duration) {
u.t.Fatalf("layar tidak memuat %q dalam %v.\n--- snapshot layar ---\n%s", substr, d, u.screen())
}
// clickUntil — klik target berulang sampai substr tujuan muncul. Layar bisa
// tertinggal satu frame tepat saat toast menutup; klik ulang aman (klik yang
// tidak kena apa-apa tidak berefek).
func (u *tui) clickUntil(clickSubstr, want string, d time.Duration) {
u.t.Helper()
deadline := time.Now().Add(d)
for time.Now().Before(deadline) {
if strings.Contains(u.screen(), want) {
return
}
if !u.clickText(clickSubstr) {
u.t.Fatalf("target klik %q tidak ditemukan", clickSubstr)
}
time.Sleep(120 * time.Millisecond)
}
u.waitContains(want, time.Second) // gagalkan dengan snapshot
}
// waitGone — tunggu sampai substr hilang dari layar (mis. toast selesai).
func (u *tui) waitGone(substr string, d time.Duration) {
u.t.Helper()
deadline := time.Now().Add(d)
for time.Now().Before(deadline) {
if !strings.Contains(u.screen(), substr) {
return
}
time.Sleep(50 * time.Millisecond)
}
u.t.Fatalf("layar masih memuat %q setelah %v", substr, d)
}
// waitHighlighted — menunggu baris yang memuat substr ber-latar (SGR bg),
// yaitu penanda baris terpilih di daftar.
func (u *tui) waitHighlighted(substr string, d time.Duration) {
@@ -743,6 +791,53 @@ func TestWorkspaceBar(t *testing.T) {
}
}
// TestSettingsScreen — layar Pengaturan: tombol di beranda membuka, hint suara
// & toggle berlaku, ganti bahasa langsung + tersimpan ke config.
func TestSettingsScreen(t *testing.T) {
xdg := writeTestConfig(t, "id")
u := startTUIEnv(t, "XDG_CONFIG_HOME="+xdg)
u.waitContains("beranda", 5*time.Second)
if !u.clickText("Pengaturan") { // chip di kolom tengah beranda
t.Fatal("tombol Pengaturan tidak ditemukan di beranda")
}
u.waitContains("Efek suara", 3*time.Second)
// efek suara: nyala → mati (toast + baris)
if !u.clickText("nyala") {
t.Fatal("nilai efek suara tidak ditemukan")
}
u.waitContains("Efek suara: mati", 3*time.Second)
// `,` tutup → kembali ke beranda; hint "[m] suara: mati" ikut berubah
u.send(",")
u.waitContains("suara: mati", 3*time.Second)
u.waitGone("Efek suara: mati", 6*time.Second) // tunggu toast tutup (layout stabil)
// klik hint suara = toggle (mouse-native): mati → nyala
u.clickUntil("suara: mati", "suara: nyala", 6*time.Second)
// `,` buka lagi; ganti bahasa → UI langsung Inggris
u.send(",")
u.waitContains("Konfigurasi", 3*time.Second) // penanda layar Pengaturan (bukan chip beranda)
u.clickUntil("Indonesia", "[ English ]", 6*time.Second) // nilai baris = penanda tetap
u.send("q")
if code := u.waitExit(5 * time.Second); code != 0 {
t.Fatalf("exit code %d, mau 0", code)
}
// perubahan tersimpan ke config
b, err := os.ReadFile(filepath.Join(xdg, "tia", "config.yaml"))
if err != nil {
t.Fatalf("config tidak terbaca: %v", err)
}
for _, want := range []string{"language: en", "sound_enabled: true"} {
if !strings.Contains(string(b), want) {
t.Fatalf("config tidak memuat %q:\n%s", want, b)
}
}
}
// 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()
@@ -872,7 +967,7 @@ func TestThemeCycle(t *testing.T) {
}
func TestEnglishLanguage(t *testing.T) {
u := startTUIEnv(t, "TIA_LANG=en")
u := startTUIEnv(t, "XDG_CONFIG_HOME="+writeTestConfig(t, "en"))
u.waitContains("· home", 5*time.Second)
u.waitContains("Clock", 5*time.Second)
u.send("1")