1
0

feat: Add relative Reminders (#1427)

Partially resolves #1416

Co-authored-by: ce72 <christoph.ernst72@googlemail.com>
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1427
Reviewed-by: konrad <k@knt.li>
Co-authored-by: cernst <ce72@noreply.kolaente.de>
Co-committed-by: cernst <ce72@noreply.kolaente.de>
This commit is contained in:
cernst
2023-03-27 20:07:06 +00:00
committed by konrad
parent 823c817b1f
commit 3f5252dc24
21 changed files with 724 additions and 207 deletions

View File

@ -875,6 +875,33 @@ func (err ErrUserAlreadyAssigned) HTTPError() web.HTTPError {
}
}
// ErrReminderRelativeToMissing represents an error where a task has a relative reminder without reference date
type ErrReminderRelativeToMissing struct {
TaskID int64
}
// IsErrReminderRelativeToMissing checks if an error is ErrReminderRelativeToMissing.
func IsErrReminderRelativeToMissing(err error) bool {
_, ok := err.(ErrReminderRelativeToMissing)
return ok
}
func (err ErrReminderRelativeToMissing) Error() string {
return fmt.Sprintf("Task [TaskID: %v] has a relative reminder without relative_to", err.TaskID)
}
// ErrCodeRelationDoesNotExist holds the unique world-error code of this error
const ErrCodeReminderRelativeToMissing = 4022
// HTTPError holds the http error description
func (err ErrReminderRelativeToMissing) HTTPError() web.HTTPError {
return web.HTTPError{
HTTPCode: http.StatusBadRequest,
Code: ErrCodeReminderRelativeToMissing,
Message: "Please provide what the reminder date is relative to",
}
}
// =================
// Namespace errors
// =================

View File

@ -266,6 +266,13 @@ func getNativeValueForTaskField(fieldName string, comparator taskFilterComparato
return nil, nil, ErrInvalidTaskField{TaskField: fieldName}
}
if realFieldName == "Reminders" {
field, ok = reflect.TypeOf(&TaskReminder{}).Elem().FieldByName("Reminder")
if !ok {
return nil, nil, ErrInvalidTaskField{TaskField: fieldName}
}
}
if comparator == taskFilterComparatorIn {
vals := strings.Split(value, ",")
valueSlice := []interface{}{}

View File

@ -169,9 +169,17 @@ func TestTaskCollection_ReadAll(t *testing.T) {
label4,
},
RelatedTasks: map[RelationKind][]*Task{},
Reminders: []time.Time{
ReminderDates: []time.Time{
time.Unix(1543626824, 0).In(loc),
},
Reminders: []*TaskReminder{
{
ID: 3,
TaskID: 2,
Reminder: time.Unix(1543626824, 0).In(loc),
Created: time.Unix(1543626724, 0).In(loc),
},
},
Created: time.Unix(1543626724, 0).In(loc),
Updated: time.Unix(1543626724, 0).In(loc),
}
@ -472,15 +480,32 @@ func TestTaskCollection_ReadAll(t *testing.T) {
}
task27 := &Task{
ID: 27,
Title: "task #27 with reminders",
Title: "task #27 with reminders and start_date",
Identifier: "test1-12",
Index: 12,
CreatedByID: 1,
CreatedBy: user1,
Reminders: []time.Time{
ReminderDates: []time.Time{
time.Unix(1543626724, 0).In(loc),
time.Unix(1543626824, 0).In(loc),
},
Reminders: []*TaskReminder{
{
ID: 1,
TaskID: 27,
Reminder: time.Unix(1543626724, 0).In(loc),
Created: time.Unix(1543626724, 0).In(loc),
},
{
ID: 2,
TaskID: 27,
Reminder: time.Unix(1543626824, 0).In(loc),
Created: time.Unix(1543626724, 0).In(loc),
RelativePeriod: -3600,
RelativeTo: "start_date",
},
},
StartDate: time.Unix(1543616724, 0).In(loc),
ProjectID: 1,
BucketID: 1,
RelatedTasks: map[RelationKind][]*Task{},
@ -906,7 +931,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
wantErr: false,
},
{
name: "filtered reminders",
name: "filtered reminder dates",
fields: fields{
FilterBy: []string{"reminders", "reminders"},
FilterValue: []string{"2018-10-01T00:00:00+00:00", "2018-12-10T00:00:00+00:00"},
@ -1246,7 +1271,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
return
}
t.Errorf("Test %s, Task.ReadAll() = %v, want %v, \ndiff: %v", tt.name, got, tt.want, diff)
t.Errorf("Test %s, Task.ReadAll() = %v, \nwant %v, \ndiff: %v", tt.name, got, tt.want, diff)
}
})
}

View File

@ -33,12 +33,29 @@ import (
"code.vikunja.io/api/pkg/user"
)
// TaskReminder holds a reminder on a task
// ReminderRelation represents the date attribute of the task which a period based reminder relates to
type ReminderRelation string
// All valid ReminderRelations
const (
ReminderRelationDueDate ReminderRelation = `due_date`
ReminderRelationStartDate ReminderRelation = `start_date`
ReminderRelationEndDate ReminderRelation = `end_date`
)
// TaskReminder holds a reminder on a task.
// If RelativeTo and the assciated date field are defined, then the attribute Reminder will be computed.
// If RelativeTo is missing, than Reminder must be given.
type TaskReminder struct {
ID int64 `xorm:"bigint autoincr not null unique pk"`
TaskID int64 `xorm:"bigint not null INDEX"`
Reminder time.Time `xorm:"DATETIME not null INDEX 'reminder'"`
Created time.Time `xorm:"created not null"`
ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"`
TaskID int64 `xorm:"bigint not null INDEX" json:"-"`
// The absolute time when the user wants to be reminded of the task.
Reminder time.Time `xorm:"DATETIME not null INDEX 'reminder'" json:"reminder"`
Created time.Time `xorm:"created not null" json:"-"`
// A period in seconds relative to another date argument. Negative values mean the reminder triggers before the date. Default: 0, tiggers when RelativeTo is due.
RelativePeriod int64 `xorm:"bigint null" json:"relative_period"`
// The name of the date field to which the relative period refers to.
RelativeTo ReminderRelation `xorm:"varchar(50) null" json:"relative_to"`
}
// TableName returns a pretty table name

View File

@ -62,7 +62,11 @@ type Task struct {
// The time when the task is due.
DueDate time.Time `xorm:"DATETIME INDEX null 'due_date'" json:"due_date"`
// An array of datetimes when the user wants to be reminded of the task.
Reminders []time.Time `xorm:"-" json:"reminder_dates"`
//
// Deprecated: Use Reminders
ReminderDates []time.Time `xorm:"-" json:"reminder_dates"`
// An array of reminders that are associated with this task.
Reminders []*TaskReminder `xorm:"-" json:"reminders"`
// The project this task belongs to.
ProjectID int64 `xorm:"bigint INDEX not null" json:"project_id" param:"project"`
// An amount in seconds this task repeats itself. If this is set, when marking the task as done, it will mark itself as "undone" and then increase all remindes and the due date by its amount.
@ -641,8 +645,8 @@ func addAttachmentsToTasks(s *xorm.Session, taskIDs []int64, taskMap map[int64]*
return
}
func getTaskReminderMap(s *xorm.Session, taskIDs []int64) (taskReminders map[int64][]time.Time, err error) {
taskReminders = make(map[int64][]time.Time)
func getTaskReminderMap(s *xorm.Session, taskIDs []int64) (taskReminders map[int64][]*TaskReminder, err error) {
taskReminders = make(map[int64][]*TaskReminder)
// Get all reminders and put them in a map to have it easier later
reminders, err := getRemindersForTasks(s, taskIDs)
@ -651,7 +655,7 @@ func getTaskReminderMap(s *xorm.Session, taskIDs []int64) (taskReminders map[int
}
for _, r := range reminders {
taskReminders[r.TaskID] = append(taskReminders[r.TaskID], r.Reminder)
taskReminders[r.TaskID] = append(taskReminders[r.TaskID], r)
}
return
@ -772,6 +776,11 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth) (e
// Make created by user objects
task.CreatedBy = users[task.CreatedByID]
// Add the reminder dates (Remove, when ReminderDates is removed)
for _, r := range taskReminders[task.ID] {
task.ReminderDates = append(task.ReminderDates, r.Reminder)
}
// Add the reminders
task.Reminders = taskReminders[task.ID]
@ -965,7 +974,7 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err
}
// Update the reminders
if err := t.updateReminders(s, t.Reminders); err != nil {
if err := t.updateReminders(s, t); err != nil {
return err
}
@ -1017,15 +1026,19 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
t.ProjectID = ot.ProjectID
}
// Get the reminders
// Get the stored reminders
reminders, err := getRemindersForTasks(s, []int64{t.ID})
if err != nil {
return
}
ot.Reminders = make([]time.Time, len(reminders))
// Old task has the stored reminders
ot.Reminders = reminders
// Deprecated: remove when ReminderDates is removed
ot.ReminderDates = make([]time.Time, len(reminders))
for i, r := range reminders {
ot.Reminders[i] = r.Reminder
ot.ReminderDates[i] = r.Reminder
}
targetBucket, err := setTaskBucket(s, t, &ot, t.BucketID != 0 && t.BucketID != ot.BucketID)
@ -1049,7 +1062,7 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
}
// Update the reminders
if err := ot.updateReminders(s, t.Reminders); err != nil {
if err := ot.updateReminders(s, t); err != nil {
return err
}
@ -1331,9 +1344,9 @@ func setTaskDatesDefault(oldTask, newTask *Task) {
// To make this easier, we sort them first because we can then rely on the fact the first is the smallest
if len(oldTask.Reminders) > 0 {
for in, r := range oldTask.Reminders {
newTask.Reminders[in] = r.Add(repeatDuration)
for !newTask.Reminders[in].After(now) {
newTask.Reminders[in] = newTask.Reminders[in].Add(repeatDuration)
newTask.Reminders[in].Reminder = r.Reminder.Add(repeatDuration)
for !newTask.Reminders[in].Reminder.After(now) {
newTask.Reminders[in].Reminder = newTask.Reminders[in].Reminder.Add(repeatDuration)
}
}
}
@ -1364,7 +1377,7 @@ func setTaskDatesMonthRepeat(oldTask, newTask *Task) {
newTask.Reminders = oldTask.Reminders
if len(oldTask.Reminders) > 0 {
for in, r := range oldTask.Reminders {
newTask.Reminders[in] = addOneMonthToDate(r)
newTask.Reminders[in].Reminder = addOneMonthToDate(r.Reminder)
}
}
@ -1405,12 +1418,12 @@ func setTaskDatesFromCurrentDateRepeat(oldTask, newTask *Task) {
// To make this easier, we sort them first because we can then rely on the fact the first is the smallest
if len(oldTask.Reminders) > 0 {
sort.Slice(oldTask.Reminders, func(i, j int) bool {
return oldTask.Reminders[i].Unix() < oldTask.Reminders[j].Unix()
return oldTask.Reminders[i].Reminder.Unix() < oldTask.Reminders[j].Reminder.Unix()
})
first := oldTask.Reminders[0]
first := oldTask.Reminders[0].Reminder
for in, r := range oldTask.Reminders {
diff := r.Sub(first)
newTask.Reminders[in] = now.Add(repeatDuration + diff)
diff := r.Reminder.Sub(first)
newTask.Reminders[in].Reminder = now.Add(repeatDuration + diff)
}
}
@ -1478,11 +1491,66 @@ func updateDone(oldTask *Task, newTask *Task) {
}
}
// Deprecated: will be removed when ReminderDates are removed from Task.
// For now the method just creates TaskReminder objects from the ReminderDates and overwrites Reminder.
func (t *Task) overwriteRemindersWithReminderDates(reminderDates []time.Time) {
// If the client still sends old reminder_dates, then these will overwrite
// the Reminders, if the were sent by the client, too.
// We assume that clients still using the old API with reminder_dates do not understand the new reminders.
// Clients who want to use the new Reminder structure must explicitey unset reminder_dates.
// start with empty Reminders
reminders := make([]*TaskReminder, 0)
// append absolute triggers from ReminderDates
for _, reminderDate := range reminderDates {
reminders = append(reminders, &TaskReminder{TaskID: t.ID, Reminder: reminderDate})
}
t.Reminders = reminders
}
// Set the absolute trigger dates for Reminders with relative period
func updateRelativeReminderDates(task *Task) (err error) {
for _, reminder := range task.Reminders {
relativeDuration := time.Duration(reminder.RelativePeriod) * time.Second
if reminder.RelativeTo != "" {
reminder.Reminder = time.Time{}
}
switch reminder.RelativeTo {
case ReminderRelationDueDate:
if !task.DueDate.IsZero() {
reminder.Reminder = task.DueDate.Add(relativeDuration)
}
case ReminderRelationStartDate:
if !task.StartDate.IsZero() {
reminder.Reminder = task.StartDate.Add(relativeDuration)
}
case ReminderRelationEndDate:
if !task.EndDate.IsZero() {
reminder.Reminder = task.EndDate.Add(relativeDuration)
}
default:
if reminder.RelativePeriod != 0 {
err = ErrReminderRelativeToMissing{
TaskID: task.ID,
}
return err
}
}
}
return nil
}
// Removes all old reminders and adds the new ones. This is a lot easier and less buggy than
// trying to figure out which reminders changed and then only re-add those needed. And since it does
// not make a performance difference we'll just do that.
// The parameter is a slice with unix dates which holds the new reminders.
func (t *Task) updateReminders(s *xorm.Session, reminders []time.Time) (err error) {
// The parameter is a slice which holds the new reminders.
func (t *Task) updateReminders(s *xorm.Session, task *Task) (err error) {
// Deprecated: This statement must be removed when ReminderDates will be removed
if task.ReminderDates != nil {
task.overwriteRemindersWithReminderDates(task.ReminderDates)
}
_, err = s.
Where("task_id = ?", t.ID).
@ -1491,23 +1559,43 @@ func (t *Task) updateReminders(s *xorm.Session, reminders []time.Time) (err erro
return
}
// Resolve duplicates and sort them
reminderMap := make(map[int64]time.Time, len(reminders))
for _, reminder := range reminders {
reminderMap[reminder.UTC().Unix()] = reminder
err = updateRelativeReminderDates(task)
if err != nil {
return
}
// Resolve duplicates and sort them
reminderMap := make(map[int64]*TaskReminder, len(task.Reminders))
for _, reminder := range task.Reminders {
reminderMap[reminder.Reminder.UTC().Unix()] = reminder
}
t.Reminders = make([]*TaskReminder, 0, len(reminderMap))
t.ReminderDates = make([]time.Time, 0, len(reminderMap))
// Loop through all reminders and add them
for _, r := range reminderMap {
_, err = s.Insert(&TaskReminder{TaskID: t.ID, Reminder: r})
taskReminder := &TaskReminder{
TaskID: t.ID,
Reminder: r.Reminder,
RelativePeriod: r.RelativePeriod,
RelativeTo: r.RelativeTo}
_, err = s.Insert(taskReminder)
if err != nil {
return err
}
t.Reminders = append(t.Reminders, taskReminder)
t.ReminderDates = append(t.ReminderDates, taskReminder.Reminder)
}
t.Reminders = reminders
if len(reminders) == 0 {
// sort reminders
sort.Slice(t.Reminders, func(i, j int) bool {
return t.Reminders[i].Reminder.Before(t.Reminders[j].Reminder)
})
if len(t.Reminders) == 0 {
t.Reminders = nil
t.ReminderDates = nil
}
err = updateProjectLastUpdated(s, &Project{ID: t.ProjectID})

View File

@ -70,6 +70,48 @@ func TestTask_Create(t *testing.T) {
events.AssertDispatched(t, &TaskCreatedEvent{})
})
t.Run("with reminders", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
task := &Task{
Title: "Lorem",
Description: "Lorem Ipsum Dolor",
ProjectID: 1,
DueDate: time.Date(2023, time.March, 7, 22, 5, 0, 0, time.Local),
StartDate: time.Date(2023, time.March, 7, 22, 5, 10, 0, time.Local),
EndDate: time.Date(2023, time.March, 7, 22, 5, 20, 0, time.Local),
Reminders: []*TaskReminder{
{
RelativeTo: "due_date",
RelativePeriod: 1,
},
{
RelativeTo: "start_date",
RelativePeriod: -2,
},
{
RelativeTo: "end_date",
RelativePeriod: -1,
},
{
Reminder: time.Date(2023, time.March, 7, 23, 0, 0, 0, time.Local),
},
}}
err := task.Create(s, usr)
assert.NoError(t, err)
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 1, 0, time.Local), task.Reminders[0].Reminder)
assert.Equal(t, int64(1), task.Reminders[0].RelativePeriod)
assert.Equal(t, ReminderRelationDueDate, task.Reminders[0].RelativeTo)
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 8, 0, time.Local), task.Reminders[1].Reminder)
assert.Equal(t, ReminderRelationStartDate, task.Reminders[1].RelativeTo)
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 19, 0, time.Local), task.Reminders[2].Reminder)
assert.Equal(t, ReminderRelationEndDate, task.Reminders[2].RelativeTo)
assert.Equal(t, time.Date(2023, time.March, 7, 23, 0, 0, 0, time.Local), task.Reminders[3].Reminder)
err = s.Commit()
assert.NoError(t, err)
})
t.Run("empty title", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
@ -98,7 +140,7 @@ func TestTask_Create(t *testing.T) {
assert.Error(t, err)
assert.True(t, IsErrProjectDoesNotExist(err))
})
t.Run("noneixtant user", func(t *testing.T) {
t.Run("nonexistant user", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
@ -368,7 +410,51 @@ func TestTask_Update(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, int64(3), task.Index)
})
t.Run("the same date multiple times should be saved once", func(t *testing.T) {
t.Run("reminders will be updated", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
task := &Task{
ID: 1,
ProjectID: 1,
Title: "test",
DueDate: time.Date(2023, time.March, 7, 22, 5, 0, 0, time.Local),
StartDate: time.Date(2023, time.March, 7, 22, 5, 10, 0, time.Local),
EndDate: time.Date(2023, time.March, 7, 22, 5, 20, 0, time.Local),
Reminders: []*TaskReminder{
{
RelativeTo: "due_date",
RelativePeriod: 1,
},
{
RelativeTo: "start_date",
RelativePeriod: -2,
},
{
RelativeTo: "end_date",
RelativePeriod: -1,
},
{
Reminder: time.Date(2023, time.March, 7, 23, 0, 0, 0, time.Local),
},
}}
err := task.Update(s, u)
assert.NoError(t, err)
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 1, 0, time.Local), task.Reminders[0].Reminder)
assert.Equal(t, int64(1), task.Reminders[0].RelativePeriod)
assert.Equal(t, ReminderRelationDueDate, task.Reminders[0].RelativeTo)
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 8, 0, time.Local), task.Reminders[1].Reminder)
assert.Equal(t, ReminderRelationStartDate, task.Reminders[1].RelativeTo)
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 19, 0, time.Local), task.Reminders[2].Reminder)
assert.Equal(t, ReminderRelationEndDate, task.Reminders[2].RelativeTo)
assert.Equal(t, time.Date(2023, time.March, 7, 23, 0, 0, 0, time.Local), task.Reminders[3].Reminder)
err = s.Commit()
assert.NoError(t, err)
db.AssertCount(t, "task_reminders", builder.Eq{"task_id": 1}, 4)
})
t.Run("the same reminder multiple times should be saved once", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
@ -376,9 +462,13 @@ func TestTask_Update(t *testing.T) {
task := &Task{
ID: 1,
Title: "test",
Reminders: []time.Time{
time.Unix(1674745156, 0),
time.Unix(1674745156, 223),
Reminders: []*TaskReminder{
{
Reminder: time.Unix(1674745156, 0),
},
{
Reminder: time.Unix(1674745156, 223),
},
},
ProjectID: 1,
}
@ -386,9 +476,42 @@ func TestTask_Update(t *testing.T) {
assert.NoError(t, err)
err = s.Commit()
assert.NoError(t, err)
db.AssertCount(t, "task_reminders", builder.Eq{"task_id": 1}, 1)
})
t.Run("update relative reminder when start_date changes", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
// given task with start_date and relative reminder for start_date
taskBefore := &Task{
Title: "test",
ProjectID: 1,
StartDate: time.Date(2022, time.March, 8, 8, 5, 20, 0, time.Local),
Reminders: []*TaskReminder{
{
RelativeTo: "start_date",
RelativePeriod: -60,
},
}}
err := taskBefore.Create(s, u)
assert.NoError(t, err)
err = s.Commit()
assert.NoError(t, err)
assert.Equal(t, time.Date(2022, time.March, 8, 8, 4, 20, 0, time.Local), taskBefore.Reminders[0].Reminder)
// when start_date is modified
task := taskBefore
task.StartDate = time.Date(2023, time.March, 8, 8, 5, 0, 0, time.Local)
task.ReminderDates = nil
err = task.Update(s, u)
assert.NoError(t, err)
// then reminder time is updated
assert.Equal(t, time.Date(2023, time.March, 8, 8, 4, 0, 0, time.Local), task.Reminders[0].Reminder)
err = s.Commit()
assert.NoError(t, err)
})
}
func TestTask_Delete(t *testing.T) {
@ -487,9 +610,13 @@ func TestUpdateDone(t *testing.T) {
oldTask := &Task{
Done: false,
RepeatAfter: 8600,
Reminders: []time.Time{
time.Unix(1550000000, 0),
time.Unix(1555000000, 0),
Reminders: []*TaskReminder{
{
Reminder: time.Unix(1550000000, 0),
},
{
Reminder: time.Unix(1555000000, 0),
},
},
}
newTask := &Task{
@ -507,8 +634,8 @@ func TestUpdateDone(t *testing.T) {
}
assert.Len(t, newTask.Reminders, 2)
assert.Equal(t, expected1, newTask.Reminders[0])
assert.Equal(t, expected2, newTask.Reminders[1])
assert.Equal(t, expected1, newTask.Reminders[0].Reminder)
assert.Equal(t, expected2, newTask.Reminders[1].Reminder)
assert.False(t, newTask.Done)
})
t.Run("update start date", func(t *testing.T) {
@ -585,22 +712,25 @@ func TestUpdateDone(t *testing.T) {
Done: false,
RepeatAfter: 8600,
RepeatMode: TaskRepeatModeFromCurrentDate,
Reminders: []time.Time{
time.Unix(1550000000, 0),
time.Unix(1555000000, 0),
},
}
Reminders: []*TaskReminder{
{
Reminder: time.Unix(1550000000, 0),
},
{
Reminder: time.Unix(1555000000, 0),
},
}}
newTask := &Task{
Done: true,
}
updateDone(oldTask, newTask)
diff := oldTask.Reminders[1].Sub(oldTask.Reminders[0])
diff := oldTask.Reminders[1].Reminder.Sub(oldTask.Reminders[0].Reminder)
assert.Len(t, newTask.Reminders, 2)
// Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value
assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.Reminders[0].Unix())
assert.Equal(t, time.Now().Add(diff+time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.Reminders[1].Unix())
assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.Reminders[0].Reminder.Unix())
assert.Equal(t, time.Now().Add(diff+time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.Reminders[1].Reminder.Unix())
assert.False(t, newTask.Done)
})
t.Run("start date", func(t *testing.T) {
@ -678,23 +808,28 @@ func TestUpdateDone(t *testing.T) {
oldTask := &Task{
Done: false,
RepeatMode: TaskRepeatModeMonth,
Reminders: []time.Time{
time.Unix(1550000000, 0),
time.Unix(1555000000, 0),
},
}
Reminders: []*TaskReminder{
{
Reminder: time.Unix(1550000000, 0),
},
{
Reminder: time.Unix(1555000000, 0),
},
}}
newTask := &Task{
Done: true,
}
oldReminders := make([]time.Time, len(oldTask.Reminders))
copy(oldReminders, oldTask.Reminders)
for i, r := range newTask.Reminders {
oldReminders[i] = r.Reminder
}
updateDone(oldTask, newTask)
assert.Len(t, newTask.Reminders, len(oldReminders))
for i, r := range newTask.Reminders {
assert.True(t, r.After(oldReminders[i]))
assert.NotEqual(t, oldReminders[i].Month(), r.Month())
assert.True(t, r.Reminder.After(oldReminders[i]))
assert.NotEqual(t, oldReminders[i].Month(), r.Reminder.Month())
}
assert.False(t, newTask.Done)
})