Backup/restore: - berkas .lazy (format versi 2) kini menyimpan ISI berkas: binary/script dan ikon (maks 512 MB per berkas); format versi 1 tetap bisa diimpor - impor memakai shortcut.Restore: binary ditulis ke ~/.local/bin lokal, ikon dari cadangan (atau diunduh ulang dari icon_source), .desktop ditulis dengan path lokal, salinan Desktop dibuat sesuai target - path dari komputer lama tidak dipakai mentah -> tidak ada lagi 'banyak file missing'; entri yang tidak bisa dipulihkan ditandai dan bisa dibetulkan via edit - tes end-to-end: pasang 3 shortcut -> ekspor -> pulihkan di HOME lain -> semua berkas lokal, script hasil restore benar-benar jalan Script: - script dipasang sebagai wrapper + <slug>.real: wrapper menambahkan ~/.local/bin ke PATH dan cd ke folder asli script (desktop environment menjalankan dengan cwd = home, PATH sesi) - .desktop dapat Path= sesuai folder asli script - opsi 'Run in a terminal window (scripts)' di dialog install & edit - uninstall membuang wrapper + script aslinya
177 lines
5.2 KiB
Go
177 lines
5.2 KiB
Go
package ui
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"fyne.io/fyne/v2/test"
|
|
|
|
"lazyrun/internal/db"
|
|
"lazyrun/internal/icon"
|
|
"lazyrun/internal/store"
|
|
)
|
|
|
|
// TestNoEmptyDismissButton memastikan modal tidak memakai dialog.NewCustom,
|
|
// yang selalu menambah tombol dismiss walau labelnya kosong (tombol hantu
|
|
// di bagian bawah modal). Kita punya baris tombol sendiri, jadi harus memakai
|
|
// NewCustomWithoutButtons.
|
|
func TestNoEmptyDismissButton(t *testing.T) {
|
|
files, err := filepath.Glob("*.go")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, file := range files {
|
|
if strings.HasSuffix(file, "_test.go") {
|
|
continue
|
|
}
|
|
data, err := os.ReadFile(file)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(string(data), "dialog.NewCustom(") {
|
|
t.Errorf("%s memakai dialog.NewCustom (menambah tombol kosong); gunakan NewCustomWithoutButtons", file)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestDialogsOpenWithoutPanic membuka semua modal di test driver.
|
|
func TestDialogsOpenWithoutPanic(t *testing.T) {
|
|
a := test.NewApp()
|
|
defer a.Quit()
|
|
|
|
// pencarian ikon diarahkan ke port mati: tes tidak boleh menyentuh internet
|
|
// dan tidak boleh mengubah UI dari goroutine latar.
|
|
icon.SetSearchEndpoint("http://127.0.0.1:1")
|
|
defer icon.SetSearchEndpoint("https://api.iconify.design")
|
|
|
|
ui, err := build(a, "test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ui.win.Show()
|
|
|
|
ui.showExecutableDialog()
|
|
if top := ui.win.Canvas().Overlays().Top(); top == nil {
|
|
t.Fatal("modal executable tidak muncul")
|
|
}
|
|
ui.showWebAppDialog()
|
|
if len(ui.browsers) > 0 {
|
|
if top := ui.win.Canvas().Overlays().Top(); top == nil {
|
|
t.Fatal("modal web app tidak muncul")
|
|
}
|
|
}
|
|
ui.showEditDialog(store.Shortcut{Slug: "winbox", Name: "Winbox", Type: store.TypeExecutable, Kind: "windows",
|
|
BinaryPath: "/tmp/winbox", IconPath: "", Categories: "Network;", Targets: store.Targets{Menu: true, Desktop: true}})
|
|
if top := ui.win.Canvas().Overlays().Top(); top == nil {
|
|
t.Fatal("modal edit executable tidak muncul")
|
|
}
|
|
ui.showEditDialog(store.Shortcut{Slug: "wa", Name: "WhatsApp Web", Type: store.TypeWebApp, Kind: "pwa",
|
|
URL: "https://web.whatsapp.com", Browser: "/usr/bin/vivaldi", Targets: store.Targets{Menu: true}})
|
|
if top := ui.win.Canvas().Overlays().Top(); top == nil {
|
|
t.Fatal("modal edit webapp tidak muncul")
|
|
}
|
|
|
|
ui.showIconSearch("whatsapp", func(icon.Result, []byte) {})
|
|
if top := ui.win.Canvas().Overlays().Top(); top == nil {
|
|
t.Fatal("modal pencarian ikon tidak muncul")
|
|
}
|
|
|
|
// tunggu goroutine pencarian selesai: test driver menjalankan fyne.Do inline,
|
|
// jadi sisa goroutine bisa bertabrakan dengan tes berikutnya kalau dibiarkan.
|
|
time.Sleep(300 * time.Millisecond)
|
|
}
|
|
|
|
// TestStringsCoverUsedKeys memastikan setiap kunci T() yang dipakai ada di tabel
|
|
// terjemahan (kalau tidak, UI menampilkan nama kunci mentah).
|
|
func TestStringsCoverUsedKeys(t *testing.T) {
|
|
files, err := filepath.Glob("*.go")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
used := map[string]bool{}
|
|
for _, file := range files {
|
|
if strings.HasSuffix(file, "_test.go") {
|
|
continue
|
|
}
|
|
data, err := os.ReadFile(file)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, m := range regexp.MustCompile(`T\("([a-z_]+)"\)`).FindAllStringSubmatch(string(data), -1) {
|
|
used[m[1]] = true
|
|
}
|
|
}
|
|
if len(used) == 0 {
|
|
t.Fatal("tidak ada kunci T() yang ditemukan")
|
|
}
|
|
for key := range used {
|
|
if _, ok := tr[key]; !ok {
|
|
t.Errorf("kunci %q dipakai tapi tidak ada di tabel terjemahan", key)
|
|
}
|
|
}
|
|
|
|
// dan sebaliknya: kunci terjemahan yang tidak terpakai sebaiknya dihapus
|
|
for key := range tr {
|
|
if !used[key] {
|
|
t.Errorf("kunci %q ada di tabel terjemahan tapi tidak dipakai", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestRestoreImported memasang ulang entri cadangan: berkas dibuat di HOME saat ini
|
|
// dan entri yang sudah ada tidak ditimpa.
|
|
func TestRestoreImported(t *testing.T) {
|
|
home := t.TempDir()
|
|
t.Setenv("HOME", home)
|
|
t.Setenv("XDG_DATA_HOME", "")
|
|
t.Setenv("PATH", "")
|
|
|
|
script := []byte("#!/bin/sh\necho halo\n")
|
|
items := []db.Backup{
|
|
{Item: store.Shortcut{Slug: "lama", Name: "Deploy", Type: store.TypeExecutable, Kind: "script",
|
|
BinaryPath: "/komputer-lama/.local/bin/deploy", Targets: store.Targets{Menu: true}}, Binary: script},
|
|
{Item: store.Shortcut{Name: "Figma", Type: store.TypeWebApp, Kind: "pwa",
|
|
URL: "https://figma.com", Browser: "/usr/bin/vivaldi", Targets: store.Targets{Menu: true}}},
|
|
}
|
|
|
|
a := test.NewApp()
|
|
defer a.Quit()
|
|
ui, err := build(a, "test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ui.store.Items = nil
|
|
ui.store.Add(store.Shortcut{Slug: "figma", Name: "Figma"}) // sudah ada -> dilewati
|
|
|
|
added, skipped := ui.restoreImported(items)
|
|
if added != 1 || skipped != 1 {
|
|
t.Fatalf("added=%d skipped=%d, mau 1 dan 1", added, skipped)
|
|
}
|
|
|
|
deploy, ok := ui.store.Get("deploy")
|
|
if !ok {
|
|
t.Fatal("shortcut hasil restore tidak masuk daftar")
|
|
}
|
|
sc := ui.store.Items[deploy]
|
|
want := filepath.Join(home, ".local", "bin", "deploy")
|
|
if sc.BinaryPath != want {
|
|
t.Errorf("binary = %s, mau %s", sc.BinaryPath, want)
|
|
}
|
|
if _, err := os.Stat(want + ".real"); err != nil {
|
|
t.Errorf("script asli (.real) tidak ditulis: %v", err)
|
|
}
|
|
if _, err := os.Stat(sc.DesktopPath); err != nil {
|
|
t.Errorf(".desktop tidak dibuat: %v", err)
|
|
}
|
|
if sc.IconPath == "" {
|
|
t.Error("ikon tidak dipasang")
|
|
}
|
|
if sc.Kind != "script" {
|
|
t.Errorf("kind hasil restore = %q", sc.Kind)
|
|
}
|
|
}
|