Calendars, date and time inputs, pickers, clocks, timers, a sun dial, a season wheel, an agenda and a day schedule. Kit.At rebuilds at a moment (relative times, midnight). Stepper and NumberInput arrows stop under Inert. DateInput parks a popup focus as its own.
191 lines
6.3 KiB
Go
191 lines
6.3 KiB
Go
package input_test
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
|
|
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
|
)
|
|
|
|
// Each control, disabled after taking focus, ignores every input.
|
|
func TestDisabledHoldButtonIgnoresAHeldPress(t *testing.T) {
|
|
clk := &clock{time.Unix(3_000_000, 0)}
|
|
confirmed := 0
|
|
tt := view(400, 200, clk, func(c *ui.Context) {
|
|
if input.HoldButton(c, "HOLD", input.HoldButtonOptions{Disabled: true}).Confirmed {
|
|
confirmed++
|
|
}
|
|
})
|
|
r := find(t, tt, "HOLD")
|
|
tt.Press(r.X+r.W/2, r.Y+r.H/2)
|
|
clk.t = clk.t.Add(2 * time.Second)
|
|
tt.Frame()
|
|
if confirmed != 0 {
|
|
t.Fatal("a disabled hold button confirmed")
|
|
}
|
|
}
|
|
|
|
func TestHoldButtonForgetsAHoldWhenDisabled(t *testing.T) {
|
|
clk := &clock{time.Unix(3_000_000, 0)}
|
|
confirmed, off := 0, false
|
|
tt := view(400, 200, clk, func(c *ui.Context) {
|
|
input.Button(c, "before", input.ButtonOptions{})
|
|
if input.HoldButton(c, "HOLD", input.HoldButtonOptions{Disabled: off}).Confirmed {
|
|
confirmed++
|
|
}
|
|
})
|
|
hold := func(d time.Duration) {
|
|
for end := clk.t.Add(d); clk.t.Before(end); clk.t = clk.t.Add(50 * time.Millisecond) {
|
|
tt.Key(0, ui.KeySpace)
|
|
}
|
|
}
|
|
click(t, tt, "before")
|
|
tt.Key(0, ui.KeyTab)
|
|
hold(500 * time.Millisecond)
|
|
off = true
|
|
clk.t = clk.t.Add(100 * time.Millisecond)
|
|
tt.Frame()
|
|
off = false
|
|
tt.Frame()
|
|
click(t, tt, "before")
|
|
tt.Key(0, ui.KeyTab)
|
|
hold(600 * time.Millisecond)
|
|
if confirmed != 0 {
|
|
t.Fatal("a hold from before disabling counted")
|
|
}
|
|
}
|
|
|
|
func TestDisabledWhileFocusedTakesNoInput(t *testing.T) {
|
|
type state struct {
|
|
s, list string
|
|
n, events int
|
|
f float64
|
|
tags []string
|
|
slot pixel.Slot
|
|
}
|
|
cases := []struct {
|
|
name, label string
|
|
build func(c *ui.Context, v *state, off bool)
|
|
}{
|
|
{"Select", "Class", func(c *ui.Context, v *state, off bool) {
|
|
input.Select(c, "Class", &v.s, []string{"A", "B"}, input.SelectOptions{Disabled: off})
|
|
}},
|
|
{"MultiSelect", "Gear", func(c *ui.Context, v *state, off bool) {
|
|
input.MultiSelect(c, "Gear", &v.tags, []string{"A", "B"}, input.MultiSelectOptions{Disabled: off})
|
|
}},
|
|
{"Combobox", "Fruit", func(c *ui.Context, v *state, off bool) {
|
|
input.Combobox(c, "Fruit", &v.s, []string{"Apple"}, input.ComboboxOptions{Disabled: off})
|
|
}},
|
|
{"Autocomplete", "Spell", func(c *ui.Context, v *state, off bool) {
|
|
input.Autocomplete(c, "Spell", &v.s, []string{"Fire"}, input.AutocompleteOptions{Disabled: off})
|
|
}},
|
|
{"TokenField", "Tags", func(c *ui.Context, v *state, off bool) {
|
|
input.TokenField(c, "Tags", &v.tags, input.TokenFieldOptions{Disabled: off})
|
|
}},
|
|
{"Segmented", "B", func(c *ui.Context, v *state, off bool) {
|
|
input.Segmented(c, "Mode", []string{"A", "B", "C"}, &v.n, input.SegmentedOptions{Disabled: off})
|
|
}},
|
|
{"Rating", "2/5", func(c *ui.Context, v *state, off bool) {
|
|
input.Rating(c, "Stars", &v.n, input.RatingOptions{Disabled: off})
|
|
}},
|
|
{"Knob", "Gain", func(c *ui.Context, v *state, off bool) {
|
|
input.Knob(c, "Gain", &v.f, input.KnobOptions{Disabled: off})
|
|
}},
|
|
{"PinInput", "PIN", func(c *ui.Context, v *state, off bool) {
|
|
input.PinInput(c, "PIN", &v.s, input.PinInputOptions{Disabled: off})
|
|
}},
|
|
{"NameEntry", "Initials", func(c *ui.Context, v *state, off bool) {
|
|
input.NameEntry(c, "Initials", &v.s, input.NameEntryOptions{Disabled: off})
|
|
}},
|
|
{"VirtualKeyboard", "Keys", func(c *ui.Context, v *state, off bool) {
|
|
input.VirtualKeyboard(c, "Keys", &v.s, input.VirtualKeyboardOptions{Disabled: off})
|
|
}},
|
|
{"DPad", "Pad", func(c *ui.Context, v *state, off bool) {
|
|
if input.DPad(c, "Pad", input.DPadOptions{Disabled: off}).Pressed != input.None {
|
|
v.events++
|
|
}
|
|
}},
|
|
{"HoldButton", "HOLD", func(c *ui.Context, v *state, off bool) {
|
|
if input.HoldButton(c, "HOLD", input.HoldButtonOptions{Duration: time.Millisecond, Disabled: off}).Confirmed {
|
|
v.events++
|
|
}
|
|
}},
|
|
{"EditableText", "Name", func(c *ui.Context, v *state, off bool) {
|
|
input.EditableText(c, "Name", &v.s, input.EditableTextOptions{Disabled: off})
|
|
}},
|
|
{"ColorSlot", "Paint", func(c *ui.Context, v *state, off bool) {
|
|
input.ColorSlot(c, "Paint", &v.slot, input.ColorSlotOptions{Disabled: off})
|
|
}},
|
|
{"Joystick", "Stick", func(c *ui.Context, v *state, off bool) {
|
|
if input.Joystick(c, "Stick", input.JoystickOptions{Disabled: off}).Moved {
|
|
v.events++
|
|
}
|
|
}},
|
|
{"CheatCode", "Cheat", func(c *ui.Context, v *state, off bool) {
|
|
if input.CheatCode(c, "Cheat", input.CheatCodeOptions{Sequence: []ui.Key{ui.KeyUp}, Disabled: off}).Unlocked {
|
|
v.events++
|
|
}
|
|
}},
|
|
{"SlotPicker", "Reel", func(c *ui.Context, v *state, off bool) {
|
|
input.SlotPicker(c, "Reel", &v.n, []string{"A", "B", "C"}, input.SlotPickerOptions{Disabled: off})
|
|
}},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
var v state
|
|
off := false
|
|
tt := view(400, 300, nil, func(c *ui.Context) {
|
|
tc.build(c, &v, off)
|
|
ui.Box(c).Size(4, 4).Label("elsewhere")
|
|
})
|
|
click(t, tt, tc.label)
|
|
if tc.name == "EditableText" {
|
|
tt.Key(0, ui.KeyEnter)
|
|
}
|
|
off = true
|
|
tt.Frame()
|
|
if _, open := tt.Find("A"); open && tc.name != "Segmented" && tc.name != "SlotPicker" {
|
|
t.Fatal("a disabled control's popup stayed open")
|
|
}
|
|
// Focused counts ancestors: only a shared one may hold it.
|
|
if tt.Focused(tc.label) && !tt.Focused("elsewhere") {
|
|
t.Fatal("a disabled control kept the focus")
|
|
}
|
|
before := fmt.Sprint(v)
|
|
tt.Type("1a,")
|
|
tt.SetClipboard("12")
|
|
tt.Command("paste")
|
|
for _, k := range []ui.Key{ui.KeyUp, ui.KeyDown, ui.KeyLeft, ui.KeyRight, ui.KeyHome, ui.KeySpace, ui.KeyEnter, ui.KeyBackspace} {
|
|
tt.Key(0, k)
|
|
}
|
|
click(t, tt, tc.label)
|
|
if now := fmt.Sprint(v); now != before {
|
|
t.Fatalf("disabled control changed: %s to %s", before, now)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInertSteppersTakeNoPress(t *testing.T) {
|
|
n, v := 5, 5.0
|
|
tt := ui.NewTester(func(c *ui.Context) {
|
|
pixel.Use(c, pixel.Settings{Palette: pixel.SpringDay, Inert: true})
|
|
ui.Row(c).Children(func() {
|
|
input.Stepper(c, "Lives", &n, input.StepperOptions{Max: 9})
|
|
input.NumberInput(c, "Speed", &v, input.NumberInputOptions{Max: 10})
|
|
})
|
|
}, 600, 100)
|
|
s, f := find(t, tt, "Lives"), find(t, tt, "Speed")
|
|
// Arrows sit left of a stepper, right of a field.
|
|
tt.Press(s.X-12, s.Y+8)
|
|
tt.Release(s.X-12, s.Y+8)
|
|
tt.ClickAt(f.X+f.W+10, f.Y+4)
|
|
if n != 5 || v != 5 {
|
|
t.Fatalf("inert arrows set %d and %v", n, v)
|
|
}
|
|
}
|