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.
229 lines
7.1 KiB
Go
229 lines
7.1 KiB
Go
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")
|
||
}
|
||
}
|