▍ humdrum codex / ticktock v0.0.2
license AGPL-3.0
3.4 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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package activity

import (
	"os"
	"path/filepath"
	"testing"
	"time"
)

// calSample matches the cal-events helper's output shape (ISO-8601 with
// offset, one object per non-all-day event, tagged with its calendar name).
const calSample = `[
  {"title": "Standup", "start": "2026-07-07T09:00:00-05:00", "end": "2026-07-07T09:15:00-05:00", "calendar": "Archer"},
  {"title": "Dentist", "start": "2026-07-07T13:00:00-05:00", "end": "2026-07-07T14:00:00-05:00", "calendar": "Personal"},
  {"title": "Focus block", "start": "2026-07-07T15:00:00-05:00", "end": "2026-07-07T17:00:00-05:00", "calendar": "Focus"}
]`

func TestParseEventsSample(t *testing.T) {
	events := parseEvents([]byte(calSample), CalendarFilter{})
	if len(events) != 3 {
		t.Fatalf("want 3 events, got %d", len(events))
	}
	if events[0].Title != "Standup" || events[0].Calendar != "Archer" {
		t.Errorf("event0 = %+v", events[0])
	}
	wantStart, _ := time.Parse(time.RFC3339, "2026-07-07T09:00:00-05:00")
	if !events[0].Start.Equal(wantStart) {
		t.Errorf("start = %v, want %v", events[0].Start, wantStart)
	}
	if parseEvents([]byte("not json"), CalendarFilter{}) != nil {
		t.Error("bad output ⇒ empty slice")
	}
}

func TestCalendarFilterAllow(t *testing.T) {
	cases := []struct {
		name   string
		filter CalendarFilter
		cal    string
		want   bool
	}{
		{"empty filter passes all", CalendarFilter{}, "Personal", true},
		{"show allowlist keeps listed", CalendarFilter{Show: []string{"Archer"}}, "Archer", true},
		{"show allowlist drops unlisted", CalendarFilter{Show: []string{"Archer"}}, "Personal", false},
		{"show is case-insensitive", CalendarFilter{Show: []string{"archer"}}, "Archer", true},
		{"hide drops listed", CalendarFilter{Hide: []string{"Holidays"}}, "Holidays", false},
		{"hide is case-insensitive", CalendarFilter{Hide: []string{"holidays"}}, "Holidays", false},
		{"hide applies after show", CalendarFilter{Show: []string{"Archer", "Focus"}, Hide: []string{"Focus"}}, "Focus", false},
		{"show+hide keeps the rest", CalendarFilter{Show: []string{"Archer", "Focus"}, Hide: []string{"Focus"}}, "Archer", true},
	}
	for _, c := range cases {
		if got := c.filter.Allow(c.cal); got != c.want {
			t.Errorf("%s: Allow(%q)=%v, want %v", c.name, c.cal, got, c.want)
		}
	}
}

func TestParseEventsAppliesFilter(t *testing.T) {
	events := parseEvents([]byte(calSample), CalendarFilter{Show: []string{"Archer", "Focus"}})
	if len(events) != 2 || events[0].Calendar != "Archer" || events[1].Calendar != "Focus" {
		t.Fatalf("filtered = %+v", events)
	}
}

func TestLoadCalendarDegradesGracefully(t *testing.T) {
	if ev, err := LoadCalendar("", "2026-07-07", CalendarFilter{}); err != nil || ev != nil {
		t.Errorf("empty bin ⇒ nil, nil; got %v, %v", ev, err)
	}
	if ev, err := LoadCalendar("/nonexistent/cal-events", "2026-07-07", CalendarFilter{}); err != nil || ev != nil {
		t.Errorf("missing helper ⇒ nil, nil; got %v, %v", ev, err)
	}
}

func TestLoadCalendarRunsHelper(t *testing.T) {
	// Stand in for cal-events with a shell script that echoes the sample.
	bin := filepath.Join(t.TempDir(), "cal-events")
	script := "#!/bin/sh\ncat <<'EOF'\n" + calSample + "\nEOF\n"
	if err := os.WriteFile(bin, []byte(script), 0o755); err != nil {
		t.Fatal(err)
	}
	events, err := LoadCalendar(bin, "2026-07-07", CalendarFilter{Hide: []string{"Personal"}})
	if err != nil {
		t.Fatal(err)
	}
	if len(events) != 2 {
		t.Fatalf("want 2 events after hide, got %+v", events)
	}
}