Files
8bitMyGoUI/icon/icon_test.go
T

178 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package icon_test
import (
"go/ast"
"go/parser"
"go/token"
"path/filepath"
"regexp"
"strconv"
"strings"
"testing"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
type decl struct {
file string
expr ast.Expr
}
var upper = regexp.MustCompile(`([a-z])([A-Z])`)
func kebab(s string) string { return strings.ToLower(upper.ReplaceAllString(s, "$1-$2")) }
// decls maps each icon's kebab name to its source.
func decls(t *testing.T) map[string]decl {
files, _ := filepath.Glob("*.go")
out := map[string]decl{}
for _, f := range files {
if strings.HasSuffix(f, "_test.go") || f == "icon.go" {
continue
}
file, err := parser.ParseFile(token.NewFileSet(), f, nil, 0)
if err != nil {
t.Fatal(err)
}
for _, d := range file.Decls {
if g, ok := d.(*ast.GenDecl); ok && g.Tok == token.VAR {
for _, sp := range g.Specs {
v := sp.(*ast.ValueSpec)
for i, n := range v.Names {
out[kebab(n.Name)] = decl{f, v.Values[i]}
}
}
}
}
}
return out
}
// build rebuilds an icon from its declared art.
func build(t *testing.T, ds map[string]decl, name string) *pixel.Sprite {
call, ok := ds[name].expr.(*ast.CallExpr)
if !ok {
t.Fatalf("%s is not a sprite call", name)
}
sel := call.Fun.(*ast.SelectorExpr)
switch sel.Sel.Name {
case "MustSprite":
art, err := strconv.Unquote(call.Args[0].(*ast.BasicLit).Value)
if err != nil {
t.Fatal(err)
}
return pixel.MustSprite(art)
case "Mirror":
return build(t, ds, kebab(sel.X.(*ast.Ident).Name)).Mirror()
}
t.Fatalf("%s built by %s", name, sel.Sel.Name)
return nil
}
func render(sp *pixel.Sprite, ink pixel.Slot) []byte {
return ui.Render(func(c *ui.Context) {
pixel.Use(c, pixel.Settings{Palette: pixel.SummerDay, Dot: 1})
k := pixel.K(c)
ui.Box(c).Size(12, 12).Draw(func(p *ui.Painter, r ui.Rect) { k.Canvas(p, r).Sprite(0, 0, sp, 0, ink, 1) })
}, 12, 12, 1).Pix
}
func TestEveryIconIsListedWithItsArt(t *testing.T) {
ds := decls(t)
listed := map[string]bool{}
for _, n := range icon.All {
if listed[n.Name] {
t.Errorf("icon %q listed twice", n.Name)
}
listed[n.Name] = true
if _, ok := ds[n.Name]; !ok {
t.Fatalf("listed icon %q has no declaration", n.Name)
}
if string(render(n.Sprite, pixel.Magic)) != string(render(build(t, ds, n.Name), pixel.Magic)) {
t.Errorf("icon %q is bound to another icon's art", n.Name)
}
}
for name := range ds {
if !listed[name] {
t.Errorf("icon %q missing from All", name)
}
}
if len(icon.All) < 100 {
t.Fatalf("%d icons, want at least 100", len(icon.All))
}
}
func TestOneBitIconsTakeTheInk(t *testing.T) {
ds := decls(t)
back, ink := pixel.SummerDay.C(pixel.Back), pixel.SummerDay.C(pixel.Magic)
for _, n := range icon.All {
if ds[n.Name].file == "items.go" {
continue
}
pix, drawn := render(n.Sprite, pixel.Magic), 0
for i := 0; i < len(pix); i += 4 {
switch {
case pix[i] == back.R && pix[i+1] == back.G && pix[i+2] == back.B:
case pix[i] == ink.R && pix[i+1] == ink.G && pix[i+2] == ink.B:
drawn++
default:
t.Fatalf("%s draws a color other than the ink", n.Name)
}
}
if drawn == 0 {
t.Fatalf("%s draws nothing in the ink", n.Name)
}
}
}
func TestIconsAreDistinctTwelveByTwelve(t *testing.T) {
seen := map[string]string{}
for _, n := range icon.All {
if n.Sprite.W != 12 || n.Sprite.H != 12 || n.Sprite.Frames() != 1 {
t.Fatalf("%s is %d×%d with %d frames", n.Name, n.Sprite.W, n.Sprite.H, n.Sprite.Frames())
}
pix, back, drawn := render(n.Sprite, pixel.Ink), pixel.SummerDay.C(pixel.Back), 0
for i := 0; i < len(pix); i += 4 {
if pix[i] != back.R || pix[i+1] != back.G || pix[i+2] != back.B {
drawn++
}
}
if drawn == 0 {
t.Fatalf("%s draws nothing", n.Name)
}
key := string(pix)
if other, ok := seen[key]; ok {
t.Fatalf("%s and %s are the same picture", n.Name, other)
}
seen[key] = n.Name
}
}
func TestItemsAreOutlined(t *testing.T) {
for name, d := range decls(t) {
if d.file != "items.go" {
continue
}
art, _ := strconv.Unquote(d.expr.(*ast.CallExpr).Args[0].(*ast.BasicLit).Value)
var rows []string
for _, r := range strings.Split(strings.TrimSpace(art), "\n") {
rows = append(rows, strings.TrimSpace(r))
}
for y, row := range rows {
for x := range len(row) {
if strings.IndexByte(".e#", row[x]) >= 0 {
continue
}
for _, n := range [][2]int{{x + 1, y}, {x - 1, y}, {x, y + 1}, {x, y - 1}} {
if n[0] < 0 || n[1] < 0 || n[0] >= len(row) || n[1] >= len(rows) || rows[n[1]][n[0]] == '.' {
t.Fatalf("%s: dot %d,%d touches the outside without an Edge outline", name, x, y)
}
}
}
}
}
}