ui_test.go

  1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2//
  3// SPDX-License-Identifier: AGPL-3.0-or-later
  4
  5package init //nolint:testpackage // testing internal validators
  6
  7import (
  8	"errors"
  9	"testing"
 10)
 11
 12func TestValidateKeyFormat_Valid(t *testing.T) {
 13	t.Parallel()
 14
 15	tests := []struct {
 16		name  string
 17		input string
 18	}{
 19		{"simple key", "work"},
 20		{"key with hyphen", "q1-goals"},
 21		{"key with numbers", "area2"},
 22		{"multiple hyphens", "my-special-area"},
 23	}
 24
 25	for _, tc := range tests {
 26		t.Run(tc.name, func(t *testing.T) {
 27			t.Parallel()
 28
 29			if err := validateKeyFormat(tc.input); err != nil {
 30				t.Errorf("validateKeyFormat(%q) = %v, want nil", tc.input, err)
 31			}
 32		})
 33	}
 34}
 35
 36func TestValidateKeyFormat_Invalid(t *testing.T) {
 37	t.Parallel()
 38
 39	tests := []struct {
 40		name    string
 41		input   string
 42		wantErr error
 43	}{
 44		{"empty key", "", errKeyRequired},
 45		{"uppercase letters", "Work", errKeyFormat},
 46		{"spaces", "my area", errKeyFormat},
 47		{"underscores", "my_area", errKeyFormat},
 48		{"leading hyphen", "-work", errKeyFormat},
 49		{"trailing hyphen", "work-", errKeyFormat},
 50		{"double hyphen", "my--area", errKeyFormat},
 51		{"special characters", "work@home", errKeyFormat},
 52	}
 53
 54	for _, tc := range tests {
 55		t.Run(tc.name, func(t *testing.T) {
 56			t.Parallel()
 57
 58			if err := validateKeyFormat(tc.input); !errors.Is(err, tc.wantErr) {
 59				t.Errorf("validateKeyFormat(%q) = %v, want %v", tc.input, err, tc.wantErr)
 60			}
 61		})
 62	}
 63}
 64
 65func TestValidateReference_Valid(t *testing.T) {
 66	t.Parallel()
 67
 68	const (
 69		uuidLower = "123e4567-e89b-12d3-a456-426614174000"
 70		uuidArea  = "3bbf1923-64ae-4bcf-96a9-9bb86c799dab"
 71		uuidGoal  = "9d79e922-9ca8-4b8c-9aa5-dd98bb2492b2"
 72		uuidNote  = "e230ef3e-d9a5-4211-9dfb-515cc892d6e5"
 73	)
 74
 75	tests := []struct {
 76		name             string
 77		input            string
 78		supportsDeepLink bool
 79		wantID           string
 80	}{
 81		{"valid UUID lowercase", uuidLower, false, uuidLower},
 82		{"valid UUID uppercase", "123E4567-E89B-12D3-A456-426614174000", false, "123E4567-E89B-12D3-A456-426614174000"},
 83		{"deep link area", "lunatask://areas/" + uuidArea, true, uuidArea},
 84		{"deep link goal", "lunatask://goals/" + uuidGoal, true, uuidGoal},
 85		{"deep link note", "lunatask://notes/" + uuidNote, true, uuidNote},
 86	}
 87
 88	for _, testCase := range tests {
 89		t.Run(testCase.name, func(t *testing.T) {
 90			t.Parallel()
 91
 92			id, err := validateReference(testCase.input, testCase.supportsDeepLink)
 93			if err != nil {
 94				t.Errorf("validateReference(%q, %v) = %v, want nil",
 95					testCase.input, testCase.supportsDeepLink, err)
 96			}
 97
 98			if id != testCase.wantID {
 99				t.Errorf("validateReference(%q, %v) = %q, want %q",
100					testCase.input, testCase.supportsDeepLink, id, testCase.wantID)
101			}
102		})
103	}
104}
105
106func TestValidateReference_Invalid(t *testing.T) {
107	t.Parallel()
108
109	const validUUID = "123e4567-e89b-12d3-a456-426614174000"
110
111	tests := []struct {
112		name             string
113		input            string
114		supportsDeepLink bool
115		wantErr          error
116	}{
117		{"empty reference", "", true, errRefRequired},
118		{"too short", "123e4567-e89b", true, errRefFormat},
119		{"random string", "not-a-uuid", true, errRefFormat},
120		{"wrong characters", "123e4567-e89b-12d3-a456-42661417zzzz", false, errRefFormat},
121		{"invalid scheme", "http://areas/" + validUUID, true, errRefFormat},
122		{"invalid resource", "lunatask://invalid/" + validUUID, true, errRefFormat},
123	}
124
125	for _, testCase := range tests {
126		t.Run(testCase.name, func(t *testing.T) {
127			t.Parallel()
128
129			_, err := validateReference(testCase.input, testCase.supportsDeepLink)
130			if !errors.Is(err, testCase.wantErr) {
131				t.Errorf("validateReference(%q, %v) = %v, want %v",
132					testCase.input, testCase.supportsDeepLink, err, testCase.wantErr)
133			}
134		})
135	}
136}
137
138func TestParseEditIndex_Valid(t *testing.T) {
139	t.Parallel()
140
141	tests := []struct {
142		name    string
143		input   string
144		wantIdx int
145	}{
146		{"index 0", "edit:0", 0},
147		{"index 5", "edit:5", 5},
148		{"large index", "edit:999", 999},
149		{"negative index", "edit:-1", -1},
150	}
151
152	for _, testCase := range tests {
153		t.Run(testCase.name, func(t *testing.T) {
154			t.Parallel()
155
156			idx, valid := parseEditIndex(testCase.input)
157			if !valid {
158				t.Errorf("parseEditIndex(%q) valid = false, want true", testCase.input)
159			}
160
161			if idx != testCase.wantIdx {
162				t.Errorf("parseEditIndex(%q) idx = %d, want %d", testCase.input, idx, testCase.wantIdx)
163			}
164		})
165	}
166}
167
168func TestParseEditIndex_Invalid(t *testing.T) {
169	t.Parallel()
170
171	tests := []struct {
172		name  string
173		input string
174	}{
175		{"back choice", "back"},
176		{"add choice", "add"},
177		{"done choice", "done"},
178		{"empty prefix", "edit:"},
179		{"non-numeric", "edit:abc"},
180		{"empty string", ""},
181	}
182
183	for _, testCase := range tests {
184		t.Run(testCase.name, func(t *testing.T) {
185			t.Parallel()
186
187			_, valid := parseEditIndex(testCase.input)
188			if valid {
189				t.Errorf("parseEditIndex(%q) valid = true, want false", testCase.input)
190			}
191		})
192	}
193}
194
195func TestTitleCase(t *testing.T) {
196	t.Parallel()
197
198	tests := []struct {
199		input string
200		want  string
201	}{
202		{"area", "Area"},
203		{"goal", "Goal"},
204		{"notebook", "Notebook"},
205		{"habit", "Habit"},
206		{"a", "A"},
207		{"", ""},
208		{"already Capitalized", "Already Capitalized"},
209	}
210
211	for _, tc := range tests {
212		t.Run(tc.input, func(t *testing.T) {
213			t.Parallel()
214
215			if got := titleCase(tc.input); got != tc.want {
216				t.Errorf("titleCase(%q) = %q, want %q", tc.input, got, tc.want)
217			}
218		})
219	}
220}