Files
8bitMyGoUI/account/prefs_test.go
T
ZacharyZhang-NY c745b082a9 Add account components 386-405; Reduce setting, PinInput Cmd+V
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.
2026-10-12 08:21:39 -04:00

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))
}
}