Brand assets in brand/ (the 512px mark, sizes for each surface, a multi-size .ico). Windows executables carry it as a compiled-in resource (rsrc_windows_amd64.syso from go-winres) so Explorer, the taskbar and the installer show it; installer/build.ps1 therefore uses a plain go build rather than wails build, which would add a second copy and fail the link. The tray icon is the mark with a state dot over its corner - a plain coloured circle read as a generic status light among other icons - rendered from the embedded PNG at 32px so it survives 150% scaling. The desktop app's login, setup and sidebar marks, the head-office web app's mark and favicon, and the engine dashboard's favicon are the same file. Also found while packaging: no wheel so far shipped static/, so the engine's own dashboard at :8010 on a Windows source install would have failed with a missing file. package-data now includes it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
95 lines
3.2 KiB
Go
95 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"image/png"
|
|
"runtime"
|
|
"testing"
|
|
)
|
|
|
|
// The tray icon shipped as a PNG for a while. systray hands the bytes to
|
|
// LoadImageW, which decodes .ico only, so Windows logged one line and drew
|
|
// nothing — and nothing on a Mac could notice. These tests are the substitute
|
|
// for the Windows box we do not have.
|
|
func TestEncodeICOIsAValidIconFile(t *testing.T) {
|
|
b := encodeICO(trayImage(colorFor("ok")))
|
|
if len(b) < 22 {
|
|
t.Fatalf("far too short: %d bytes", len(b))
|
|
}
|
|
if got := b[:6]; !bytes.Equal(got, []byte{0, 0, 1, 0, 1, 0}) {
|
|
t.Errorf("ICONDIR = % x, want 00 00 01 00 01 00", got)
|
|
}
|
|
if b[6] != iconSize || b[7] != iconSize {
|
|
t.Errorf("entry is %dx%d, want %dx%d", b[6], b[7], iconSize, iconSize)
|
|
}
|
|
if bpp := binary.LittleEndian.Uint16(b[12:14]); bpp != 32 {
|
|
t.Errorf("bits per pixel = %d, want 32", bpp)
|
|
}
|
|
// A wrong length here loads as a truncated or garbage icon rather than
|
|
// failing outright, which is the harder version of this bug to spot.
|
|
size := binary.LittleEndian.Uint32(b[14:18])
|
|
off := binary.LittleEndian.Uint32(b[18:22])
|
|
if off != 22 {
|
|
t.Errorf("image offset = %d, want 22", off)
|
|
}
|
|
if int(off)+int(size) != len(b) {
|
|
t.Errorf("entry claims %d bytes at %d, file is %d", size, off, len(b))
|
|
}
|
|
if h := binary.LittleEndian.Uint32(b[22:26]); h != 40 {
|
|
t.Errorf("BITMAPINFOHEADER size = %d, want 40", h)
|
|
}
|
|
// Height must be doubled for the implied AND mask or Windows draws the
|
|
// bottom half of the icon stretched over the whole square.
|
|
if hh := int32(binary.LittleEndian.Uint32(b[30:34])); hh != int32(iconSize*2) {
|
|
t.Errorf("biHeight = %d, want %d", hh, iconSize*2)
|
|
}
|
|
}
|
|
|
|
func TestEncodeICOPixelsAreBGRABottomUp(t *testing.T) {
|
|
want := colorFor("error") // red: distinguishable from B and G if swapped
|
|
img := trayImage(want)
|
|
b := encodeICO(img)
|
|
// Centre of the state dot, which is solid fill. Bottom-up means image row
|
|
// y lands at DIB row iconSize-1-y counting from the start.
|
|
x, y := iconSize-6-1, iconSize-6-1
|
|
row := iconSize - 1 - y
|
|
i := 22 + 40 + (row*iconSize+x)*4
|
|
got := b[i : i+4]
|
|
if !bytes.Equal(got, []byte{want.B, want.G, want.R, 0xFF}) {
|
|
t.Errorf("centre pixel = % x, want % x (BGRA)",
|
|
got, []byte{want.B, want.G, want.R, 0xFF})
|
|
}
|
|
}
|
|
|
|
func TestIconForMatchesThePlatformDecoder(t *testing.T) {
|
|
b := iconFor("ok")
|
|
pngMagic := []byte{0x89, 'P', 'N', 'G'}
|
|
if runtime.GOOS == "windows" {
|
|
if bytes.HasPrefix(b, pngMagic) {
|
|
t.Fatal("Windows tray icon is a PNG; LoadImageW will refuse it")
|
|
}
|
|
if !bytes.HasPrefix(b, []byte{0, 0, 1, 0}) {
|
|
t.Fatalf("Windows tray icon is not an ICO: % x", b[:4])
|
|
}
|
|
return
|
|
}
|
|
if _, err := png.Decode(bytes.NewReader(b)); err != nil {
|
|
t.Fatalf("non-Windows tray icon is not a decodable PNG: %v", err)
|
|
}
|
|
}
|
|
|
|
// The four states exist to be told apart at a glance; identical bytes would
|
|
// mean the icon never changes and the amber "running but blind" state — the
|
|
// one that otherwise goes unnoticed for weeks — looks exactly like healthy.
|
|
func TestEveryStateLooksDifferent(t *testing.T) {
|
|
seen := map[string]string{}
|
|
for _, s := range []string{"ok", "warn", "error", "stopped"} {
|
|
k := string(iconFor(s))
|
|
if prev, dup := seen[k]; dup {
|
|
t.Errorf("%q and %q render identically", prev, s)
|
|
}
|
|
seen[k] = s
|
|
}
|
|
}
|