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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
package ui
import (
"os"
"path/filepath"
"testing"
"codeberg.org/snonux/tasksamurai/internal/task"
)
// TestExtractFileRef verifies that @path/to/file.txt style references are
// parsed out of free text (and that non-references such as email addresses are
// ignored).
func TestExtractFileRef(t *testing.T) {
cases := []struct {
name string
text string
want string
}{
{"plain reference", "see @notes/todo.txt for details", "notes/todo.txt"},
{"reference at start", "@path/to/file.txt is here", "path/to/file.txt"},
{"absolute path", "check @/etc/hosts now", "/etc/hosts"},
{"trailing punctuation", "open @docs/readme.md.", "docs/readme.md"},
{"email is not a reference", "mail paul@example.com please", ""},
{"no reference", "nothing to open here", ""},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := extractFileRef(tc.text); got != tc.want {
t.Fatalf("extractFileRef(%q) = %q, want %q", tc.text, got, tc.want)
}
})
}
}
// TestResolveFileRefPathTilde verifies that a leading "~" expands to the user's
// home directory while relative paths are left untouched.
func TestResolveFileRefPathTilde(t *testing.T) {
home, err := os.UserHomeDir()
if err != nil {
t.Skipf("no home directory available: %v", err)
}
if got := extractFileRef("edit @~/notes.txt today"); got != filepath.Join(home, "notes.txt") {
t.Fatalf("tilde path = %q, want %q", got, filepath.Join(home, "notes.txt"))
}
if got := extractFileRef("edit @relative/notes.txt"); got != "relative/notes.txt" {
t.Fatalf("relative path = %q, want %q", got, "relative/notes.txt")
}
}
// TestExtractFileRefSpaced verifies the "@ path" form: a space after the @ is
// only treated as a file reference when the resolved path exists on disk, so
// ordinary "@ word" prose does not false-match.
func TestExtractFileRefSpaced(t *testing.T) {
dir := t.TempDir()
existing := filepath.Join(dir, "real.txt")
if err := os.WriteFile(existing, []byte("hi"), 0o644); err != nil {
t.Fatalf("write temp file: %v", err)
}
missing := filepath.Join(dir, "nope.txt")
cases := []struct {
name string
text string
want string
}{
{"spaced existing file", "please open @ " + existing, existing},
{"spaced missing file", "please open @ " + missing, ""},
{"spaced prose not a file", "let's meet @ 5pm today", ""},
{"no-space form still wins", "@main.go and @ " + existing, "main.go"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := extractFileRef(tc.text); got != tc.want {
t.Fatalf("extractFileRef(%q) = %q, want %q", tc.text, got, tc.want)
}
})
}
}
// TestFindTaskFileRefFallsBackToAnnotations verifies that the description is
// scanned first and annotations are used only when the description has no
// reference.
func TestFindTaskFileRefFallsBackToAnnotations(t *testing.T) {
desc := task.Task{Description: "open @main.go"}
if got := findTaskFileRef(&desc); got != "main.go" {
t.Fatalf("description ref = %q, want %q", got, "main.go")
}
ann := task.Task{
Description: "no file here",
Annotations: []task.Annotation{{Description: "see @docs/spec.md"}},
}
if got := findTaskFileRef(&ann); got != "docs/spec.md" {
t.Fatalf("annotation ref = %q, want %q", got, "docs/spec.md")
}
none := task.Task{Description: "plain text"}
if got := findTaskFileRef(&none); got != "" {
t.Fatalf("no ref = %q, want empty", got)
}
}
// TestFindTaskURLPrecedence documents that URL detection is independent from
// file references so handleOpenURL can prefer URLs when both are present.
func TestFindTaskURLPrecedence(t *testing.T) {
both := task.Task{Description: "see https://example.com and @notes.txt"}
if got := findTaskURL(&both); got != "https://example.com" {
t.Fatalf("url = %q, want https://example.com", got)
}
if got := findTaskFileRef(&both); got != "notes.txt" {
t.Fatalf("file ref = %q, want notes.txt", got)
}
}
|