Lists, tables, trees, grids, inventories and records in data/. Outline and GridView build on ui.List: ui.Outline strokes its arrows and ui.GridView spaces and tints items. The pixel theme keeps MyGo's row highlights clear so scrolled rows stay on whole dots; off lists take a veil against the wheel. Adds Canvas.Silhouette, SwitchOptions Label and exports Rarity.Slot.
235 lines
6.9 KiB
Go
235 lines
6.9 KiB
Go
package input_test
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
|
)
|
|
|
|
func TestEditableTextEnterKeepsEscapeReverts(t *testing.T) {
|
|
hero, changes := "Hero", 0
|
|
tt := view(400, 200, nil, func(c *ui.Context) {
|
|
input.Button(c, "before", input.ButtonOptions{})
|
|
if input.EditableText(c, "Name", &hero, input.EditableTextOptions{}).Changed {
|
|
changes++
|
|
}
|
|
})
|
|
click(t, tt, "before")
|
|
tt.Key(0, ui.KeyTab)
|
|
tt.Key(0, ui.KeyEnter)
|
|
if _, ok := tt.TextCaret(); !ok {
|
|
t.Fatal("Enter did not open a field")
|
|
}
|
|
tt.Key(ui.Cmd, ui.KeyA)
|
|
tt.Type("Zed")
|
|
tt.Key(0, ui.KeyEnter)
|
|
if hero != "Zed" || changes != 1 || !tt.Focused("Name") {
|
|
t.Fatalf("Enter kept %q after %d changes, focus back %v", hero, changes, tt.Focused("Name"))
|
|
}
|
|
if _, ok := tt.TextCaret(); ok {
|
|
t.Fatal("the field stayed open")
|
|
}
|
|
r := find(t, tt, "Name")
|
|
tt.ClickAt(r.X+r.W/2, r.Y+r.H/2)
|
|
tt.ClickAt(r.X+r.W/2, r.Y+r.H/2)
|
|
if _, ok := tt.TextCaret(); !ok {
|
|
t.Fatal("a double click did not open the field")
|
|
}
|
|
tt.Type("ox")
|
|
tt.Key(0, ui.KeyEscape)
|
|
if hero != "Zed" || changes != 1 {
|
|
t.Fatalf("Escape kept %q", hero)
|
|
}
|
|
if _, ok := tt.TextCaret(); ok {
|
|
t.Fatal("a double click did not open the field, or Escape left it open")
|
|
}
|
|
}
|
|
|
|
func TestFieldAndFieldsetLayOutTheirNotes(t *testing.T) {
|
|
name := ""
|
|
tt := view(400, 300, nil, func(c *ui.Context) {
|
|
input.Fieldset(c, "Hero", input.FieldsetOptions{}, func() {
|
|
input.Field(c, "Call sign", input.FieldOptions{Description: "Shown on the board.", Error: "Taken!"}, func() {
|
|
input.TextInput(c, "sign", &name, input.TextInputOptions{})
|
|
})
|
|
})
|
|
})
|
|
label, field, note, bad := find(t, tt, "Call sign"), find(t, tt, "sign"), find(t, tt, "Shown on the board."), find(t, tt, "Taken!")
|
|
if !(label.Y < field.Y && field.Y < note.Y && note.Y < bad.Y) {
|
|
t.Fatal("not label, control, description, error from top down")
|
|
}
|
|
if !has(slots(t, tt.Image(), ui.Rect{X: bad.X - 30, Y: bad.Y, W: 30, H: bad.H}), pixel.Bad) {
|
|
t.Fatal("the error has no Bad sign")
|
|
}
|
|
set := find(t, tt, "Hero")
|
|
if !has(slots(t, tt.Image(), ui.Rect{X: set.X, Y: set.Y, W: set.W, H: 2 * pixel.LineHeight}), pixel.Accent) || label.X <= set.X {
|
|
t.Fatal("the fieldset has no legend, or its fields sit on its edge")
|
|
}
|
|
}
|
|
|
|
func TestColorSlotPicks(t *testing.T) {
|
|
slot, changes := pixel.Slot(3), 0
|
|
tt := view(400, 200, nil, func(c *ui.Context) {
|
|
if input.ColorSlot(c, "Paint", &slot, input.ColorSlotOptions{}).Changed {
|
|
changes++
|
|
}
|
|
})
|
|
at(t, tt, "Paint", 6, 12+6)
|
|
if slot != pixel.Slot(8) {
|
|
t.Fatalf("a click on the ninth cell gave %v", slot)
|
|
}
|
|
tt.Key(0, ui.KeyUp)
|
|
tt.Key(0, ui.KeyRight)
|
|
if slot != pixel.Slot(1) || changes != 3 {
|
|
t.Fatalf("Up, Right gave %v after %d changes", slot, changes)
|
|
}
|
|
}
|
|
|
|
func TestJoystickDragsNudgesAndSprings(t *testing.T) {
|
|
clk := &clock{time.Unix(3_000_000, 0)}
|
|
var x, y float64
|
|
tt := view(400, 200, clk, func(c *ui.Context) {
|
|
r := input.Joystick(c, "Stick", input.JoystickOptions{})
|
|
x, y = r.X, r.Y
|
|
})
|
|
r := find(t, tt, "Stick")
|
|
cx, cy := r.X+r.W/2, r.Y+r.H/2
|
|
tt.Press(cx, cy)
|
|
tt.Move(cx+60, cy)
|
|
if math.Abs(x-1) > 1e-9 || y != 0 {
|
|
t.Fatalf("dragging far right gave %v, %v", x, y)
|
|
}
|
|
tt.Move(cx, cy-12)
|
|
if math.Abs(y+0.5) > 1e-9 {
|
|
t.Fatalf("dragging up half way gave y %v", y)
|
|
}
|
|
tt.Release(cx, cy-12)
|
|
if x != 0 || y != 0 {
|
|
t.Fatal("letting go did not spring back")
|
|
}
|
|
tt.Key(0, ui.KeyDown)
|
|
tt.Key(0, ui.KeyDown)
|
|
if y != 0.5 {
|
|
t.Fatalf("two nudges gave y %v", y)
|
|
}
|
|
clk.t = clk.t.Add(time.Second)
|
|
tt.Frame()
|
|
if y != 0 {
|
|
t.Fatal("a nudge did not spring back")
|
|
}
|
|
}
|
|
|
|
func TestCheatCodeUnlocks(t *testing.T) {
|
|
unlocked := 0
|
|
tt := view(400, 200, nil, func(c *ui.Context) {
|
|
if input.CheatCode(c, "Cheat", input.CheatCodeOptions{}).Unlocked {
|
|
unlocked++
|
|
}
|
|
})
|
|
click(t, tt, "Cheat")
|
|
r := find(t, tt, "Cheat")
|
|
if has(slots(t, tt.Image(), r), pixel.Accent) {
|
|
t.Fatal("an empty trail is lit")
|
|
}
|
|
for _, k := range []ui.Key{ui.KeyUp, ui.KeyUp, ui.KeyUp, ui.KeyDown, ui.KeyDown, ui.KeyLeft, ui.KeyRight, ui.KeyLeft, ui.KeyRight, ui.KeyB} {
|
|
tt.Key(0, k)
|
|
}
|
|
if unlocked != 0 || !has(slots(t, tt.Image(), r), pixel.Accent) {
|
|
t.Fatal("unlocked early, or the trail is not lit")
|
|
}
|
|
tt.Key(0, ui.KeyA)
|
|
if unlocked != 1 || !has(slots(t, tt.Image(), r), pixel.Good) {
|
|
t.Fatal("the code did not unlock")
|
|
}
|
|
}
|
|
|
|
func TestCheatCodeBreaksOnOtherKeys(t *testing.T) {
|
|
unlocked := 0
|
|
tt := view(400, 200, nil, func(c *ui.Context) {
|
|
if input.CheatCode(c, "Cheat", input.CheatCodeOptions{Sequence: []ui.Key{ui.KeyA, ui.KeyB}}).Unlocked {
|
|
unlocked++
|
|
}
|
|
})
|
|
click(t, tt, "Cheat")
|
|
r := find(t, tt, "Cheat")
|
|
tt.Key(0, ui.KeyA)
|
|
tt.Key(0, ui.KeyX)
|
|
if !has(slots(t, tt.Image(), r), pixel.Faint) {
|
|
t.Fatal("the wrong key is not in the trail")
|
|
}
|
|
tt.Key(0, ui.KeyB)
|
|
if unlocked != 0 {
|
|
t.Fatal("A, X, B unlocked A, B")
|
|
}
|
|
tt.Key(0, ui.KeyA)
|
|
tt.Key(0, ui.KeyB)
|
|
if unlocked != 1 {
|
|
t.Fatal("A, B did not unlock")
|
|
}
|
|
}
|
|
|
|
func TestSlotPickerSpins(t *testing.T) {
|
|
reel, changes := 0, 0
|
|
tt := view(400, 200, nil, func(c *ui.Context) {
|
|
if input.SlotPicker(c, "Reel", &reel, []string{"Cherry", "Bell", "Seven"}, input.SlotPickerOptions{}).Changed {
|
|
changes++
|
|
}
|
|
})
|
|
click(t, tt, "Reel")
|
|
tt.Key(0, ui.KeyDown)
|
|
tt.Key(0, ui.KeyUp)
|
|
tt.Key(0, ui.KeyUp)
|
|
if reel != 2 || changes != 3 {
|
|
t.Fatalf("Down, Up, Up gave %d after %d changes", reel, changes)
|
|
}
|
|
r := find(t, tt, "Reel")
|
|
tt.Scroll(r.X+r.W/2, r.Y+r.H/2, 0, 2*2*pixel.LineHeight)
|
|
if reel != 1 {
|
|
t.Fatalf("two rows of wheel gave %d", reel)
|
|
}
|
|
tt.Press(r.X+r.W/2, r.Y+r.H/2)
|
|
tt.Move(r.X+r.W/2, r.Y+r.H/2+2*pixel.LineHeight+4)
|
|
tt.Release(r.X+r.W/2, r.Y+r.H/2+2*pixel.LineHeight+4)
|
|
if reel != 0 {
|
|
t.Fatalf("dragging a row down gave %d", reel)
|
|
}
|
|
}
|
|
|
|
func TestGridCopyFollowsTheLocale(t *testing.T) {
|
|
render := func(l pixel.Locale) (end, banner []pixel.Slot) {
|
|
name := ""
|
|
tt := ui.NewTester(func(c *ui.Context) {
|
|
pixel.Use(c, pixel.Settings{Palette: pixel.SpringDay, Locale: l})
|
|
input.NameEntry(c, "Initials", &name, input.NameEntryOptions{})
|
|
input.CheatCode(c, "Cheat", input.CheatCodeOptions{Sequence: []ui.Key{ui.KeyA}})
|
|
}, 400, 300)
|
|
click(t, tt, "Cheat")
|
|
tt.Key(0, ui.KeyA)
|
|
g, ch := find(t, tt, "Initials"), find(t, tt, "Cheat")
|
|
end = slots(t, tt.Image(), ui.Rect{X: g.X + g.W - 28, Y: g.Y + g.H - 32, W: 28, H: 32})
|
|
banner = slots(t, tt.Image(), ui.Rect{X: ch.X, Y: ch.Y + 28, W: ch.W, H: ch.H - 28})
|
|
return end, banner
|
|
}
|
|
enEnd, enBanner := render(pixel.En)
|
|
zhEnd, zhBanner := render(pixel.Zh)
|
|
if equal(enEnd, zhEnd) || equal(enBanner, zhBanner) {
|
|
t.Fatal("END or the unlock banner ignores the locale")
|
|
}
|
|
}
|
|
|
|
func TestSwitchLabelNamesABarePlate(t *testing.T) {
|
|
lever := false
|
|
tt := view(300, 100, nil, func(c *ui.Context) {
|
|
input.Switch(c, "", &lever, input.SwitchOptions{Label: "Fly"})
|
|
})
|
|
click(t, tt, "Fly")
|
|
if !lever || len(tt.Texts()) != 1 {
|
|
t.Fatalf("switch %v, texts %q", lever, tt.Texts())
|
|
}
|
|
}
|