1package modes
2
3import (
4 "context"
5 "os"
6 "path/filepath"
7 "testing"
8 "time"
9
10 "github.com/bnema/zut/packages/tui"
11)
12
13func writeWatchedTheme(t *testing.T, home, body string) string {
14 t.Helper()
15 path := filepath.Join(home, "themes", "live.json")
16 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
17 t.Fatal(err)
18 }
19 if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
20 t.Fatal(err)
21 }
22 return path
23}
24
25func TestWatchThemeSourceTicksPublishesOnlyValidRevisions(t *testing.T) {
26 home := t.TempDir()
27 path := writeWatchedTheme(t, home, `{"colors":{"accent":20}}`)
28 accepted, err := tui.LoadThemeSource(home, "live")
29 if err != nil {
30 t.Fatal(err)
31 }
32 ctx, cancel := context.WithCancel(context.Background())
33 defer cancel()
34 ticks := make(chan time.Time)
35 events := make(chan themeWatchEvent, 3)
36 go watchThemeSourceTicks(ctx, home, "live", accepted, ticks, events)
37
38 if err := os.WriteFile(path, []byte(`{"colors":{"accent":21}}`), 0o644); err != nil {
39 t.Fatal(err)
40 }
41 ticks <- time.Unix(1, 0)
42 select {
43 case event := <-events:
44 if event.source == nil || event.source.File.Colors.Base.Accent == nil || event.source.File.Colors.Base.Accent.Index != 21 {
45 t.Fatalf("valid revision event = %#v", event)
46 }
47 case <-time.After(time.Second):
48 t.Fatal("watcher did not publish valid revision")
49 }
50
51 if err := os.WriteFile(path, []byte(`{"colors":{"accent":999}}`), 0o644); err != nil {
52 t.Fatal(err)
53 }
54 ticks <- time.Unix(2, 0)
55 select {
56 case event := <-events:
57 if event.err == nil || event.source != nil {
58 t.Fatalf("invalid revision event = %#v", event)
59 }
60 case <-time.After(time.Second):
61 t.Fatal("watcher did not report invalid revision")
62 }
63}
64
65func TestWatchThemeSourceTicksRestartsDeletionGraceAfterInvalidReappearance(t *testing.T) {
66 home := t.TempDir()
67 path := writeWatchedTheme(t, home, `{}`)
68 accepted, err := tui.LoadThemeSource(home, "live")
69 if err != nil {
70 t.Fatal(err)
71 }
72 ctx, cancel := context.WithCancel(context.Background())
73 defer cancel()
74 ticks := make(chan time.Time)
75 events := make(chan themeWatchEvent, 1)
76 go watchThemeSourceTicks(ctx, home, "live", accepted, ticks, events)
77
78 if err := os.Remove(path); err != nil {
79 t.Fatal(err)
80 }
81 ticks <- time.Unix(1, 0)
82 if err := os.WriteFile(path, []byte(`{"colors":{"accent":999}}`), 0o644); err != nil {
83 t.Fatal(err)
84 }
85 ticks <- time.Unix(2, 0)
86 select {
87 case event := <-events:
88 if event.err == nil {
89 t.Fatalf("invalid reappearance event = %#v", event)
90 }
91 case <-time.After(time.Second):
92 t.Fatal("watcher did not report invalid reappearance")
93 }
94
95 if err := os.Remove(path); err != nil {
96 t.Fatal(err)
97 }
98 ticks <- time.Unix(3, 0)
99 ticks <- time.Unix(4, 0)
100 select {
101 case event := <-events:
102 t.Fatalf("deletion happened before restarted grace period: %#v", event)
103 default:
104 }
105 ticks <- time.Unix(6, 0)
106 select {
107 case event := <-events:
108 if !event.deleted {
109 t.Fatalf("persistent deletion event = %#v", event)
110 }
111 case <-time.After(time.Second):
112 t.Fatal("watcher did not publish persistent deletion")
113 }
114}