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

229 lines
7.1 KiB
Go
Raw Permalink 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 datetime_test
import (
"testing"
"time"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/datetime"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
func TestCalendarMovesByKeysAndPicksByClick(t *testing.T) {
date := time.Date(2026, 1, 31, 8, 15, 0, 0, time.UTC)
picks := 0
tt := view(400, 300, newClock(), false, func(c *ui.Context) {
if datetime.Calendar(c, "Date", &date, datetime.CalendarOptions{}).Picked {
picks++
}
})
r := find(t, tt, "Date")
// The grid sits under the header and the day names.
grid := ui.Rect{X: r.X, Y: r.Y + r.H - 6*32, W: 7 * 32, H: 6 * 32}
tt.ClickAt(grid.X+3*32+4, grid.Y+4)
if day(date) != "2025-12-31" || picks != 1 {
t.Fatalf("a click on the first row's Wednesday chose %s", day(date))
}
tt.Key(0, ui.KeyRight)
tt.Key(0, ui.KeyPageDown)
if day(date) != "2026-02-01" {
t.Fatalf("Right, Page Down gave %s", day(date))
}
date = time.Date(2026, 1, 31, 8, 15, 0, 0, time.UTC)
tt.Frame()
tt.Key(0, ui.KeyPageDown)
if day(date) != "2026-02-28" {
t.Fatalf("Page Down from January 31 gave %s", day(date))
}
tt.Key(0, ui.KeyDown)
tt.Key(0, ui.KeyHome)
if day(date) != "2026-03-01" || date.Hour() != 8 {
t.Fatalf("Page Down, Down, Home gave %s %d:00", day(date), date.Hour())
}
tt.Key(0, ui.KeyEnd)
tt.Key(0, ui.KeyEnter)
if day(date) != "2026-03-31" || picks != 2 {
t.Fatalf("End, Enter gave %s, %d picks", day(date), picks)
}
keeps(t, tt.Image())
}
func TestCalendarMarksTodayAndTheChosenDay(t *testing.T) {
date := time.Date(2026, 10, 14, 0, 0, 0, 0, time.UTC)
tt := view(400, 300, newClock(), false, func(c *ui.Context) {
datetime.Calendar(c, "Date", &date, datetime.CalendarOptions{})
})
r := find(t, tt, "Date")
top := r.Y + r.H - 6*32
// October 2026 starts Thursday: the 11th is row 2.
today := ui.Rect{X: r.X + 6, Y: top + 2*32 + 28, W: 20, H: 2}
if n := count(t, tt.Image(), today, pixel.Accent); n == 0 {
t.Fatal("today is not marked")
}
chosen := ui.Rect{X: r.X + 3*32, Y: top + 2*32, W: 32, H: 32}
if n := count(t, tt.Image(), chosen, pixel.Shade); n == 0 {
t.Fatal("the chosen day is not sunken")
}
}
func TestOffCalendarStaysPut(t *testing.T) {
for _, inert := range []bool{false, true} {
date := time.Date(2026, 10, 14, 0, 0, 0, 0, time.UTC)
tt := view(400, 300, newClock(), inert, func(c *ui.Context) {
datetime.Calendar(c, "Date", &date, datetime.CalendarOptions{Disabled: !inert})
})
r := find(t, tt, "Date")
tt.ClickAt(r.X+4, r.Y+r.H-4)
tt.Key(0, ui.KeyRight)
click(t, tt, "NEXT")
if day(date) != "2026-10-14" {
t.Fatalf("inert %v: an off calendar moved to %s", inert, day(date))
}
}
}
func TestDateInputOpensACalendar(t *testing.T) {
date := time.Date(2026, 10, 11, 0, 0, 0, 0, time.UTC)
disabled, changes := false, 0
tt := view(400, 400, newClock(), false, func(c *ui.Context) {
if datetime.DateInput(c, "Birthday", &date, datetime.DateInputOptions{Disabled: disabled}).Changed {
changes++
}
})
click(t, tt, "Birthday")
if !shows(tt, "OCT 2026") {
t.Fatalf("no calendar: %q", tt.Texts())
}
tt.Key(0, ui.KeyRight)
tt.Key(0, ui.KeyEnter)
if day(date) != "2026-10-12" || shows(tt, "OCT 2026") || changes != 1 {
t.Fatalf("Right, Enter gave %s", day(date))
}
click(t, tt, "Birthday")
disabled = true
tt.Frame()
if shows(tt, "OCT 2026") {
t.Fatal("the calendar stayed open as its field turned off")
}
keeps(t, tt.Image())
}
func TestTimeInputTypesAndSteps(t *testing.T) {
tm := time.Date(2026, 10, 11, 0, 0, 0, 0, time.UTC)
tt := view(300, 100, newClock(), false, func(c *ui.Context) {
datetime.TimeInput(c, "Wake", &tm, datetime.TimeInputOptions{})
})
click(t, tt, "Wake")
tt.Key(0, ui.KeyLeft)
for _, k := range []ui.Key{ui.Key0, ui.Key7, ui.Key3, ui.Key0} {
tt.Key(0, k)
}
if got := tm.Format("15:04"); got != "07:30" {
t.Fatalf("0730 typed %s", got)
}
tt.Key(0, ui.KeyUp)
tt.Key(0, ui.KeyLeft)
tt.Key(0, ui.KeyDown)
if got := tm.Format("15:04"); got != "06:31" || day(tm) != "2026-10-11" {
t.Fatalf("Up, Left, Down gave %s", got)
}
tt.Key(0, ui.Key2)
tt.Key(0, ui.Key9)
if got := tm.Format("15:04"); got != "09:31" {
t.Fatalf("29 on hours gave %s", got)
}
keeps(t, tt.Image())
}
func TestDateRangePicksAndDithersBetween(t *testing.T) {
start := time.Date(2026, 10, 11, 0, 0, 0, 0, time.UTC)
end := start
tt := view(400, 300, newClock(), false, func(c *ui.Context) {
datetime.DateRange(c, "Trip", &start, &end, datetime.DateRangeOptions{})
})
tt.Key(0, ui.KeyTab)
tt.Key(0, ui.KeyTab)
tt.Key(0, ui.KeyTab)
tt.Key(0, ui.KeyEnter)
tt.Key(0, ui.KeyRight)
tt.Key(0, ui.KeyRight)
tt.Key(0, ui.KeyRight)
tt.Key(0, ui.KeyEnter)
if day(start) != "2026-10-11" || day(end) != "2026-10-14" {
t.Fatalf("Enter, 3×Right, Enter gave %s to %s", day(start), day(end))
}
r := find(t, tt, "Trip")
top := r.Y + r.H - 6*32
between := ui.Rect{X: r.X + 32, Y: top + 2*32, W: 32, H: 32}
if n := count(t, tt.Image(), between, pixel.Accent); n == 0 {
t.Fatal("the days between are not dithered")
}
tt.Key(0, ui.KeyEnter)
tt.Key(0, ui.KeyUp)
tt.Key(0, ui.KeyEnter)
if day(start) != "2026-10-07" || day(end) != "2026-10-14" {
t.Fatalf("a start, then an end before it, gave %s to %s", day(start), day(end))
}
}
func TestMonthAndYearPickers(t *testing.T) {
month, year := time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC), 2026
tt := view(400, 300, newClock(), false, func(c *ui.Context) {
datetime.MonthPicker(c, "Month", &month, datetime.MonthPickerOptions{})
datetime.YearPicker(c, "Year", &year, datetime.YearPickerOptions{})
})
if !shows(tt, "2026") || !shows(tt, "2020–2029") {
t.Fatalf("texts %q", tt.Texts())
}
r := find(t, tt, "Month")
tt.ClickAt(r.X+16, r.Y-20)
tt.ClickAt(r.X+2*r.W/4+4, r.Y+4)
if month.Format("2006-01") != "2025-03" {
t.Fatalf("◀ then MAR gave %s", month.Format("2006-01"))
}
y := find(t, tt, "Year")
tt.ClickAt(y.X+3*56+4, y.Y+32+4)
if year != 2028 {
t.Fatalf("a click on the second row's fourth year gave %d", year)
}
keeps(t, tt.Image())
}
func TestDateInputParksFocusAsItTurnsOff(t *testing.T) {
date := raid
disabled := false
tt := view(400, 400, newClock(), false, func(c *ui.Context) {
datetime.DateInput(c, "Due", &date, datetime.DateInputOptions{Disabled: disabled})
pixel.Text(c, "elsewhere", pixel.TextOptions{})
})
click(t, tt, "Due")
disabled = true
tt.Frame()
// Focused counts containers: root focus would light "elsewhere" too.
if tt.Focused("Due") && !tt.Focused("elsewhere") {
t.Fatal("the focus came back to the field as it turned off")
}
}
func TestDateInputGetsItsFocusBackAfterAVeil(t *testing.T) {
date, inert := raid, false
tt := ui.NewTester(func(c *ui.Context) {
pixel.Use(c, pixel.Settings{Palette: pixel.SpringDay, Now: newClock().now, Inert: inert})
datetime.DateInput(c, "Due", &date, datetime.DateInputOptions{})
pixel.Text(c, "elsewhere", pixel.TextOptions{})
}, 400, 400)
click(t, tt, "Due")
inert = true
tt.Frame()
inert = false
tt.Frame()
if !tt.Focused("Due") || tt.Focused("elsewhere") {
t.Fatal("the field did not get its focus back after the veil")
}
tt.Key(0, ui.KeyEnter)
if !shows(tt, "OCT 2026") {
t.Fatal("Enter did not reopen the calendar")
}
}