vikunja-api/pkg/models/task_collection_sort_test.go
kolaente 1555f04bd2
Fix sorting tasks by bool values
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
2019-12-07 16:56:18 +01:00

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)
})
}