package sprite import ( "bytes" "encoding/xml" "image/color" "strings" "testing" ) const coin = ` # a tiny coin sprite: coin palette: k = #000000 y = gold Y = #FFF3A0 highlight grid: .kk. kyYk kyyk .kk. ` func parseOK(t *testing.T, src string) *Sprite { t.Helper() s, err := Parse(strings.NewReader(src)) if err != nil { t.Fatalf("parse failed: %v", err) } return s } func TestParseBasics(t *testing.T) { s := parseOK(t, coin) if s.Name != "coin" { t.Errorf("name = %q, want coin", s.Name) } if s.W != 4 || s.H != 4 { t.Errorf("size = %dx%d, want 4x4", s.W, s.H) } if got := s.At(0, 0); got.A != 0 { t.Errorf("(0,0) should be transparent via the implicit '.', got %v", got) } if got := s.At(1, 1); got != (color.NRGBA{255, 215, 0, 255}) { t.Errorf("(1,1) = %v, want gold", got) } if got := s.At(2, 1); got != (color.NRGBA{255, 243, 160, 255}) { t.Errorf("(2,1) = %v, want #FFF3A0", got) } } func TestParseErrors(t *testing.T) { cases := map[string]string{ "ragged rows": "palette:\n a = red\ngrid:\naa\naaa\n", "unknown key": "palette:\n a = red\ngrid:\nab\naa\n", "bad color": "palette:\n a = notacolor\ngrid:\na\n", "no grid": "palette:\n a = red\n", "dup key": "palette:\n a = red\n a = blue\ngrid:\na\n", "multirune key": "palette:\n ab = red\ngrid:\na\n", "colon key": "palette:\n : = red\ngrid:\n.\n", } for name, src := range cases { if _, err := Parse(strings.NewReader(src)); err == nil { t.Errorf("%s: expected an error", name) } } } func TestMaxSize(t *testing.T) { row := strings.Repeat("a", 257) src := "palette:\n a = red\ngrid:\n" + row + "\n" if _, err := Parse(strings.NewReader(src)); err == nil { t.Error("257 px wide sprite should be rejected") } ok := "palette:\n a = red\ngrid:\n" + strings.Repeat(strings.Repeat("a", 256)+"\n", 256) s := parseOK(t, ok) if s.W != 256 || s.H != 256 { t.Errorf("size = %dx%d, want 256x256", s.W, s.H) } } func TestParseColorForms(t *testing.T) { cases := map[string]color.NRGBA{ "none": {0, 0, 0, 0}, "-": {0, 0, 0, 0}, "#F00": {255, 0, 0, 255}, "#F00A": {255, 0, 0, 170}, "#00FF00": {0, 255, 0, 255}, "#00FF0080": {0, 255, 0, 128}, "rgb(1,2,3)": {1, 2, 3, 255}, "rgba(1,2,3,64)": {1, 2, 3, 64}, "rgba(1, 2, 3, .5)": {1, 2, 3, 128}, "RebeccaPurple": {102, 51, 153, 255}, } for in, want := range cases { got, err := ParseColor(in) if err != nil { t.Errorf("ParseColor(%q): %v", in, err) continue } if got != want { t.Errorf("ParseColor(%q) = %v, want %v", in, got, want) } } for _, bad := range []string{"", "#12345", "rgb(300,0,0)", "purpleish"} { if _, err := ParseColor(bad); err == nil { t.Errorf("ParseColor(%q): expected error", bad) } } } func TestImageAndScale(t *testing.T) { s := parseOK(t, coin) img := Image(s, 3) if b := img.Bounds(); b.Dx() != 12 || b.Dy() != 12 { t.Fatalf("scaled bounds = %v, want 12x12", b) } // pixel (1,1) is gold -> block at (3..5, 3..5) if got := img.NRGBAAt(4, 4); got != (color.NRGBA{255, 215, 0, 255}) { t.Errorf("scaled gold pixel = %v", got) } if got := img.NRGBAAt(0, 0); got.A != 0 { t.Errorf("corner should stay transparent, got %v", got) } } func TestSVGOutput(t *testing.T) { s := parseOK(t, coin) var buf bytes.Buffer if err := WriteSVG(&buf, s, 10); err != nil { t.Fatal(err) } out := buf.String() if !strings.Contains(out, `viewBox="0 0 4 4"`) || !strings.Contains(out, `width="40"`) { t.Errorf("svg header wrong:\n%s", out) } // row 2 (y=1) has runs k, yY?, no: k y Y k -> y and Y differ, so no merge. // row 3 (y=2) k yy k -> the two golds merge into one rect of width 2. if !strings.Contains(out, ``) { t.Errorf("expected RLE-merged gold rect:\n%s", out) } var doc struct{ XMLName xml.Name } if err := xml.Unmarshal(buf.Bytes(), &doc); err != nil { t.Errorf("svg is not valid XML: %v", err) } } func TestSheetLayoutAndNaming(t *testing.T) { a := parseOK(t, coin) b := parseOK(t, coin) c := parseOK(t, coin) sh, err := NewSheet([]*Sprite{a, b, c}, 2) if err != nil { t.Fatal(err) } if sh.Cols != 2 || sh.Rows != 2 { t.Errorf("layout = %dx%d, want 2x2", sh.Cols, sh.Rows) } if w, h := sh.Bounds(); w != 8 || h != 8 { t.Errorf("bounds = %dx%d, want 8x8", w, h) } if got := sh.FileBase("walk"); got != "walk_4x4_2x2" { t.Errorf("FileBase = %q, want walk_4x4_2x2", got) } // cell (1,1) in frame 2 (top-right) is gold if got := sh.At(5, 1); got != (color.NRGBA{255, 215, 0, 255}) { t.Errorf("sheet pixel in frame 2 = %v, want gold", got) } // 4th cell (bottom-right) has no sprite -> transparent if got := sh.At(7, 7); got.A != 0 { t.Errorf("empty cell should be transparent, got %v", got) } } func TestSheetSizeMismatch(t *testing.T) { a := parseOK(t, coin) b := parseOK(t, "palette:\n a = red\ngrid:\naa\n") if _, err := NewSheet([]*Sprite{a, b}, 0); err == nil { t.Error("mismatched sprite sizes should be rejected") } } func TestSheetDefaultSingleRow(t *testing.T) { a := parseOK(t, coin) sh, err := NewSheet([]*Sprite{a, a, a}, 0) if err != nil { t.Fatal(err) } if sh.Cols != 3 || sh.Rows != 1 { t.Errorf("default layout = %dx%d, want 3x1", sh.Cols, sh.Rows) } } func TestJPGComposite(t *testing.T) { s := parseOK(t, coin) var buf bytes.Buffer if err := WriteJPG(&buf, s, 1, color.NRGBA{255, 255, 255, 255}, 90); err != nil { t.Fatal(err) } if buf.Len() == 0 { t.Error("empty jpg output") } if err := WriteJPG(&buf, s, 1, Transparent, 150); err == nil { t.Error("quality 150 should be rejected") } }