Files
8bitMyGoUI/input/pick_test.go
T

291 lines
8.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package input_test
import (
"slices"
"testing"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
func TestRadioGroupArrowsChooseAfterAClick(t *testing.T) {
season := 0
tt := view(400, 200, nil, func(c *ui.Context) {
input.RadioGroup(c, []string{"Spring", "Summer", "Autumn"}, &season, input.RadioGroupOptions{})
})
click(t, tt, "Autumn")
tt.Key(0, ui.KeyUp)
if season != 1 {
t.Fatalf("Up after a click chose %d, want 1", season)
}
// A press dragged off a radio chooses nothing.
r := find(t, tt, "Spring")
tt.Press(r.X+4, r.Y+r.H/2)
tt.Move(r.X+4, r.Y+200)
tt.Release(r.X+4, r.Y+200)
if season != 1 {
t.Fatalf("a press dragged off chose %d", season)
}
}
func TestSelectPicksByClickAndKeys(t *testing.T) {
class, changes, disabled := "Knight", 0, false
tt := view(400, 300, nil, func(c *ui.Context) {
if input.Select(c, "Class", &class, []string{"Knight", "Mage", "Thief"}, input.SelectOptions{Disabled: disabled}).Changed {
changes++
}
})
click(t, tt, "Class")
mage := find(t, tt, "Mage")
if !has(slots(t, tt.Image(), find(t, tt, "Knight")), pixel.Accent) {
t.Fatal("the chosen option is not marked")
}
tt.Move(mage.X+mage.W/2, mage.Y+mage.H/2)
if !has(slots(t, tt.Image(), find(t, tt, "Mage")), pixel.Lit) {
t.Fatal("the option under the pointer is not lit")
}
click(t, tt, "Mage")
if class != "Mage" || changes != 1 || tt.HasText("Thief") {
t.Fatalf("click chose %q with %d changes; popup open %v", class, changes, tt.HasText("Thief"))
}
if _, ok := tt.Find("Thief"); ok {
t.Fatal("the popup stayed open")
}
tt.Key(0, ui.KeyTab)
tt.Key(ui.Shift, ui.KeyTab)
tt.Key(0, ui.KeyDown)
tt.Key(0, ui.KeyDown)
tt.Key(0, ui.KeyEnter)
if class != "Thief" || changes != 2 {
t.Fatalf("Down, Down, Enter chose %q", class)
}
tt.Key(0, ui.KeyDown)
tt.Key(0, ui.KeyEscape)
if _, ok := tt.Find("Mage"); ok || !tt.Focused("Class") {
t.Fatal("Escape left the popup open or the focus elsewhere")
}
disabled = true
tt.Frame()
click(t, tt, "Class")
if _, ok := tt.Find("Mage"); ok {
t.Fatal("a disabled select opened")
}
}
func TestComboboxReachesEveryOption(t *testing.T) {
v := ""
letters := []string{"A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9"}
tt := view(400, 400, nil, func(c *ui.Context) {
input.Combobox(c, "Pick", &v, letters, input.ComboboxOptions{})
})
click(t, tt, "Pick")
tt.Key(0, ui.KeyDown)
if _, ok := tt.Find("A9"); ok {
t.Fatal("more than eight rows show")
}
// A resting pointer must not steal the light.
a1 := find(t, tt, "A1")
tt.Move(a1.X+a1.W/2, a1.Y+a1.H/2)
for range 8 {
tt.Key(0, ui.KeyDown)
}
_, first := tt.Find("A1")
if _, last := tt.Find("A9"); first || !last {
t.Fatal("the rows did not follow the lit option")
}
tt.Key(0, ui.KeyEnter)
if v != "A9" {
t.Fatalf("eight Downs and Enter took %q", v)
}
tt.Key(0, ui.KeyDown)
// With the pointer on the first row, the wheel moves.
r := find(t, tt, "A1")
tt.Move(r.X+r.W/2, r.Y+r.H/2)
tt.Scroll(r.X+r.W/2, r.Y+r.H/2, 0, 4*2*pixel.LineHeight)
tt.Key(0, ui.KeyEnter)
if v != "A5" {
t.Fatalf("four rows of wheel then Enter took %q", v)
}
}
func TestBadOptionsPanic(t *testing.T) {
for name, build := range map[string]func(c *ui.Context){
"Select Width": func(c *ui.Context) {
s := "A"
input.Select(c, "S", &s, []string{"A"}, input.SelectOptions{Width: -1})
},
"Knob Step": func(c *ui.Context) {
v := 0.5
input.Knob(c, "K", &v, input.KnobOptions{Step: -0.1})
},
} {
func() {
defer func() {
if recover() == nil {
t.Errorf("%s: a negative value did not panic", name)
}
}()
view(200, 100, nil, build)
}()
}
}
func TestMultiSelectChecksAndShowsTags(t *testing.T) {
var gear []string
changes := 0
tt := view(400, 300, nil, func(c *ui.Context) {
if input.MultiSelect(c, "Gear", &gear, []string{"Sword", "Shield", "Potion"}, input.MultiSelectOptions{}).Changed {
changes++
}
})
trigger := find(t, tt, "Gear")
empty := slots(t, tt.Image(), trigger)
click(t, tt, "Gear")
click(t, tt, "Shield")
click(t, tt, "Potion")
click(t, tt, "Shield")
if !slices.Equal(gear, []string{"Potion"}) || changes != 3 {
t.Fatalf("checks left %q after %d changes", gear, changes)
}
tt.Key(0, ui.KeyEscape)
if _, ok := tt.Find("Sword"); ok || !tt.Focused("Gear") {
t.Fatal("Escape left the popup open or the focus in it")
}
if equal(empty, slots(t, tt.Image(), trigger)) {
t.Fatal("the chosen option shows no tag")
}
tt.Key(0, ui.KeyDown)
if !tt.Focused("Sword") {
t.Fatal("opening did not focus the first option")
}
tt.Key(0, ui.KeySpace)
if !slices.Contains(gear, "Sword") {
t.Fatalf("Space left %q", gear)
}
}
func TestComboboxFiltersAndChooses(t *testing.T) {
fruit := ""
chosen := 0
tt := view(400, 300, nil, func(c *ui.Context) {
if input.Combobox(c, "Fruit", &fruit, []string{"Apple", "Apricot", "Banana", "Cherry"}, input.ComboboxOptions{}).Chosen {
chosen++
}
input.Button(c, "after", input.ButtonOptions{})
})
click(t, tt, "Fruit")
tt.Type("ap")
if _, ok := tt.Find("Banana"); ok {
t.Fatal("Banana does not match ap")
}
find(t, tt, "Apricot")
tt.Key(0, ui.KeyDown)
tt.Key(0, ui.KeyEnter)
if fruit != "Apricot" || chosen != 1 {
t.Fatalf("Down, Enter took %q", fruit)
}
tt.Key(ui.Cmd, ui.KeyA)
tt.Type("b")
click(t, tt, "Banana")
if fruit != "Banana" || !tt.Focused("Fruit") {
t.Fatalf("a click took %q; focus kept %v", fruit, tt.Focused("Fruit"))
}
tt.Key(ui.Cmd, ui.KeyA)
tt.Type("c")
find(t, tt, "Cherry")
tt.Key(0, ui.KeyEscape)
if _, ok := tt.Find("Cherry"); ok {
t.Fatal("Escape left the list open")
}
tt.Type("h")
find(t, tt, "Cherry")
tt.Key(0, ui.KeyTab)
if _, ok := tt.Find("Cherry"); ok || !tt.Focused("after") {
t.Fatal("leaving the field left the list open")
}
}
func TestAutocompleteGhostsAndAccepts(t *testing.T) {
monster, accepted := "", 0
tt := view(400, 200, nil, func(c *ui.Context) {
if input.Autocomplete(c, "Monster", &monster, []string{"Dragon", "Drake", "Dungeon"}, input.AutocompleteOptions{}).Accepted {
accepted++
}
input.Button(c, "after", input.ButtonOptions{})
})
click(t, tt, "Monster")
tt.Type("zz")
if has(slots(t, tt.Image(), find(t, tt, "Monster")), pixel.Faint) {
t.Fatal("a ghost shows without a completion")
}
tt.Key(ui.Cmd, ui.KeyA)
tt.Type("dra")
if !has(slots(t, tt.Image(), find(t, tt, "Monster")), pixel.Faint) {
t.Fatal("no ghost completion")
}
tt.Key(0, ui.KeyTab)
if monster != "Dragon" || accepted != 1 || !tt.Focused("Monster") {
t.Fatalf("Tab took %q, focus kept %v", monster, tt.Focused("Monster"))
}
tt.Key(ui.Cmd, ui.KeyA)
tt.Type("dun")
tt.Key(0, ui.KeyEnter)
if monster != "Dungeon" || accepted != 2 {
t.Fatalf("Enter took %q", monster)
}
tt.Key(0, ui.KeyTab)
if !tt.Focused("after") {
t.Fatal("Tab without a completion kept the focus")
}
}
func TestTokenFieldAddsAndRemoves(t *testing.T) {
var tags []string
tt := view(400, 200, nil, func(c *ui.Context) {
input.TokenField(c, "Tags", &tags, input.TokenFieldOptions{})
})
click(t, tt, "Tags")
tt.Type("fire")
tt.Key(0, ui.KeyEnter)
tt.Type("ice,wind,")
tt.Type("fire")
tt.Key(0, ui.KeyEnter)
if !slices.Equal(tags, []string{"fire", "ice", "wind"}) {
t.Fatalf("Enter and commas gave %q", tags)
}
tt.Type("x")
tt.Key(0, ui.KeyBackspace)
tt.Key(0, ui.KeyBackspace)
if !slices.Equal(tags, []string{"fire", "ice"}) {
t.Fatalf("Backspace on empty gave %q", tags)
}
click(t, tt, "REMOVE fire")
if !slices.Equal(tags, []string{"ice"}) {
t.Fatalf("the × gave %q", tags)
}
}
func TestSegmentedClicksAndArrows(t *testing.T) {
mode, changes := 0, 0
tt := view(400, 200, nil, func(c *ui.Context) {
if input.Segmented(c, "Mode", []string{"EASY", "NORMAL", "HARD"}, &mode, input.SegmentedOptions{}).Changed {
changes++
}
})
click(t, tt, "HARD")
tt.Key(0, ui.KeyLeft)
if mode != 1 || changes != 2 {
t.Fatalf("click and Left gave %d after %d changes", mode, changes)
}
// One dot in from each segment's corner is its fill.
fill := func(l string) pixel.Slot {
r := find(t, tt, l)
return slots(t, tt.Image(), ui.Rect{X: r.X + 4, Y: r.Y + 4, W: 2, H: 2})[0]
}
if fill("NORMAL") != pixel.Accent || fill("HARD") == pixel.Accent {
t.Fatal("the chosen segment is not the Accent one")
}
}