Calendars can now have a color (hex, e.g. #3b82f6) that DAVx5 and other
CalDAV clients pick up via the Apple/dav4jvm calendar-color property.
- db: add calendars.color column with migration for existing DBs;
CreateCalendarWithColor, SetCalendarColor, GetCalendarColor;
ListCalendars now returns []Calendar{Name, Color} instead of []string
- caldav: since go-webdav's caldav.Backend interface has no extension
point for vendor properties, wrap the handler with a response-rewriting
middleware that injects <calendar-color xmlns="http://apple.com/ns/ical/">
into PROPFIND responses for calendars that have a color set
- web: color picker on the "New calendar" form and an inline color swatch/
picker on each calendar card (calendars only, not address books)
- nidusctl: `calendar create --color` flag and a new `calendar color`
subcommand; `calendar list` now also prints the color if set
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
104 lines
3.1 KiB
Go
104 lines
3.1 KiB
Go
package web
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestCreateAndDeleteCalendarViaWebUI(t *testing.T) {
|
|
s := newTestServer(t)
|
|
handler := s.Handler(emptyStaticFS{})
|
|
cookie := loginAs(t, handler, "alice", "password")
|
|
|
|
// Create a new calendar.
|
|
form := url.Values{"name": {"vacation"}}
|
|
req := httptest.NewRequest(http.MethodPost, "/resources/calendar", strings.NewReader(form.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
req.AddCookie(cookie)
|
|
rr := httptest.NewRecorder()
|
|
handler.ServeHTTP(rr, req)
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("create: expected 200, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
if !strings.Contains(rr.Body.String(), "vacation") {
|
|
t.Fatalf("expected resource list to include new calendar, got: %s", rr.Body.String())
|
|
}
|
|
|
|
names, err := s.dbase.ListCalendars("alice")
|
|
if err != nil {
|
|
t.Fatalf("ListCalendars: %v", err)
|
|
}
|
|
found := false
|
|
for _, c := range names {
|
|
if c.Name == "vacation" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("expected vacation calendar to be registered, got %v", names)
|
|
}
|
|
|
|
// Duplicate creation should fail with 409.
|
|
req = httptest.NewRequest(http.MethodPost, "/resources/calendar", strings.NewReader(form.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
req.AddCookie(cookie)
|
|
rr = httptest.NewRecorder()
|
|
handler.ServeHTTP(rr, req)
|
|
if rr.Code != http.StatusConflict {
|
|
t.Fatalf("expected 409 on duplicate create, got %d", rr.Code)
|
|
}
|
|
|
|
// Delete it — htmx v2 sends DELETE params as a URL query string.
|
|
req = httptest.NewRequest(http.MethodDelete, "/resources/calendar?name=vacation", nil)
|
|
req.AddCookie(cookie)
|
|
rr = httptest.NewRecorder()
|
|
handler.ServeHTTP(rr, req)
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("delete: expected 200, got %d: %s", rr.Code, rr.Body.String())
|
|
}
|
|
if strings.Contains(rr.Body.String(), "vacation") {
|
|
t.Fatalf("expected resource list to no longer include deleted calendar, got: %s", rr.Body.String())
|
|
}
|
|
|
|
names, err = s.dbase.ListCalendars("alice")
|
|
if err != nil {
|
|
t.Fatalf("ListCalendars: %v", err)
|
|
}
|
|
for _, c := range names {
|
|
if c.Name == "vacation" {
|
|
t.Fatalf("expected vacation calendar to be gone, got %v", names)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCreateAddressBookInvalidName(t *testing.T) {
|
|
s := newTestServer(t)
|
|
handler := s.Handler(emptyStaticFS{})
|
|
cookie := loginAs(t, handler, "alice", "password")
|
|
|
|
form := url.Values{"name": {"has a space"}}
|
|
req := httptest.NewRequest(http.MethodPost, "/resources/addressbook", strings.NewReader(form.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
req.AddCookie(cookie)
|
|
rr := httptest.NewRecorder()
|
|
handler.ServeHTTP(rr, req)
|
|
if rr.Code != http.StatusBadRequest {
|
|
t.Fatalf("expected 400 for invalid name, got %d", rr.Code)
|
|
}
|
|
}
|
|
|
|
func TestDeleteCalendarRequiresLogin(t *testing.T) {
|
|
s := newTestServer(t)
|
|
handler := s.Handler(emptyStaticFS{})
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/resources/calendar?name=work", nil)
|
|
rr := httptest.NewRecorder()
|
|
handler.ServeHTTP(rr, req)
|
|
if rr.Code != http.StatusSeeOther {
|
|
t.Fatalf("expected redirect to login, got %d", rr.Code)
|
|
}
|
|
}
|