Files
ZacharyZhang-NY a2f2796455 Add datetime components 196-215; Kit.At; inert stepper arrows
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.
2026-10-11 23:56:10 -04:00

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