▍ humdrum codex / ticktock v0.0.2
license AGPL-3.0
1.9 KB raw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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")
	}
}