@@ -0,0 +1,341 @@
+// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+package lunatask_test
+
+import (
+ "testing"
+
+ lunatask "git.secluded.site/go-lunatask"
+)
+
+// --- Eisenhower Type Constants ---
+
+func TestEisenhower_Constants(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ value lunatask.Eisenhower
+ want int
+ }{
+ {"uncategorized", lunatask.EisenhowerUncategorized, 0},
+ {"do_now", lunatask.EisenhowerDoNow, 1},
+ {"delegate", lunatask.EisenhowerDelegate, 2},
+ {"do_later", lunatask.EisenhowerDoLater, 3},
+ {"eliminate", lunatask.EisenhowerEliminate, 4},
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ if int(tc.value) != tc.want {
+ t.Errorf("Eisenhower constant = %d, want %d", tc.value, tc.want)
+ }
+ })
+ }
+}
+
+// --- Eisenhower Helper Methods ---
+
+func TestEisenhower_IsUrgent(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ value lunatask.Eisenhower
+ want bool
+ }{
+ {"uncategorized", lunatask.EisenhowerUncategorized, false},
+ {"do_now", lunatask.EisenhowerDoNow, true}, // urgent + important
+ {"delegate", lunatask.EisenhowerDelegate, true}, // urgent only
+ {"do_later", lunatask.EisenhowerDoLater, false}, // important only
+ {"eliminate", lunatask.EisenhowerEliminate, false}, // neither
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ if got := tc.value.IsUrgent(); got != tc.want {
+ t.Errorf("Eisenhower(%d).IsUrgent() = %v, want %v", tc.value, got, tc.want)
+ }
+ })
+ }
+}
+
+func TestEisenhower_IsImportant(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ value lunatask.Eisenhower
+ want bool
+ }{
+ {"uncategorized", lunatask.EisenhowerUncategorized, false},
+ {"do_now", lunatask.EisenhowerDoNow, true}, // urgent + important
+ {"delegate", lunatask.EisenhowerDelegate, false}, // urgent only
+ {"do_later", lunatask.EisenhowerDoLater, true}, // important only
+ {"eliminate", lunatask.EisenhowerEliminate, false}, // neither
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ if got := tc.value.IsImportant(); got != tc.want {
+ t.Errorf("Eisenhower(%d).IsImportant() = %v, want %v", tc.value, got, tc.want)
+ }
+ })
+ }
+}
+
+// --- Eisenhower Helper Function ---
+
+func TestNewEisenhower(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ important bool
+ urgent bool
+ want lunatask.Eisenhower
+ }{
+ {"both", true, true, lunatask.EisenhowerDoNow},
+ {"urgent_only", false, true, lunatask.EisenhowerDelegate},
+ {"important_only", true, false, lunatask.EisenhowerDoLater},
+ {"neither", false, false, lunatask.EisenhowerEliminate},
+ }
+
+ for _, testCase := range tests {
+ t.Run(testCase.name, func(t *testing.T) {
+ t.Parallel()
+
+ got := lunatask.NewEisenhower(testCase.important, testCase.urgent)
+ if got != testCase.want {
+ t.Errorf("NewEisenhower(%v, %v) = %d, want %d", testCase.important, testCase.urgent, got, testCase.want)
+ }
+ })
+ }
+}
+
+// --- TaskBuilder Semantic Methods ---
+
+func TestTaskBuilder_Important(t *testing.T) {
+ t.Parallel()
+
+ server, capture := newPOSTServer(t, "/tasks", singleTaskResponseBody)
+ defer server.Close()
+
+ client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
+
+ _, err := client.NewTask("Important task").
+ Important().
+ Create(ctx())
+ if err != nil {
+ t.Fatalf("error = %v", err)
+ }
+
+ // Important only = 3 (DoLater)
+ assertBodyFieldFloat(t, capture.Body, "eisenhower", 3)
+}
+
+func TestTaskBuilder_Urgent(t *testing.T) {
+ t.Parallel()
+
+ server, capture := newPOSTServer(t, "/tasks", singleTaskResponseBody)
+ defer server.Close()
+
+ client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
+
+ _, err := client.NewTask("Urgent task").
+ Urgent().
+ Create(ctx())
+ if err != nil {
+ t.Fatalf("error = %v", err)
+ }
+
+ // Urgent only = 2 (Delegate)
+ assertBodyFieldFloat(t, capture.Body, "eisenhower", 2)
+}
+
+func TestTaskBuilder_ImportantAndUrgent(t *testing.T) {
+ t.Parallel()
+
+ server, capture := newPOSTServer(t, "/tasks", singleTaskResponseBody)
+ defer server.Close()
+
+ client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
+
+ _, err := client.NewTask("Do now task").
+ Important().
+ Urgent().
+ Create(ctx())
+ if err != nil {
+ t.Fatalf("error = %v", err)
+ }
+
+ // Both = 1 (DoNow)
+ assertBodyFieldFloat(t, capture.Body, "eisenhower", 1)
+}
+
+func TestTaskBuilder_UrgentAndImportant_ReverseOrder(t *testing.T) {
+ t.Parallel()
+
+ server, capture := newPOSTServer(t, "/tasks", singleTaskResponseBody)
+ defer server.Close()
+
+ client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
+
+ _, err := client.NewTask("Do now task").
+ Urgent().
+ Important().
+ Create(ctx())
+ if err != nil {
+ t.Fatalf("error = %v", err)
+ }
+
+ // Both = 1 (DoNow), order shouldn't matter
+ assertBodyFieldFloat(t, capture.Body, "eisenhower", 1)
+}
+
+func TestTaskBuilder_NeitherImportantNorUrgent(t *testing.T) {
+ t.Parallel()
+
+ server, capture := newPOSTServer(t, "/tasks", singleTaskResponseBody)
+ defer server.Close()
+
+ client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
+
+ // Explicitly set neither - should result in Eliminate (4)
+ _, err := client.NewTask("Eliminate task").
+ NotImportant().
+ NotUrgent().
+ Create(ctx())
+ if err != nil {
+ t.Fatalf("error = %v", err)
+ }
+
+ // Neither = 4 (Eliminate)
+ assertBodyFieldFloat(t, capture.Body, "eisenhower", 4)
+}
+
+// --- TaskUpdateBuilder Semantic Methods ---
+
+func TestTaskUpdateBuilder_Important(t *testing.T) {
+ t.Parallel()
+
+ server, capture := newPUTServer(t, "/tasks/"+taskID, singleTaskResponseBody)
+ defer server.Close()
+
+ client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
+
+ _, err := client.NewTaskUpdate(taskID).
+ Important().
+ Update(ctx())
+ if err != nil {
+ t.Fatalf("error = %v", err)
+ }
+
+ // Important only = 3 (DoLater)
+ assertBodyFieldFloat(t, capture.Body, "eisenhower", 3)
+}
+
+func TestTaskUpdateBuilder_Urgent(t *testing.T) {
+ t.Parallel()
+
+ server, capture := newPUTServer(t, "/tasks/"+taskID, singleTaskResponseBody)
+ defer server.Close()
+
+ client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
+
+ _, err := client.NewTaskUpdate(taskID).
+ Urgent().
+ Update(ctx())
+ if err != nil {
+ t.Fatalf("error = %v", err)
+ }
+
+ // Urgent only = 2 (Delegate)
+ assertBodyFieldFloat(t, capture.Body, "eisenhower", 2)
+}
+
+func TestTaskUpdateBuilder_ImportantAndUrgent(t *testing.T) {
+ t.Parallel()
+
+ server, capture := newPUTServer(t, "/tasks/"+taskID, singleTaskResponseBody)
+ defer server.Close()
+
+ client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
+
+ _, err := client.NewTaskUpdate(taskID).
+ Important().
+ Urgent().
+ Update(ctx())
+ if err != nil {
+ t.Fatalf("error = %v", err)
+ }
+
+ // Both = 1 (DoNow)
+ assertBodyFieldFloat(t, capture.Body, "eisenhower", 1)
+}
+
+func TestTaskUpdateBuilder_NeitherImportantNorUrgent(t *testing.T) {
+ t.Parallel()
+
+ server, capture := newPUTServer(t, "/tasks/"+taskID, singleTaskResponseBody)
+ defer server.Close()
+
+ client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
+
+ _, err := client.NewTaskUpdate(taskID).
+ NotImportant().
+ NotUrgent().
+ Update(ctx())
+ if err != nil {
+ t.Fatalf("error = %v", err)
+ }
+
+ // Neither = 4 (Eliminate)
+ assertBodyFieldFloat(t, capture.Body, "eisenhower", 4)
+}
+
+// --- WithEisenhower still works with typed constant ---
+
+func TestTaskBuilder_WithEisenhowerTyped(t *testing.T) {
+ t.Parallel()
+
+ server, capture := newPOSTServer(t, "/tasks", singleTaskResponseBody)
+ defer server.Close()
+
+ client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
+
+ _, err := client.NewTask("Typed eisenhower").
+ WithEisenhower(lunatask.EisenhowerDoNow).
+ Create(ctx())
+ if err != nil {
+ t.Fatalf("error = %v", err)
+ }
+
+ assertBodyFieldFloat(t, capture.Body, "eisenhower", 1)
+}
+
+func TestTaskUpdateBuilder_WithEisenhowerTyped(t *testing.T) {
+ t.Parallel()
+
+ server, capture := newPUTServer(t, "/tasks/"+taskID, singleTaskResponseBody)
+ defer server.Close()
+
+ client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
+
+ _, err := client.NewTaskUpdate(taskID).
+ WithEisenhower(lunatask.EisenhowerDelegate).
+ Update(ctx())
+ if err != nil {
+ t.Fatalf("error = %v", err)
+ }
+
+ assertBodyFieldFloat(t, capture.Body, "eisenhower", 2)
+}
@@ -23,7 +23,7 @@ type Task struct {
Priority *int `json:"priority"`
Progress *int `json:"progress"`
Motivation *Motivation `json:"motivation"`
- Eisenhower *int `json:"eisenhower"`
+ Eisenhower *Eisenhower `json:"eisenhower"`
Sources []Source `json:"sources"`
ScheduledOn *Date `json:"scheduled_on"`
CompletedAt *time.Time `json:"completed_at"`
@@ -41,7 +41,7 @@ type createTaskRequest struct {
Motivation *Motivation `json:"motivation,omitempty"`
Estimate *int `json:"estimate,omitempty"`
Priority *int `json:"priority,omitempty"`
- Eisenhower *int `json:"eisenhower,omitempty"`
+ Eisenhower *Eisenhower `json:"eisenhower,omitempty"`
ScheduledOn *Date `json:"scheduled_on,omitempty"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
Source *string `json:"source,omitempty"`
@@ -58,7 +58,7 @@ type updateTaskRequest struct {
Motivation *Motivation `json:"motivation,omitempty"`
Estimate *int `json:"estimate,omitempty"`
Priority *int `json:"priority,omitempty"`
- Eisenhower *int `json:"eisenhower,omitempty"`
+ Eisenhower *Eisenhower `json:"eisenhower,omitempty"`
ScheduledOn *Date `json:"scheduled_on,omitempty"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
}
@@ -113,8 +113,10 @@ func (c *Client) DeleteTask(ctx context.Context, taskID string) (*Task, error) {
// WithEstimate(30).
// Create(ctx)
type TaskBuilder struct {
- client *Client
- req createTaskRequest
+ client *Client
+ req createTaskRequest
+ important *bool
+ urgent *bool
}
// NewTask starts building a task with the given name.
@@ -173,14 +175,52 @@ func (b *TaskBuilder) WithPriority(priority int) *TaskBuilder {
return b
}
-// WithEisenhower sets the Eisenhower matrix quadrant:
-// 0=uncategorized, 1=urgent+important, 2=urgent, 3=important, 4=neither.
-func (b *TaskBuilder) WithEisenhower(eisenhower int) *TaskBuilder {
+// WithEisenhower sets the Eisenhower matrix quadrant directly.
+// Prefer [TaskBuilder.Important] and [TaskBuilder.Urgent] for a more readable API.
+func (b *TaskBuilder) WithEisenhower(eisenhower Eisenhower) *TaskBuilder {
b.req.Eisenhower = &eisenhower
return b
}
+// Important marks the task as important. Produces [EisenhowerDoLater] alone,
+// or [EisenhowerDoNow] when combined with [TaskBuilder.Urgent].
+func (b *TaskBuilder) Important() *TaskBuilder {
+ t := true
+ b.important = &t
+
+ return b
+}
+
+// NotImportant marks the task as not important. Use with [TaskBuilder.NotUrgent]
+// to explicitly set [EisenhowerEliminate], or with [TaskBuilder.Urgent] for [EisenhowerDelegate].
+// Calling neither Important nor NotImportant leaves Eisenhower unset.
+func (b *TaskBuilder) NotImportant() *TaskBuilder {
+ f := false
+ b.important = &f
+
+ return b
+}
+
+// Urgent marks the task as urgent. Produces [EisenhowerDelegate] alone,
+// or [EisenhowerDoNow] when combined with [TaskBuilder.Important].
+func (b *TaskBuilder) Urgent() *TaskBuilder {
+ t := true
+ b.urgent = &t
+
+ return b
+}
+
+// NotUrgent marks the task as not urgent. Use with [TaskBuilder.NotImportant]
+// to explicitly set [EisenhowerEliminate], or with [TaskBuilder.Important] for [EisenhowerDoLater].
+// Calling neither Urgent nor NotUrgent leaves Eisenhower unset.
+func (b *TaskBuilder) NotUrgent() *TaskBuilder {
+ f := false
+ b.urgent = &f
+
+ return b
+}
+
// ScheduledOn sets when the task should appear on your schedule.
func (b *TaskBuilder) ScheduledOn(date Date) *TaskBuilder {
b.req.ScheduledOn = &date
@@ -207,6 +247,13 @@ func (b *TaskBuilder) FromSource(source, sourceID string) *TaskBuilder {
// Create sends the task to Lunatask. Returns (nil, nil) if a not-completed
// task already exists in the same area with matching source/source_id.
func (b *TaskBuilder) Create(ctx context.Context) (*Task, error) {
+ if b.important != nil || b.urgent != nil {
+ important := b.important != nil && *b.important
+ urgent := b.urgent != nil && *b.urgent
+ e := NewEisenhower(important, urgent)
+ b.req.Eisenhower = &e
+ }
+
return create(ctx, b.client, "/tasks", b.req, func(r taskResponse) Task { return r.Task })
}
@@ -218,9 +265,11 @@ func (b *TaskBuilder) Create(ctx context.Context) (*Task, error) {
// CompletedAt(time.Now()).
// Update(ctx)
type TaskUpdateBuilder struct {
- client *Client
- taskID string
- req updateTaskRequest
+ client *Client
+ taskID string
+ req updateTaskRequest
+ important *bool
+ urgent *bool
}
// NewTaskUpdate starts building a task update for the given task ID.
@@ -286,14 +335,52 @@ func (b *TaskUpdateBuilder) WithPriority(priority int) *TaskUpdateBuilder {
return b
}
-// WithEisenhower sets the Eisenhower matrix quadrant:
-// 0=uncategorized, 1=urgent+important, 2=urgent, 3=important, 4=neither.
-func (b *TaskUpdateBuilder) WithEisenhower(eisenhower int) *TaskUpdateBuilder {
+// WithEisenhower sets the Eisenhower matrix quadrant directly.
+// Prefer [TaskUpdateBuilder.Important] and [TaskUpdateBuilder.Urgent] for a more readable API.
+func (b *TaskUpdateBuilder) WithEisenhower(eisenhower Eisenhower) *TaskUpdateBuilder {
b.req.Eisenhower = &eisenhower
return b
}
+// Important marks the task as important. Produces [EisenhowerDoLater] alone,
+// or [EisenhowerDoNow] when combined with [TaskUpdateBuilder.Urgent].
+func (b *TaskUpdateBuilder) Important() *TaskUpdateBuilder {
+ t := true
+ b.important = &t
+
+ return b
+}
+
+// NotImportant marks the task as not important. Use with [TaskUpdateBuilder.NotUrgent]
+// to explicitly set [EisenhowerEliminate], or with [TaskUpdateBuilder.Urgent] for [EisenhowerDelegate].
+// Calling neither Important nor NotImportant leaves Eisenhower unset.
+func (b *TaskUpdateBuilder) NotImportant() *TaskUpdateBuilder {
+ f := false
+ b.important = &f
+
+ return b
+}
+
+// Urgent marks the task as urgent. Produces [EisenhowerDelegate] alone,
+// or [EisenhowerDoNow] when combined with [TaskUpdateBuilder.Important].
+func (b *TaskUpdateBuilder) Urgent() *TaskUpdateBuilder {
+ t := true
+ b.urgent = &t
+
+ return b
+}
+
+// NotUrgent marks the task as not urgent. Use with [TaskUpdateBuilder.NotImportant]
+// to explicitly set [EisenhowerEliminate], or with [TaskUpdateBuilder.Important] for [EisenhowerDoLater].
+// Calling neither Urgent nor NotUrgent leaves Eisenhower unset.
+func (b *TaskUpdateBuilder) NotUrgent() *TaskUpdateBuilder {
+ f := false
+ b.urgent = &f
+
+ return b
+}
+
// ScheduledOn sets when the task should appear on your schedule.
func (b *TaskUpdateBuilder) ScheduledOn(date Date) *TaskUpdateBuilder {
b.req.ScheduledOn = &date
@@ -310,5 +397,12 @@ func (b *TaskUpdateBuilder) CompletedAt(t time.Time) *TaskUpdateBuilder {
// Update sends the changes to Lunatask.
func (b *TaskUpdateBuilder) Update(ctx context.Context) (*Task, error) {
+ if b.important != nil || b.urgent != nil {
+ important := b.important != nil && *b.important
+ urgent := b.urgent != nil && *b.urgent
+ e := NewEisenhower(important, urgent)
+ b.req.Eisenhower = &e
+ }
+
return update(ctx, b.client, "/tasks", b.taskID, "task", b.req, func(r taskResponse) Task { return r.Task })
}