1555f04bd2
There was a bug where it would return all tasks with a true value before the ones with a false value. This is the exact opposite of what the db does, leading to wrongly sorted values
865 lines
14 KiB
Go
865 lines
14 KiB
Go
// Vikunja is a todo-list application to facilitate your life.
|
|
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package models
|
|
|
|
import (
|
|
"github.com/mohae/deepcopy"
|
|
"github.com/stretchr/testify/assert"
|
|
"math/rand"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestSortParamValidation(t *testing.T) {
|
|
t.Run("Test valid order by", func(t *testing.T) {
|
|
t.Run(orderAscending.String(), func(t *testing.T) {
|
|
s := &sortParam{
|
|
orderBy: orderAscending,
|
|
sortBy: "id",
|
|
}
|
|
err := s.validate()
|
|
assert.NoError(t, err)
|
|
})
|
|
t.Run(orderDescending.String(), func(t *testing.T) {
|
|
s := &sortParam{
|
|
orderBy: orderDescending,
|
|
sortBy: "id",
|
|
}
|
|
err := s.validate()
|
|
assert.NoError(t, err)
|
|
})
|
|
})
|
|
t.Run("Test valid sort by", func(t *testing.T) {
|
|
for _, test := range []sortProperty{
|
|
taskPropertyID,
|
|
taskPropertyText,
|
|
taskPropertyDescription,
|
|
taskPropertyDone,
|
|
taskPropertyDoneAtUnix,
|
|
taskPropertyDueDateUnix,
|
|
taskPropertyCreatedByID,
|
|
taskPropertyListID,
|
|
taskPropertyRepeatAfter,
|
|
taskPropertyPriority,
|
|
taskPropertyStartDateUnix,
|
|
taskPropertyEndDateUnix,
|
|
taskPropertyHexColor,
|
|
taskPropertyPercentDone,
|
|
taskPropertyUID,
|
|
taskPropertyCreated,
|
|
taskPropertyUpdated,
|
|
} {
|
|
t.Run(test.String(), func(t *testing.T) {
|
|
s := &sortParam{
|
|
orderBy: orderAscending,
|
|
sortBy: test,
|
|
}
|
|
err := s.validate()
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|
|
})
|
|
t.Run("Test invalid order by", func(t *testing.T) {
|
|
s := &sortParam{
|
|
orderBy: "somethingInvalid",
|
|
sortBy: "id",
|
|
}
|
|
err := s.validate()
|
|
assert.Error(t, err)
|
|
assert.True(t, IsErrInvalidSortOrder(err))
|
|
})
|
|
t.Run("Test invalid sort by", func(t *testing.T) {
|
|
s := &sortParam{
|
|
orderBy: orderAscending,
|
|
sortBy: "somethingInvalid",
|
|
}
|
|
err := s.validate()
|
|
assert.Error(t, err)
|
|
assert.True(t, IsErrInvalidSortParam(err))
|
|
})
|
|
}
|
|
|
|
var (
|
|
task1 = &Task{
|
|
ID: 1,
|
|
Text: "aaa",
|
|
Description: "Lorem Ipsum",
|
|
Done: true,
|
|
DoneAtUnix: 1543626000,
|
|
ListID: 1,
|
|
UID: "JywtBPCESImlyKugvaZWrxmXAFAWXFISMeXYImEh",
|
|
Created: 1543626724,
|
|
Updated: 1543626724,
|
|
}
|
|
task2 = &Task{
|
|
ID: 2,
|
|
Text: "bbb",
|
|
Description: "Arem Ipsum",
|
|
Done: true,
|
|
DoneAtUnix: 1543626724,
|
|
CreatedByID: 1,
|
|
ListID: 2,
|
|
PercentDone: 0.3,
|
|
StartDateUnix: 1543626724,
|
|
Created: 1553626724,
|
|
Updated: 1553626724,
|
|
}
|
|
task3 = &Task{
|
|
ID: 3,
|
|
Text: "ccc",
|
|
DueDateUnix: 1583626724,
|
|
Priority: 100,
|
|
ListID: 3,
|
|
HexColor: "000000",
|
|
PercentDone: 0.1,
|
|
Updated: 1555555555,
|
|
}
|
|
task4 = &Task{
|
|
ID: 4,
|
|
Text: "ddd",
|
|
Priority: 1,
|
|
StartDateUnix: 1643626724,
|
|
ListID: 1,
|
|
}
|
|
task5 = &Task{
|
|
ID: 5,
|
|
Text: "eef",
|
|
Priority: 50,
|
|
UID: "shggzCHQWLhGNMNsOGOCOjcVkInOYjTAnORqTkdL",
|
|
DueDateUnix: 1543636724,
|
|
Updated: 1565555555,
|
|
}
|
|
task6 = &Task{
|
|
ID: 6,
|
|
Text: "eef",
|
|
DueDateUnix: 1543616724,
|
|
RepeatAfter: 6400,
|
|
CreatedByID: 2,
|
|
HexColor: "ffffff",
|
|
}
|
|
task7 = &Task{
|
|
ID: 7,
|
|
Text: "mmmn",
|
|
Description: "Zoremis",
|
|
StartDateUnix: 1544600000,
|
|
EndDateUnix: 1584600000,
|
|
UID: "tyzCZuLMSKhwclJOsDyDcUdyVAPBDOPHNTBOLTcW",
|
|
}
|
|
task8 = &Task{
|
|
ID: 8,
|
|
Text: "b123",
|
|
EndDateUnix: 1544700000,
|
|
}
|
|
task9 = &Task{
|
|
ID: 9,
|
|
Done: true,
|
|
DoneAtUnix: 1573626724,
|
|
Text: "a123",
|
|
RepeatAfter: 86000,
|
|
StartDateUnix: 1544600000,
|
|
EndDateUnix: 1544700000,
|
|
}
|
|
task10 = &Task{
|
|
ID: 10,
|
|
Text: "zzz",
|
|
Priority: 10,
|
|
PercentDone: 1,
|
|
}
|
|
)
|
|
|
|
type taskSortTestCase struct {
|
|
name string
|
|
wantAsc []*Task
|
|
wantDesc []*Task
|
|
sortProperty sortProperty
|
|
}
|
|
|
|
var taskSortTestCases = []taskSortTestCase{
|
|
{
|
|
name: "id",
|
|
sortProperty: taskPropertyID,
|
|
wantAsc: []*Task{
|
|
task1,
|
|
task2,
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task6,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
},
|
|
wantDesc: []*Task{
|
|
task10,
|
|
task9,
|
|
task8,
|
|
task7,
|
|
task6,
|
|
task5,
|
|
task4,
|
|
task3,
|
|
task2,
|
|
task1,
|
|
},
|
|
},
|
|
{
|
|
name: "text",
|
|
sortProperty: taskPropertyText,
|
|
wantAsc: []*Task{
|
|
task9,
|
|
task1,
|
|
task8,
|
|
task2,
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task6,
|
|
task7,
|
|
task10,
|
|
},
|
|
wantDesc: []*Task{
|
|
task10,
|
|
task7,
|
|
task5,
|
|
task6,
|
|
task4,
|
|
task3,
|
|
task2,
|
|
task8,
|
|
task1,
|
|
task9,
|
|
},
|
|
},
|
|
{
|
|
name: "description",
|
|
sortProperty: taskPropertyDescription,
|
|
wantAsc: []*Task{
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task6,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
task2,
|
|
task1,
|
|
task7,
|
|
},
|
|
wantDesc: []*Task{
|
|
task7,
|
|
task1,
|
|
task2,
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task6,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
},
|
|
},
|
|
{
|
|
name: "done",
|
|
sortProperty: taskPropertyDone,
|
|
wantAsc: []*Task{
|
|
// These are not
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task6,
|
|
task7,
|
|
task8,
|
|
task10,
|
|
// These are done
|
|
task1,
|
|
task2,
|
|
task9,
|
|
},
|
|
wantDesc: []*Task{
|
|
// These are done
|
|
task1,
|
|
task2,
|
|
task9,
|
|
// These are not
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task6,
|
|
task7,
|
|
task8,
|
|
task10,
|
|
},
|
|
},
|
|
{
|
|
name: "done at",
|
|
sortProperty: taskPropertyDoneAtUnix,
|
|
wantAsc: []*Task{
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task6,
|
|
task7,
|
|
task8,
|
|
task10,
|
|
task1,
|
|
task2,
|
|
task9,
|
|
},
|
|
wantDesc: []*Task{
|
|
task9,
|
|
task2,
|
|
task1,
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task6,
|
|
task7,
|
|
task8,
|
|
task10,
|
|
},
|
|
},
|
|
{
|
|
name: "due date",
|
|
sortProperty: taskPropertyDueDateUnix,
|
|
wantAsc: []*Task{
|
|
task1,
|
|
task2,
|
|
task4,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
task6,
|
|
task5,
|
|
task3,
|
|
},
|
|
wantDesc: []*Task{
|
|
task3,
|
|
task5,
|
|
task6,
|
|
task1,
|
|
task2,
|
|
task4,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
},
|
|
},
|
|
{
|
|
name: "created by id",
|
|
sortProperty: taskPropertyCreatedByID,
|
|
wantAsc: []*Task{
|
|
task1,
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
task2,
|
|
task6,
|
|
},
|
|
wantDesc: []*Task{
|
|
task6,
|
|
task2,
|
|
task1,
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
},
|
|
},
|
|
{
|
|
name: "list id",
|
|
sortProperty: taskPropertyListID,
|
|
wantAsc: []*Task{
|
|
task5,
|
|
task6,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
task1,
|
|
task4,
|
|
task2,
|
|
task3,
|
|
},
|
|
wantDesc: []*Task{
|
|
task3,
|
|
task2,
|
|
task1,
|
|
task4,
|
|
task5,
|
|
task6,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
},
|
|
},
|
|
{
|
|
name: "repeat after",
|
|
sortProperty: taskPropertyRepeatAfter,
|
|
wantAsc: []*Task{
|
|
task1,
|
|
task2,
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task7,
|
|
task8,
|
|
task10,
|
|
task6,
|
|
task9,
|
|
},
|
|
wantDesc: []*Task{
|
|
task9,
|
|
task6,
|
|
task1,
|
|
task2,
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task7,
|
|
task8,
|
|
task10,
|
|
},
|
|
},
|
|
{
|
|
name: "priority",
|
|
sortProperty: taskPropertyPriority,
|
|
wantAsc: []*Task{
|
|
task1,
|
|
task2,
|
|
task6,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
task4,
|
|
task10,
|
|
task5,
|
|
task3,
|
|
},
|
|
wantDesc: []*Task{
|
|
task3,
|
|
task5,
|
|
task10,
|
|
task4,
|
|
task1,
|
|
task2,
|
|
task6,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
},
|
|
},
|
|
{
|
|
name: "start date",
|
|
sortProperty: taskPropertyStartDateUnix,
|
|
wantAsc: []*Task{
|
|
task1,
|
|
task3,
|
|
task5,
|
|
task6,
|
|
task8,
|
|
task10,
|
|
task2,
|
|
task7,
|
|
task9,
|
|
task4,
|
|
},
|
|
wantDesc: []*Task{
|
|
task4,
|
|
task7,
|
|
task9,
|
|
task2,
|
|
task1,
|
|
task3,
|
|
task5,
|
|
task6,
|
|
task8,
|
|
task10,
|
|
},
|
|
},
|
|
{
|
|
name: "end date",
|
|
sortProperty: taskPropertyEndDateUnix,
|
|
wantAsc: []*Task{
|
|
task1,
|
|
task2,
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task6,
|
|
task10,
|
|
task8,
|
|
task9,
|
|
task7,
|
|
},
|
|
wantDesc: []*Task{
|
|
task7,
|
|
task8,
|
|
task9,
|
|
task1,
|
|
task2,
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task6,
|
|
task10,
|
|
},
|
|
},
|
|
{
|
|
name: "hex color",
|
|
sortProperty: taskPropertyHexColor,
|
|
wantAsc: []*Task{
|
|
task1,
|
|
task2,
|
|
task4,
|
|
task5,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
task3,
|
|
task6,
|
|
},
|
|
wantDesc: []*Task{
|
|
task6,
|
|
task3,
|
|
task1,
|
|
task2,
|
|
task4,
|
|
task5,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
},
|
|
},
|
|
{
|
|
name: "percent done",
|
|
sortProperty: taskPropertyPercentDone,
|
|
wantAsc: []*Task{
|
|
task1,
|
|
task4,
|
|
task5,
|
|
task6,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
task3,
|
|
task2,
|
|
task10,
|
|
},
|
|
wantDesc: []*Task{
|
|
task10,
|
|
task2,
|
|
task3,
|
|
task1,
|
|
task4,
|
|
task5,
|
|
task6,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
},
|
|
},
|
|
{
|
|
name: "uid",
|
|
sortProperty: taskPropertyUID,
|
|
wantAsc: []*Task{
|
|
task2,
|
|
task3,
|
|
task4,
|
|
task6,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
task1,
|
|
task5,
|
|
task7,
|
|
},
|
|
wantDesc: []*Task{
|
|
task7,
|
|
task5,
|
|
task1,
|
|
task2,
|
|
task3,
|
|
task4,
|
|
task6,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
},
|
|
},
|
|
{
|
|
name: "created",
|
|
sortProperty: taskPropertyCreated,
|
|
wantAsc: []*Task{
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task6,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
task1,
|
|
task2,
|
|
},
|
|
wantDesc: []*Task{
|
|
task2,
|
|
task1,
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task6,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
},
|
|
},
|
|
{
|
|
name: "updated",
|
|
sortProperty: taskPropertyUpdated,
|
|
wantAsc: []*Task{
|
|
task4,
|
|
task6,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
task1,
|
|
task2,
|
|
task3,
|
|
task5,
|
|
},
|
|
wantDesc: []*Task{
|
|
task5,
|
|
task3,
|
|
task2,
|
|
task1,
|
|
task4,
|
|
task6,
|
|
task7,
|
|
task8,
|
|
task9,
|
|
task10,
|
|
},
|
|
},
|
|
}
|
|
|
|
func TestTaskSort(t *testing.T) {
|
|
|
|
assertTestSliceMatch := func(t *testing.T, got, want []*Task) {
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Error("Slices do not match in order")
|
|
t.Error("Got\t| Want")
|
|
for in, task := range got {
|
|
fail := ""
|
|
if task.ID != want[in].ID {
|
|
fail = "wrong"
|
|
}
|
|
t.Errorf("\t%d\t| %d \t%s", task.ID, want[in].ID, fail)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, testCase := range taskSortTestCases {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
t.Run("asc default", func(t *testing.T) {
|
|
by := []*sortParam{
|
|
{
|
|
sortBy: testCase.sortProperty,
|
|
},
|
|
}
|
|
|
|
got := deepcopy.Copy(testCase.wantAsc).([]*Task)
|
|
|
|
// Destroy wanted order to obtain some slice we can sort
|
|
rand.Shuffle(len(got), func(i, j int) {
|
|
got[i], got[j] = got[j], got[i]
|
|
})
|
|
|
|
sortTasks(got, by)
|
|
|
|
assertTestSliceMatch(t, got, testCase.wantAsc)
|
|
})
|
|
t.Run("asc", func(t *testing.T) {
|
|
by := []*sortParam{
|
|
{
|
|
sortBy: testCase.sortProperty,
|
|
orderBy: orderAscending,
|
|
},
|
|
}
|
|
|
|
got := deepcopy.Copy(testCase.wantAsc).([]*Task)
|
|
|
|
// Destroy wanted order to obtain some slice we can sort
|
|
rand.Shuffle(len(got), func(i, j int) {
|
|
got[i], got[j] = got[j], got[i]
|
|
})
|
|
|
|
sortTasks(got, by)
|
|
|
|
assertTestSliceMatch(t, got, testCase.wantAsc)
|
|
})
|
|
t.Run("desc", func(t *testing.T) {
|
|
by := []*sortParam{
|
|
{
|
|
sortBy: testCase.sortProperty,
|
|
orderBy: orderDescending,
|
|
},
|
|
}
|
|
|
|
got := deepcopy.Copy(testCase.wantDesc).([]*Task)
|
|
|
|
// Destroy wanted order to obtain some slice we can sort
|
|
rand.Shuffle(len(got), func(i, j int) {
|
|
got[i], got[j] = got[j], got[i]
|
|
})
|
|
|
|
sortTasks(got, by)
|
|
|
|
assertTestSliceMatch(t, got, testCase.wantDesc)
|
|
})
|
|
})
|
|
}
|
|
|
|
// Other cases
|
|
t.Run("Order by Done Ascending and ID Descending", func(t *testing.T) {
|
|
want := []*Task{
|
|
// Not done
|
|
task10,
|
|
task8,
|
|
task7,
|
|
task6,
|
|
task5,
|
|
task4,
|
|
task3,
|
|
|
|
// Done
|
|
task9,
|
|
task2,
|
|
task1,
|
|
}
|
|
sortParams := []*sortParam{
|
|
{
|
|
sortBy: taskPropertyDone,
|
|
orderBy: orderAscending,
|
|
},
|
|
{
|
|
sortBy: taskPropertyID,
|
|
orderBy: orderDescending,
|
|
},
|
|
}
|
|
|
|
got := deepcopy.Copy(want).([]*Task)
|
|
|
|
// Destroy wanted order to obtain some slice we can sort
|
|
rand.Shuffle(len(got), func(i, j int) {
|
|
got[i], got[j] = got[j], got[i]
|
|
})
|
|
|
|
sortTasks(got, sortParams)
|
|
|
|
assertTestSliceMatch(t, got, want)
|
|
})
|
|
t.Run("Order by Done Ascending and Text Descending", func(t *testing.T) {
|
|
want := []*Task{
|
|
// Not done
|
|
task10,
|
|
task7,
|
|
task5,
|
|
task6,
|
|
task4,
|
|
task3,
|
|
task8,
|
|
// Done
|
|
task2,
|
|
task1,
|
|
task9,
|
|
}
|
|
sortParams := []*sortParam{
|
|
{
|
|
sortBy: taskPropertyDone,
|
|
orderBy: orderAscending,
|
|
},
|
|
{
|
|
sortBy: taskPropertyText,
|
|
orderBy: orderDescending,
|
|
},
|
|
}
|
|
|
|
got := deepcopy.Copy(want).([]*Task)
|
|
|
|
// Destroy wanted order to obtain some slice we can sort
|
|
rand.Shuffle(len(got), func(i, j int) {
|
|
got[i], got[j] = got[j], got[i]
|
|
})
|
|
|
|
sortTasks(got, sortParams)
|
|
|
|
assertTestSliceMatch(t, got, want)
|
|
})
|
|
t.Run("Order by Done Descending and Text Ascending", func(t *testing.T) {
|
|
want := []*Task{
|
|
// Done
|
|
task9,
|
|
task1,
|
|
task2,
|
|
// Not done
|
|
task8,
|
|
task3,
|
|
task4,
|
|
task5,
|
|
task6,
|
|
task7,
|
|
task10,
|
|
}
|
|
sortParams := []*sortParam{
|
|
{
|
|
sortBy: taskPropertyDone,
|
|
orderBy: orderDescending,
|
|
},
|
|
{
|
|
sortBy: taskPropertyText,
|
|
orderBy: orderAscending,
|
|
},
|
|
}
|
|
|
|
got := deepcopy.Copy(want).([]*Task)
|
|
|
|
// Destroy wanted order to obtain some slice we can sort
|
|
rand.Shuffle(len(got), func(i, j int) {
|
|
got[i], got[j] = got[j], got[i]
|
|
})
|
|
|
|
sortTasks(got, sortParams)
|
|
|
|
assertTestSliceMatch(t, got, want)
|
|
|
|
})
|
|
}
|