- Cari ikon online (Iconify) dari dialog install, plus tombol sync ikon per shortcut - Auto ikon: file terdekat -> favicon situs (termasuk ikon manifest PWA) -> ikon brand yang namanya cocok -> ikon default; sumber ikon disimpan di metadata - Query dibersihkan (web.whatsapp.com -> whatsapp) dan kata <4 huruf tidak dipakai supaya aplikasi tanpa ikon resmi tidak dapat ikon acak - Unduhan thumbnail dibatasi 4 bersamaan + retry sekali (rate limit Iconify) - Cursor: XCURSOR_THEME/XCURSOR_SIZE diambil dari config KDE/GTK/~/.icons default, dan AppImage tidak lagi membundel libX*/libwayland*/libxkbcommon (dipakai dari host) - README: bagian ikon + integrasi desktop
195 lines
5.3 KiB
Go
195 lines
5.3 KiB
Go
package shortcut
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"lazyrun/internal/icon"
|
|
"lazyrun/internal/store"
|
|
)
|
|
|
|
// iconSetEndpoint mengarahkan pencarian ikon ke server tes.
|
|
func iconSetEndpoint(t *testing.T, base string) {
|
|
t.Helper()
|
|
icon.SetSearchEndpoint(base)
|
|
t.Cleanup(func() { icon.SetSearchEndpoint("http://127.0.0.1:1") })
|
|
}
|
|
|
|
func TestRefreshIconFromURL(t *testing.T) {
|
|
testEnv(t)
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write(append(pngMagic, []byte("ikon-baru")...))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
deskDst := filepath.Join(t.TempDir(), "x.desktop")
|
|
old := Entry{Name: "X", ExecArgs: []string{"/x"}, Icon: "/lama/icon.png"}
|
|
if err := WriteDesktop(deskDst, old); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
sc := store.Shortcut{
|
|
Slug: "x",
|
|
Name: "X",
|
|
IconPath: "/lama/icon.png",
|
|
IconSource: srv.URL + "/icon.png",
|
|
DesktopPath: deskDst,
|
|
}
|
|
path, err := RefreshIcon(sc)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if filepath.Base(path) != "x.png" {
|
|
t.Errorf("ikon baru di %s", path)
|
|
}
|
|
if data, err := os.ReadFile(path); err != nil || len(data) == 0 {
|
|
t.Errorf("ikon tidak tertulis: %v", err)
|
|
}
|
|
|
|
content, err := os.ReadFile(deskDst)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(string(content), "Icon="+path+"\n") {
|
|
t.Errorf("baris Icon= tidak diperbarui:\n%s", content)
|
|
}
|
|
}
|
|
|
|
func TestRefreshIconKeepsDesktopCopyMode(t *testing.T) {
|
|
home := testEnv(t)
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write(append(pngMagic, []byte("baru")...))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
deskDst := filepath.Join(home, ".local/share/applications/y.desktop")
|
|
copyDst := filepath.Join(home, "Desktop", "y.desktop")
|
|
for _, path := range []string{deskDst, copyDst} {
|
|
if err := WriteDesktop(path, Entry{Name: "Y", ExecArgs: []string{"/y"}, Icon: "/lama.png"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := os.Chmod(copyDst, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
sc := store.Shortcut{
|
|
Slug: "y",
|
|
IconSource: srv.URL,
|
|
DesktopPath: deskDst,
|
|
DesktopCopyPath: copyDst,
|
|
}
|
|
if _, err := RefreshIcon(sc); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
st, err := os.Stat(copyDst)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if st.Mode().Perm()&0o111 == 0 {
|
|
t.Errorf("salinan desktop kehilangan mode executable: %v", st.Mode())
|
|
}
|
|
}
|
|
|
|
func TestRefreshIconWithoutOnlineSource(t *testing.T) {
|
|
testEnv(t)
|
|
sc := store.Shortcut{Slug: "z", IconSource: "file:/tmp/icon.png", DesktopPath: "/tmp/z.desktop"}
|
|
if _, err := RefreshIcon(sc); err == nil {
|
|
t.Error("ikon dari file lokal harus error, bukan diunduh")
|
|
}
|
|
}
|
|
|
|
func TestInstallExecutableSearchesIconOnline(t *testing.T) {
|
|
home := testEnv(t)
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case strings.HasPrefix(r.URL.Path, "/search"):
|
|
_, _ = w.Write([]byte(`{"icons":["mdi:winbox"]}`))
|
|
case r.URL.Path == "/mdi/winbox.svg":
|
|
_, _ = w.Write([]byte(`<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256"></svg>`))
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
iconSetEndpoint(t, srv.URL)
|
|
|
|
src := fakeBinary(t, home, "winbox")
|
|
sc, err := InstallExecutable(ExecOptions{BinaryPath: src, Name: "Winbox", Menu: true})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if sc.IconSource != "search:Winbox" {
|
|
t.Errorf("icon_source = %q", sc.IconSource)
|
|
}
|
|
if filepath.Ext(sc.IconPath) != ".svg" {
|
|
t.Errorf("ikon online harus tersimpan sebagai .svg, dapat %s", sc.IconPath)
|
|
}
|
|
}
|
|
|
|
func TestInstallWebAppUsesIconURL(t *testing.T) {
|
|
testEnv(t)
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/pilih.svg":
|
|
w.Header().Set("Content-Type", "image/svg+xml")
|
|
_, _ = w.Write([]byte(`<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256"></svg>`))
|
|
case "/":
|
|
_, _ = w.Write([]byte(`<html><head><link rel="icon" href="/situs.png"></head></html>`))
|
|
default:
|
|
_, _ = w.Write(append(pngMagic, []byte("favicon-situs")...))
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
|
|
sc, err := InstallWebApp(WebOptions{
|
|
URL: "https://example.com",
|
|
Name: "Example",
|
|
Browser: BrowserArg{Name: "Chromium", Exec: []string{"/usr/bin/chromium"}},
|
|
IconURL: srv.URL + "/pilih.svg",
|
|
Menu: true,
|
|
Desktop: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if sc.IconSource != srv.URL+"/pilih.svg" {
|
|
t.Errorf("ikon pilihan user harus dipakai, icon_source = %q", sc.IconSource)
|
|
}
|
|
if filepath.Ext(sc.IconPath) != ".svg" {
|
|
t.Errorf("ikon = %s", sc.IconPath)
|
|
}
|
|
}
|
|
|
|
func TestInstallWebAppUsesIconDataFromUI(t *testing.T) {
|
|
testEnv(t)
|
|
|
|
svg := []byte(`<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256"></svg>`)
|
|
sc, err := InstallWebApp(WebOptions{
|
|
URL: "https://example.com",
|
|
Name: "Example",
|
|
Browser: BrowserArg{Name: "Chromium", Exec: []string{"/usr/bin/chromium"}},
|
|
IconURL: "https://api.iconify.design/mdi/example.svg?height=256",
|
|
IconData: svg, // sudah diunduh UI: tidak perlu request ulang
|
|
Menu: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if sc.IconSource != "https://api.iconify.design/mdi/example.svg?height=256" {
|
|
t.Errorf("icon_source = %q", sc.IconSource)
|
|
}
|
|
data, err := os.ReadFile(sc.IconPath)
|
|
if err != nil || string(data) != string(svg) {
|
|
t.Errorf("ikon tersimpan salah: %v %d byte", err, len(data))
|
|
}
|
|
}
|