- Semua SVG diraster ke PNG lebih dulu (install, sync, thumbnail). SVG dari internet sebagian memakai fitur yang tidak didukung renderer sehingga ikon tidak muncul atau proses menggambar berhenti. - fill="currentColor" diganti warna brand (penyebab tersering ikon Material/ Remix gagal diraster), rasterisasi dibungkus recover - Thumbnail pencarian: placeholder + cache unduhan + batas 3 unduhan bersamaan - Kandidat ikon yang gagal dilewati, install tetap lanjut ke ikon default - Race pada data ikon pilihan diperbaiki (mutex); tes -race hijau - Baris status modal pencarian dipindah ke barisnya sendiri (tidak bertabrakan dengan tombol Back/Search di layar berskala besar)
113 lines
3.0 KiB
Go
113 lines
3.0 KiB
Go
package icon
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func icoWithPNG(png []byte) []byte {
|
|
ico := make([]byte, 22+len(png))
|
|
ico[2], ico[3] = 1, 0 // type = icon
|
|
ico[4], ico[5] = 1, 0 // jumlah gambar
|
|
put32 := func(off, v int) {
|
|
ico[off] = byte(v)
|
|
ico[off+1] = byte(v >> 8)
|
|
ico[off+2] = byte(v >> 16)
|
|
ico[off+3] = byte(v >> 24)
|
|
}
|
|
put32(14, len(png)) // bytesInRes
|
|
put32(18, 22) // imageOffset
|
|
copy(ico[22:], png)
|
|
return ico
|
|
}
|
|
|
|
var pngBytes = append([]byte("\x89PNG\r\n\x1a\n"), []byte("payload")...)
|
|
|
|
func TestFetchUsesLinkedIcon(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/":
|
|
io.WriteString(w, `<html><head>`+
|
|
`<link rel="apple-touch-icon" href="/small.ico">`+
|
|
`<link rel="icon" type="image/png" sizes="192x192" href="/big.png">`+
|
|
`</head></html>`)
|
|
case "/big.png":
|
|
w.Write(pngBytes)
|
|
default:
|
|
w.Write(icoWithPNG(pngBytes))
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
|
|
data, ext, err := Fetch(srv.URL)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ext != ".png" || string(data) != string(pngBytes) {
|
|
t.Errorf("ikon terpilih salah: ext=%s data=%q", ext, data)
|
|
}
|
|
}
|
|
|
|
func TestFetchFallsBackToFaviconICO(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/" {
|
|
io.WriteString(w, `<html><head><title>no icon</title></head></html>`)
|
|
return
|
|
}
|
|
w.Write(icoWithPNG(pngBytes))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
data, ext, err := Fetch(srv.URL)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ext != ".png" || string(data) != string(pngBytes) {
|
|
t.Errorf("PNG di dalam .ico harus dipakai: ext=%s data=%q", ext, data)
|
|
}
|
|
}
|
|
|
|
func TestFetchFailsWithoutIcon(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
http.NotFound(w, r)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
if _, _, err := Fetch(srv.URL); err == nil {
|
|
t.Error("tanpa favicon harus error supaya UI pakai ikon default")
|
|
}
|
|
}
|
|
|
|
func TestLargestPNGFromICO(t *testing.T) {
|
|
if got := largestPNGFromICO(icoWithPNG(pngBytes)); string(got) != string(pngBytes) {
|
|
t.Errorf("PNG di dalam .ico tidak terbaca: %q", got)
|
|
}
|
|
if got := largestPNGFromICO([]byte("bukan ico")); got != nil {
|
|
t.Errorf("data bukan ICO harus menghasilkan nil, dapat %q", got)
|
|
}
|
|
// ICO berisi BMP lama (bukan PNG) tidak didukung.
|
|
bmp := make([]byte, 40)
|
|
bmp[0], bmp[1], bmp[2], bmp[3] = 0, 0, 1, 0
|
|
bmp[4], bmp[5] = 1, 0
|
|
if got := largestPNGFromICO(bmp); got != nil {
|
|
t.Errorf("ICO BMP harus mengembalikan nil, dapat %q", got)
|
|
}
|
|
}
|
|
|
|
func TestSniffExt(t *testing.T) {
|
|
if ext := sniffExt(pngBytes); ext != ".png" {
|
|
t.Errorf("sniffExt(pngBytes) = %s", ext)
|
|
}
|
|
if ext := sniffExt([]byte(`<?xml version="1.0"?><svg xmlns="..."><path/></svg>`)); ext != ".svg" {
|
|
t.Errorf("sniffExt(svg) = %s", ext)
|
|
}
|
|
if ext := sniffExt([]byte("\xff\xd8\xff\xe0jpeg")); ext != ".jpg" {
|
|
t.Errorf("sniffExt(jpg) = %s", ext)
|
|
}
|
|
if ext := sniffExt([]byte("plain text")); ext != "" {
|
|
t.Errorf("sniffExt(text) = %s", ext)
|
|
}
|
|
}
|