package grid import ( "testing" "ticktock/internal/activity" "ticktock/internal/store" ) func TestPaintLoggedLabelAndContinuation(t *testing.T) { g := BuildGrid(30, 7*60, 9*60, nil) // FirstMin 420, Rows 4 (07:00..09:00) es := []store.Entry{{ Key: "2026-07-07-01", Start: at(7, 0), End: at(8, 30), Project: "ARCHER", Description: "work", Duration: 90 * 60 * 1e9, }} cells := paintLogged(g, es) if cells[0].Kind != CellLabel { t.Fatalf("row0 kind=%v, want Label", cells[0].Kind) } if cells[0].Project != "ARCHER" || cells[1].Project != "ARCHER" { t.Errorf("project not carried onto cells: %q / %q", cells[0].Project, cells[1].Project) } if cells[1].Kind != CellCont || cells[2].Kind != CellCont { t.Errorf("rows1-2 should be continuation") } if cells[3].Kind != CellEmpty { t.Errorf("row3 should be empty (session ends 08:30)") } if cells[0].Key != "2026-07-07-01" || cells[2].Key != "2026-07-07-01" { t.Errorf("key not propagated to covered slots") } } func TestEntryKeyAtRow(t *testing.T) { g := BuildGrid(30, 7*60, 9*60, nil) es := []store.Entry{{Key: "K", Start: at(7, 30), End: at(8, 0), Description: "x"}} cells := paintLogged(g, es) if k, ok := entryKeyAtRow(cells, 1); !ok || k != "K" { t.Errorf("row1 → %q,%v want K,true", k, ok) } if _, ok := entryKeyAtRow(cells, 0); ok { t.Errorf("row0 empty should be false") } } func TestLabelForFormatsDurationAndTags(t *testing.T) { e := store.Entry{Description: "emails", Tags: []string{"admin"}, Duration: 50 * 60 * 1e9} got := labelFor(e) if got != "✓ emails [admin] (0:50)" { t.Errorf("labelFor=%q", got) } } func TestPaintActivityLabelsAndContinuation(t *testing.T) { g := BuildGrid(30, 7*60, 10*60, nil) // rows 07:00..10:00 = 6 segs := []activity.Segment{ {Start: at(7, 0), End: at(8, 0), App: "Safari", Title: "GitHub", Domain: "github.com"}, {Start: at(8, 0), End: at(8, 30), App: "Trace"}, // empty title+domain → app fallback {Start: at(8, 30), End: at(9, 30), Idle: true}, } cells := paintActivity(g, segs, nil) if cells[0].Kind != CellLabel || cells[0].Text != "github.com" || cells[0].Act != ActActive { t.Errorf("row0 = %+v, want github.com active label", cells[0]) } if cells[1].Kind != CellCont || cells[1].Act != ActActive { t.Errorf("row1 = %+v, want active continuation", cells[1]) } if cells[2].Text != "Trace" { t.Errorf("row2 = %+v, want app fallback label", cells[2]) } if cells[3].Kind != CellLabel || cells[3].Text != "(idle)" || cells[3].Act != ActIdle { t.Errorf("row3 = %+v, want (idle) label", cells[3]) } if cells[4].Kind != CellCont || cells[4].Act != ActIdle { t.Errorf("row4 = %+v, want idle continuation", cells[4]) } if cells[5].Kind != CellEmpty { t.Errorf("row5 = %+v, want empty gap", cells[5]) } } func TestPaintActivityTitleWhenNoDomain(t *testing.T) { g := BuildGrid(30, 7*60, 8*60, nil) segs := []activity.Segment{{Start: at(7, 0), End: at(7, 30), App: "Trace", Title: "notes.md"}} cells := paintActivity(g, segs, nil) if cells[0].Text != "notes.md" { t.Errorf("row0 = %+v, want title label", cells[0]) } } func TestPaintActivityOverlapPrecedence(t *testing.T) { g := BuildGrid(30, 9*60, 11*60, nil) // rows 09:00,09:30,10:00,10:30 segs := []activity.Segment{ {Start: at(9, 30), End: at(10, 0), App: "Safari", Domain: "github.com"}, {Start: at(10, 0), End: at(10, 30), Idle: true}, } events := []activity.Event{ {Start: at(9, 0), End: at(11, 0), Title: "Sprint review", Calendar: "Archer"}, } cells := paintActivity(g, segs, events) // row0: calendar label — the human-meaningful anchor always wins if cells[0].Kind != CellLabel || cells[0].Text != "▪ Sprint review" || cells[0].Act != ActCalendar { t.Errorf("row0 = %+v, want calendar label", cells[0]) } // row1: active segment beats the calendar continuation if cells[1].Act != ActActive || cells[1].Text != "github.com" { t.Errorf("row1 = %+v, want active over cal-cont", cells[1]) } // row2: idle beats the calendar continuation if cells[2].Act != ActIdle { t.Errorf("row2 = %+v, want idle over cal-cont", cells[2]) } // row3: nothing else there — calendar continuation fills the slot if cells[3].Kind != CellCont || cells[3].Act != ActCalendar { t.Errorf("row3 = %+v, want calendar continuation", cells[3]) } } func TestPaintActivityCalendarLabelBeatsActive(t *testing.T) { g := BuildGrid(30, 9*60, 10*60, nil) segs := []activity.Segment{{Start: at(9, 0), End: at(10, 0), App: "Safari", Domain: "github.com"}} events := []activity.Event{{Start: at(9, 0), End: at(9, 30), Title: "Standup", Calendar: "Archer"}} cells := paintActivity(g, segs, events) if cells[0].Text != "▪ Standup" || cells[0].Act != ActCalendar { t.Errorf("row0 = %+v, calendar label must beat active", cells[0]) } if cells[1].Act != ActActive { t.Errorf("row1 = %+v, active continues past the event", cells[1]) } }