test: add manifest icon and first-load asset size validation
This commit is contained in:
118
templates/brand_assets_test.go
Normal file
118
templates/brand_assets_test.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"image/png"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type manifestIcon struct {
|
||||
Src string `json:"src"`
|
||||
Sizes string `json:"sizes"`
|
||||
}
|
||||
|
||||
func TestManifestIconDimensions(t *testing.T) {
|
||||
data, err := os.ReadFile("../static/assets/manifest.json")
|
||||
if err != nil {
|
||||
t.Fatalf("read manifest: %v", err)
|
||||
}
|
||||
|
||||
var manifest struct {
|
||||
Icons []manifestIcon `json:"icons"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &manifest); err != nil {
|
||||
t.Fatalf("parse manifest: %v", err)
|
||||
}
|
||||
if len(manifest.Icons) == 0 {
|
||||
t.Fatal("manifest has no icons")
|
||||
}
|
||||
|
||||
for _, icon := range manifest.Icons {
|
||||
t.Run(icon.Sizes, func(t *testing.T) {
|
||||
width, height := parseIconSize(t, icon.Sizes)
|
||||
assetURL, err := url.Parse(icon.Src)
|
||||
if err != nil {
|
||||
t.Fatalf("parse icon URL: %v", err)
|
||||
}
|
||||
if assetURL.Query().Get("v") == "" {
|
||||
t.Fatalf("icon URL is not versioned: %q", icon.Src)
|
||||
}
|
||||
|
||||
file, err := os.Open(filepath.Join("..", strings.TrimPrefix(assetURL.Path, "/")))
|
||||
if err != nil {
|
||||
t.Fatalf("open icon: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
config, err := png.DecodeConfig(file)
|
||||
if err != nil {
|
||||
t.Fatalf("decode icon: %v", err)
|
||||
}
|
||||
if config.Width != width || config.Height != height {
|
||||
t.Fatalf("icon dimensions = %dx%d, manifest declares %s", config.Width, config.Height, icon.Sizes)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirstLoadBrandAssetSizes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
width int
|
||||
height int
|
||||
maxBytes int64
|
||||
}{
|
||||
{name: "navigation logo", path: "../static/assets/logo-128.png", width: 166, height: 128, maxBytes: 25 * 1024},
|
||||
{name: "32px favicon", path: "../static/assets/favicon-32.png", width: 32, height: 32, maxBytes: 20 * 1024},
|
||||
{name: "16px favicon", path: "../static/assets/favicon-16.png", width: 16, height: 16, maxBytes: 20 * 1024},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
file, err := os.Open(tt.path)
|
||||
if err != nil {
|
||||
t.Fatalf("open asset: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
t.Fatalf("stat asset: %v", err)
|
||||
}
|
||||
if info.Size() > tt.maxBytes {
|
||||
t.Fatalf("asset size = %d bytes, max %d", info.Size(), tt.maxBytes)
|
||||
}
|
||||
|
||||
config, err := png.DecodeConfig(file)
|
||||
if err != nil {
|
||||
t.Fatalf("decode asset: %v", err)
|
||||
}
|
||||
if config.Width != tt.width || config.Height != tt.height {
|
||||
t.Fatalf("asset dimensions = %dx%d, want %dx%d", config.Width, config.Height, tt.width, tt.height)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func parseIconSize(t *testing.T, value string) (int, int) {
|
||||
t.Helper()
|
||||
widthValue, heightValue, ok := strings.Cut(value, "x")
|
||||
if !ok {
|
||||
t.Fatalf("invalid icon size %q", value)
|
||||
}
|
||||
width, err := strconv.Atoi(widthValue)
|
||||
if err != nil {
|
||||
t.Fatalf("parse icon width: %v", err)
|
||||
}
|
||||
height, err := strconv.Atoi(heightValue)
|
||||
if err != nil {
|
||||
t.Fatalf("parse icon height: %v", err)
|
||||
}
|
||||
return width, height
|
||||
}
|
||||
Reference in New Issue
Block a user