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

214 lines
5.9 KiB
Go

package overlay_test
import (
"testing"
"time"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/overlay"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
func TestPopoverOpensBelowWithANotch(t *testing.T) {
open := false
tt := view(640, 400, newClock(), false, func(c *ui.Context) {
ui.Column(c).Padding(40).Children(func() {
b := input.Button(c, "MAP", input.ButtonOptions{})
if b.Clicked {
open = !open
}
overlay.Popover(c, "Map", b.Element, &open, overlay.PopoverOptions{}, func() {
pixel.Text(c, "You are here.", pixel.TextOptions{})
})
})
})
click(t, tt, "MAP")
a, p := find(t, tt, "MAP"), find(t, tt, "You are here.")
if p.Y <= a.Y+a.H {
t.Fatalf("panel text at %v, anchor %v", p, a)
}
// The notch tip sits under the anchor's middle.
if tip := slot(t, tt.Image(), int(a.X+a.W/2), int(a.Y+a.H)+1); tip != pixel.Edge && tip != pixel.Face {
t.Fatalf("no notch under the anchor: %v", tip)
}
keeps(t, tt.Image())
tt.Key(0, ui.KeyEscape)
if open || shows(tt, "You are here.") {
t.Fatal("Escape did not close it")
}
}
func TestTooltipWaitsForRestOrKeyboard(t *testing.T) {
clk := newClock()
tt := view(640, 400, clk, false, func(c *ui.Context) {
ui.Column(c).Padding(80).Gap(20).Children(func() {
input.Button(c, "FIRST", input.ButtonOptions{})
overlay.Tooltip(c, "Casts fire", overlay.TooltipOptions{}, func() {
input.Button(c, "FIRE", input.ButtonOptions{})
})
})
})
r := find(t, tt, "FIRE")
tt.Move(r.X+4, r.Y+4)
clk.t = clk.t.Add(300 * time.Millisecond)
tt.Frame()
if shows(tt, "Casts fire") {
t.Fatal("the tip showed before 500 ms")
}
clk.t = clk.t.Add(300 * time.Millisecond)
tt.Frame()
if !shows(tt, "Casts fire") {
t.Fatal("the tip did not show after 600 ms")
}
tt.ClickAt(r.X+4, r.Y+4)
tt.Move(600, 380)
if shows(tt, "Casts fire") {
t.Fatal("focus from a click kept the tip")
}
click(t, tt, "FIRST")
tt.Move(600, 380)
tt.Key(0, ui.KeyTab)
if !tt.Focused("FIRE") || !shows(tt, "Casts fire") {
t.Fatal("keyboard focus did not show the tip")
}
keeps(t, tt.Image())
}
func TestHoverCardStaysWhileThePointerMovesOntoIt(t *testing.T) {
tt := view(640, 400, newClock(), false, func(c *ui.Context) {
ui.Column(c).Padding(40).Children(func() {
overlay.HoverCard(c, "Aria", overlay.HoverCardOptions{}, func() {
pixel.Text(c, "@aria", pixel.TextOptions{})
}, func() {
pixel.Text(c, "Knight, LV 7", pixel.TextOptions{})
})
})
})
if shows(tt, "Knight, LV 7") {
t.Fatal("the card showed unhovered")
}
r := find(t, tt, "@aria")
tt.Move(r.X+4, r.Y+4)
tt.Frame()
card := find(t, tt, "Knight, LV 7")
tt.Move(card.X+4, card.Y+4)
tt.Frame()
if !shows(tt, "Knight, LV 7") {
t.Fatal("moving onto the card hid it")
}
tt.Move(600, 380)
tt.Frame()
if shows(tt, "Knight, LV 7") {
t.Fatal("the card stayed after the pointer left")
}
}
func TestPopconfirmAsksFirst(t *testing.T) {
yes, no := 0, 0
disabled := false
tt := view(640, 400, newClock(), false, func(c *ui.Context) {
ui.Column(c).Padding(40).Children(func() {
r := overlay.Popconfirm(c, "SELL", "Sell the crown?", overlay.PopconfirmOptions{Disabled: disabled})
if r.Confirmed {
yes++
}
if r.Canceled {
no++
}
})
})
click(t, tt, "SELL")
click(t, tt, "YES")
click(t, tt, "SELL")
click(t, tt, "NO")
click(t, tt, "SELL")
tt.Key(0, ui.KeyEscape)
if yes != 1 || no != 2 || shows(tt, "Sell the crown?") {
t.Fatalf("yes %d no %d", yes, no)
}
click(t, tt, "SELL")
disabled = true
tt.Frame()
if shows(tt, "Sell the crown?") {
t.Fatal("the bubble stayed open as its button turned off")
}
click(t, tt, "SELL")
if shows(tt, "Sell the crown?") {
t.Fatal("a disabled popconfirm opened")
}
}
func TestCoachmarkCountsAndGoesOn(t *testing.T) {
next := 0
tt := view(640, 400, newClock(), false, func(c *ui.Context) {
ui.Column(c).Padding(40).Children(func() {
b := input.Button(c, "INVENTORY", input.ButtonOptions{})
if overlay.Coachmark(c, b.Element, "Your items live here.", 2, 3, overlay.CoachmarkOptions{}).Next {
next++
}
})
})
if !shows(tt, "2/3") || find(t, tt, "Your items live here.").Y <= find(t, tt, "INVENTORY").Y {
t.Fatalf("texts %q", tt.Texts())
}
click(t, tt, "NEXT ▶")
if next != 1 {
t.Fatal("NEXT did not report")
}
keeps(t, tt.Image())
}
func TestSpotlightDimsAllButTheTarget(t *testing.T) {
tt := view(640, 400, newClock(), false, func(c *ui.Context) {
ui.Column(c).Padding(60).Children(func() {
b := input.Button(c, "SHOP", input.ButtonOptions{})
overlay.Spotlight(c, b.Element, "Buy potions here.", overlay.SpotlightOptions{})
})
})
tt.Frame()
img, r := tt.Image(), find(t, tt, "SHOP")
if in := count(t, img, r, pixel.Ink); in > int(r.W*r.H)/16 {
t.Fatalf("%d Ink dots veil the target", in)
}
far := ui.Rect{X: 400, Y: 300, W: 100, H: 60}
if n := count(t, img, far, pixel.Ink); n < int(far.W*far.H)/8 {
t.Fatalf("only %d Ink dots far from the target", n)
}
if !shows(tt, "Buy potions here.") {
t.Fatal("no caption")
}
keeps(t, img)
}
func TestPopoverRefusesNegativeWidth(t *testing.T) {
defer func() {
if recover() == nil {
t.Fatal("a negative width did not panic")
}
}()
open := false
view(200, 100, newClock(), false, func(c *ui.Context) {
b := input.Button(c, "X", input.ButtonOptions{})
overlay.Popover(c, "X", b.Element, &open, overlay.PopoverOptions{Width: -1}, func() {})
})
}
func TestPopconfirmParksFocusAsItTurnsOff(t *testing.T) {
disabled := false
tt := view(640, 400, newClock(), false, func(c *ui.Context) {
ui.Column(c).Padding(40).Children(func() {
overlay.Popconfirm(c, "SELL", "Sell?", overlay.PopconfirmOptions{Disabled: disabled})
pixel.Text(c, "elsewhere", pixel.TextOptions{})
})
})
click(t, tt, "SELL")
tt.Key(0, ui.KeyTab)
disabled = true
tt.Frame()
if tt.Focused("SELL") && !tt.Focused("elsewhere") {
t.Fatal("the focus came back to the button as it turned off")
}
}