Twenty account components: SettingsList, OptionCycler, KeyBindings, ControllerMap, ProfileCard, CharacterCreator, LoginForm, PasswordGrid, SessionList, LanguagePicker, PalettePicker, VolumeMixer, DifficultySelect, AccessibilityOptions, AccountSwitcher, PlayerSelect, TwoFactor, About, PatchNotes and FeedbackForm. The components never check credentials, and no secret reaches the log: LoginForm reports Submitted and the caller counts Failures; each new failure clears the password, refocuses it, shakes the form and names no field. TwoFactor builds on input.PinInput and drains an epoch-aligned period bar. PalettePicker draws each tile in its own palette on a pixel.Pad grid. pixel.Settings.Reduce stills motion like the system preference, the micro font gains '?', display.Face draws avatar faces anywhere, and input.PinInput pastes on Cmd+V as native hosts send it.
147 lines
5.4 KiB
Go
147 lines
5.4 KiB
Go
package account_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/account"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
|
)
|
|
|
|
func TestSettingsListSetsRowsUnderTitles(t *testing.T) {
|
|
tt := view(500, 300, newWorld(), func(c *ui.Context) {
|
|
account.SettingsList(c, "Settings", []account.SettingsSection{
|
|
{Title: "AUDIO", Settings: []account.Setting{{Label: "Music", Control: func() {
|
|
ui.ButtonBase(c).Label("toggle").Size(20, 20)
|
|
}}}},
|
|
}, account.SettingsListOptions{})
|
|
})
|
|
// The row label only shows; the control carries the name.
|
|
title, ctl := find(t, tt, "AUDIO"), find(t, tt, "toggle")
|
|
if _, named := tt.Find("Music"); named || !(title.Y+title.H <= ctl.Y && ctl.X > 100) {
|
|
t.Fatalf("title %v, control %v, label named %v", title, ctl, named)
|
|
}
|
|
}
|
|
|
|
func TestOptionCyclerWrapsBothWays(t *testing.T) {
|
|
sel, changed := 0, false
|
|
opts := []string{"SLOW", "NORMAL", "FAST"}
|
|
tt := view(400, 200, newWorld(), func(c *ui.Context) {
|
|
changed = account.OptionCycler(c, "Speed", opts, &sel, account.OptionCyclerOptions{}).Changed || changed
|
|
})
|
|
r := find(t, tt, "Speed")
|
|
tt.ClickAt(r.X+4, r.Y+r.H/2)
|
|
if sel != 2 || !changed {
|
|
t.Fatalf("a click on the left half from 0 gave %d", sel)
|
|
}
|
|
tt.Key(0, ui.KeyRight)
|
|
if sel != 0 {
|
|
t.Fatalf("Right from the last gave %d", sel)
|
|
}
|
|
tt.ClickAt(r.X+r.W-4, r.Y+r.H/2)
|
|
tt.Key(0, ui.KeyLeft)
|
|
if sel != 0 {
|
|
t.Fatalf("right half then Left gave %d", sel)
|
|
}
|
|
mustPanic(t, "a choice past the options", func() {
|
|
view(400, 200, newWorld(), func(c *ui.Context) {
|
|
s := 3
|
|
account.OptionCycler(c, "S", opts, &s, account.OptionCyclerOptions{})
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestKeyBindingsRebindAndMarkConflicts(t *testing.T) {
|
|
binds := []account.Binding{{Action: "Jump", Key: ui.KeySpace}, {Action: "Attack", Key: ui.KeyZ}, {Action: "Dash", Key: ui.KeyX}}
|
|
changed := false
|
|
tt := view(500, 300, newWorld(), func(c *ui.Context) {
|
|
changed = account.KeyBindings(c, "Keys", binds, account.KeyBindingsOptions{}).Changed || changed
|
|
})
|
|
r := find(t, tt, "Keys")
|
|
x, y, w, _ := dots(r)
|
|
rh := pixel.LineHeight + 4
|
|
bad := func(row int) int { return count(tt.Image(), x+w-40, y+row*rh, 40, rh, pixel.Bad) }
|
|
if bad(1) != 0 || bad(2) != 0 {
|
|
t.Fatal("no conflict yet, but a key is Bad")
|
|
}
|
|
// Click Dash, press Z: Z is Attack's too.
|
|
tt.ClickAt(r.X+20, r.Y+float32(2*2*rh+rh))
|
|
tt.Key(0, ui.KeyZ)
|
|
if binds[2].Key != ui.KeyZ || !changed || bad(1) == 0 || bad(2) == 0 {
|
|
t.Fatalf("Dash on Z: %v, conflict marks %d %d", binds[2].Key, bad(1), bad(2))
|
|
}
|
|
// Waiting, an arrow binds; Escape gives up.
|
|
tt.Key(0, ui.KeyUp)
|
|
tt.Key(0, ui.KeyEnter)
|
|
tt.Key(0, ui.KeyLeft)
|
|
if binds[1].Key != ui.KeyLeft {
|
|
t.Fatalf("Up, Enter, Left bound %v", binds[1].Key)
|
|
}
|
|
tt.Key(0, ui.KeyEnter)
|
|
tt.Key(0, ui.KeyEscape)
|
|
tt.Key(0, ui.KeyQ)
|
|
if binds[1].Key != ui.KeyLeft {
|
|
t.Fatal("Escape did not give up the rebind")
|
|
}
|
|
mustPanic(t, "a key without a cap", func() {
|
|
view(500, 300, newWorld(), func(c *ui.Context) {
|
|
account.KeyBindings(c, "K", []account.Binding{{Action: "Help", Key: ui.KeyEscape}}, account.KeyBindingsOptions{})
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestControllerMapPaintsButtonsAndLabels(t *testing.T) {
|
|
tt := view(700, 300, newWorld(), func(c *ui.Context) {
|
|
account.ControllerMap(c, "Pad", map[account.PadButton]string{account.ButtonA: "JUMP"}, account.ControllerMapOptions{})
|
|
})
|
|
x, y, _, _ := dots(find(t, tt, "Pad"))
|
|
img := tt.Image()
|
|
if at(img, x+70+130, y+45) != pixel.Bad || at(img, x+70+120, y+55) != pixel.Warn || count(img, x+236, y+46, 40, 12, pixel.Ink) == 0 {
|
|
t.Fatal("A is not red, B not gold, or JUMP is missing")
|
|
}
|
|
mustPanic(t, "an unknown button", func() {
|
|
view(700, 300, newWorld(), func(c *ui.Context) {
|
|
account.ControllerMap(c, "P", map[account.PadButton]string{99: "?"}, account.ControllerMapOptions{})
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestProfileCardShowsStats(t *testing.T) {
|
|
tt := view(500, 300, newWorld(), func(c *ui.Context) {
|
|
account.ProfileCard(c, account.Profile{Name: "Aria", Title: "Slayer", Level: 12, Stats: []account.Stat{{Name: "HP", Value: 10, Max: 40}}}, account.ProfileCardOptions{})
|
|
})
|
|
find(t, tt, "Slayer")
|
|
find(t, tt, "LV 12")
|
|
x, y, w, _ := dots(find(t, tt, "HP"))
|
|
// The bar sits between the name and value columns.
|
|
nw, vw := pixel.MicroWidth("HP"), pixel.MicroWidth("10/40")
|
|
inner := w - nw - vw - 12
|
|
if n := count(tt.Image(), x+nw+6, y+4, inner, 1, pixel.Good); n < inner/4-1 || n > inner/4+1 {
|
|
t.Fatalf("HP 10/40 fills %d of %d dots", n, inner)
|
|
}
|
|
mustPanic(t, "a stat past its most", func() {
|
|
view(500, 300, newWorld(), func(c *ui.Context) {
|
|
account.ProfileCard(c, account.Profile{Name: "A", Level: 1, Stats: []account.Stat{{Name: "HP", Value: 5, Max: 4}}}, account.ProfileCardOptions{})
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestCharacterCreatorCyclesTheAvatar(t *testing.T) {
|
|
look, changed := account.Look{}, false
|
|
tt := view(600, 300, newWorld(), func(c *ui.Context) {
|
|
changed = account.CharacterCreator(c, "Hero", &look, account.CharacterCreatorOptions{}).Changed || changed
|
|
})
|
|
find(t, tt, "SPIKY, DOTS, TUNIC, DARK, DARK")
|
|
r := find(t, tt, "HAIR COLOR")
|
|
tt.ClickAt(r.X+r.W-4, r.Y+r.H/2)
|
|
tt.ClickAt(r.X+r.W-4, r.Y+r.H/2)
|
|
img := tt.Image()
|
|
pic := find(t, tt, "SPIKY, DOTS, TUNIC, RED, DARK")
|
|
x, y, _, _ := dots(pic)
|
|
// Spiky hair at row 2, column 5, scale 3.
|
|
if look.HairColor != 2 || !changed || at(img, x+4+3*5, y+4+3*2) != pixel.Bad {
|
|
t.Fatalf("hair color %d, hair dot %v", look.HairColor, at(tt.Image(), x+4+3*5, y+4+3*2))
|
|
}
|
|
}
|