main
1package flux
2
3import (
4 "context"
5 "os"
6 "path/filepath"
7 "testing"
8 "time"
9)
10
11const testBookmarksOrg = `#+title: Bookmarks
12#+filetags: :bookmarks:_export:
13
14Links worth keeping.
15
16* [[https://tonsky.me/blog/syntax-highlighting/][Syntax highlighting is wrong]] :emacs:design:
17
18<2026-04-01 Tue>
19
20The essay that changed how I think about code colors.
21
22* [[https://beathagenlocher.com/stream/][Beat's /stream/]] :web:design:inspiration:
23
24<2026-04-01 Tue>
25
26The original inspiration for this flux stream.
27
28* [[https://stagex.tools][StageX]] :containers:nix:
29
30<2026-03-20 Thu>
31
32Interesting approach to base images.
33
34* Plain text heading without link
35
36<2026-03-15 Fri>
37
38This one has no URL.
39`
40
41func TestBookmarksFetch(t *testing.T) {
42 dir := t.TempDir()
43 path := filepath.Join(dir, "bookmarks.org")
44 if err := os.WriteFile(path, []byte(testBookmarksOrg), 0644); err != nil {
45 t.Fatal(err)
46 }
47
48 src := &BookmarkSource{File: path}
49 entries, err := src.Fetch(context.Background(), time.Time{})
50 if err != nil {
51 t.Fatalf("Fetch: %v", err)
52 }
53
54 if len(entries) != 4 {
55 t.Fatalf("got %d entries, want 4", len(entries))
56 }
57
58 // First entry — link with tags
59 e := entries[0]
60 if e.Title != "Syntax highlighting is wrong" {
61 t.Errorf("title = %q", e.Title)
62 }
63 if e.URL != "https://tonsky.me/blog/syntax-highlighting/" {
64 t.Errorf("url = %q", e.URL)
65 }
66 if e.Kind != KindBookmark {
67 t.Errorf("kind = %s, want bookmark", e.Kind)
68 }
69 if len(e.Tags) != 2 {
70 t.Errorf("tags = %v, want [emacs design]", e.Tags)
71 }
72 if e.Body == "" {
73 t.Error("body is empty")
74 }
75 t.Logf("entry 0: %q → %q (%s)", e.Title, e.URL, e.Body)
76
77 // Second entry
78 e2 := entries[1]
79 if e2.URL != "https://beathagenlocher.com/stream/" {
80 t.Errorf("url = %q", e2.URL)
81 }
82 if len(e2.Tags) != 3 {
83 t.Errorf("tags = %v, want 3", e2.Tags)
84 }
85
86 // Fourth entry — no link
87 e4 := entries[3]
88 if e4.URL != "" {
89 t.Errorf("expected no URL, got %q", e4.URL)
90 }
91 if e4.Title != "Plain text heading without link" {
92 t.Errorf("title = %q", e4.Title)
93 }
94}
95
96func TestBookmarksFetchSince(t *testing.T) {
97 dir := t.TempDir()
98 path := filepath.Join(dir, "bookmarks.org")
99 if err := os.WriteFile(path, []byte(testBookmarksOrg), 0644); err != nil {
100 t.Fatal(err)
101 }
102
103 src := &BookmarkSource{File: path}
104 since := time.Date(2026, 3, 25, 0, 0, 0, 0, time.UTC)
105 entries, err := src.Fetch(context.Background(), since)
106 if err != nil {
107 t.Fatalf("Fetch: %v", err)
108 }
109
110 if len(entries) != 2 {
111 t.Fatalf("got %d entries since %s, want 2", len(entries), since.Format("2006-01-02"))
112 }
113}
114
115func TestBookmarksFetchMissing(t *testing.T) {
116 src := &BookmarkSource{File: "/nonexistent/bookmarks.org"}
117 entries, err := src.Fetch(context.Background(), time.Time{})
118 if err != nil {
119 t.Fatalf("expected nil error, got: %v", err)
120 }
121 if len(entries) != 0 {
122 t.Errorf("expected 0 entries, got %d", len(entries))
123 }
124}
125
126func TestBookmarksRealFile(t *testing.T) {
127 home := os.Getenv("HOME")
128 path := filepath.Join(home, "desktop", "org", "bookmarks.org")
129 if _, err := os.Stat(path); err != nil {
130 t.Skipf("bookmarks.org not found: %v", err)
131 }
132
133 src := &BookmarkSource{File: path}
134 entries, err := src.Fetch(context.Background(), time.Time{})
135 if err != nil {
136 t.Fatalf("Fetch: %v", err)
137 }
138
139 t.Logf("Found %d bookmark entries", len(entries))
140 for _, e := range entries {
141 t.Logf(" [%s] %s → %s", e.Date.Format("2006-01-02"), e.Title, e.URL)
142 }
143}
144
145func TestParseBookmarkHeading(t *testing.T) {
146 tests := []struct {
147 line string
148 title string
149 url string
150 tags int
151 }{
152 {"* [[https://example.com][Example]] :web:cool:", "Example", "https://example.com", 2},
153 {"* [[https://example.com][Example]]", "Example", "https://example.com", 0},
154 {"* Plain heading :tag:", "Plain heading", "", 1},
155 {"* Plain heading", "Plain heading", "", 0},
156 }
157
158 for _, tt := range tests {
159 be := parseBookmarkHeading(tt.line)
160 if be.title != tt.title {
161 t.Errorf("parseBookmarkHeading(%q).title = %q, want %q", tt.line, be.title, tt.title)
162 }
163 if be.url != tt.url {
164 t.Errorf("parseBookmarkHeading(%q).url = %q, want %q", tt.line, be.url, tt.url)
165 }
166 if len(be.tags) != tt.tags {
167 t.Errorf("parseBookmarkHeading(%q).tags = %v, want %d tags", tt.line, be.tags, tt.tags)
168 }
169 }
170}