package grid import ( "testing" "time" "ticktock/internal/store" ) func at(h, m int) time.Time { return time.Date(2026, 7, 7, h, m, 0, 0, time.Local) } func TestParseHM(t *testing.T) { m, err := parseHM("07:30") if err != nil || m != 450 { t.Fatalf("got %d err %v, want 450", m, err) } if _, err := parseHM("nope"); err == nil { t.Fatal("want error") } } func TestBuildGridAlignsAndCountsRows(t *testing.T) { // window 07:00..09:00, slot 30 → rows at 07:00,07:30,08:00,08:30 = 4 g := BuildGrid(30, 7*60, 9*60, nil) if g.FirstMin != 420 || g.Rows != 4 { t.Fatalf("got FirstMin=%d Rows=%d, want 420,4", g.FirstMin, g.Rows) } } func TestBuildGridExpandsToCoverEntries(t *testing.T) { // entry 06:10..09:40 must expand window 07:00..08:00 outward, slot 30: // first floors 06:10 → 06:00 (360); last ceils 09:40 → 10:00 (600) es := []store.Entry{{Start: at(6, 10), End: at(9, 40)}} g := BuildGrid(30, 7*60, 8*60, es) if g.FirstMin != 360 { t.Errorf("FirstMin=%d, want 360", g.FirstMin) } last := g.SlotMinute(g.Rows) // one past the last row = window end if last != 600 { t.Errorf("window end=%d, want 600", last) } } func TestRowOfRoundTripAndClamp(t *testing.T) { g := BuildGrid(30, 7*60, 9*60, nil) // FirstMin 420, Rows 4 if g.RowOf(420) != 0 || g.RowOf(449) != 0 || g.RowOf(450) != 1 { t.Errorf("RowOf boundaries wrong") } if g.RowOf(0) != 0 { t.Errorf("below window should clamp to 0") } if g.RowOf(100000) != g.Rows-1 { t.Errorf("above window should clamp to last row") } if g.SlotMinute(2) != 420+60 { t.Errorf("SlotMinute wrong") } } func TestBuildGridRunningEntryUsesStartOnly(t *testing.T) { es := []store.Entry{{Start: at(20, 10)}} // End zero → running g := BuildGrid(30, 7*60, 21*60, es) // running start 20:10 is inside default window; end stays 21:00 if g.SlotMinute(g.Rows) != 21*60 { t.Errorf("running entry must not expand end past window") } }