fix: rely on api to properly sort tasks on home page (#1997)

This PR changes the behaviour of how tasks are sorted. Before, the frontend would sort tasks but this resulted in some cases where tasks were not sorted properly. Most of this is test code to reliably reproduce the problem and make fixing it easier.
The actual bug was in Vikunja's api, therefore I've removed all sorting of tasks in the frontend and ensured the api properly sorts tasks.

Fixes https://github.com/go-vikunja/frontend/issues/54

Depends on vikunja/api#1177

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#1997
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
This commit is contained in:
konrad 2022-06-01 16:59:59 +00:00
parent 402b0b2acf
commit efed128f03
5 changed files with 171 additions and 28 deletions

View File

@ -21,6 +21,16 @@ describe('List View List', () => {
.contains('This list is currently empty.')
.should('exist')
})
it('Should create a new task', () => {
const newTaskTitle = 'New task'
cy.visit('/lists/1')
cy.get('.task-add textarea')
.type(newTaskTitle+'{enter}')
cy.get('.tasks')
.should('contain.text', newTaskTitle)
})
it('Should navigate to the task when the title is clicked', () => {
const tasks = TaskFactory.create(5, {

View File

@ -0,0 +1,130 @@
import {ListFactory} from '../../factories/list'
import {seed} from '../../support/seed'
import {TaskFactory} from '../../factories/task'
import {formatISO} from 'date-fns'
import {UserFactory} from '../../factories/user'
import {NamespaceFactory} from '../../factories/namespace'
import {BucketFactory} from '../../factories/bucket'
import {updateUserSettings} from '../../support/updateUserSettings'
import '../../support/authenticateUser'
function seedTasks(numberOfTasks = 100, startDueDate = new Date()) {
UserFactory.create(1)
NamespaceFactory.create(1)
const list = ListFactory.create()[0]
BucketFactory.create(1, {
list_id: list.id,
})
const tasks = []
let dueDate = startDueDate
for (let i = 0; i < numberOfTasks; i++) {
const now = new Date()
dueDate = (new Date(dueDate.valueOf())).setDate((new Date(dueDate.valueOf())).getDate() + 2)
tasks.push({
id: i + 1,
list_id: list.id,
done: false,
created_by_id: 1,
title: 'Test Task ' + i,
index: i + 1,
due_date: formatISO(dueDate),
created: formatISO(now),
updated: formatISO(now),
})
}
seed(TaskFactory.table, tasks)
return {tasks, list}
}
describe('Home Page Task Overview', () => {
it('Should show tasks with a near due date first on the home page overview', () => {
const {tasks} = seedTasks()
cy.visit('/')
cy.get('[data-cy="showTasks"] .card .task')
.each(([task], index) => {
expect(task.innerText).to.contain(tasks[index].title)
})
})
it('Should show overdue tasks first, then show other tasks', () => {
const oldDate = (new Date()).setDate((new Date()).getDate() - 14)
const {tasks} = seedTasks(100, oldDate)
cy.visit('/')
cy.get('[data-cy="showTasks"] .card .task')
.each(([task], index) => {
expect(task.innerText).to.contain(tasks[index].title)
})
})
it('Should show a new task with a very soon due date at the top', () => {
const {tasks} = seedTasks()
const newTaskTitle = 'New Task'
cy.visit('/')
TaskFactory.create(1, {
id: 999,
title: newTaskTitle,
due_date: formatISO(new Date()),
}, false)
cy.visit(`/lists/${tasks[0].list_id}/list`)
cy.get('.tasks .task')
.first()
.should('contain.text', newTaskTitle)
cy.visit('/')
cy.get('[data-cy="showTasks"] .card .task')
.first()
.should('contain.text', newTaskTitle)
})
it('Should not show a new task without a date at the bottom when there are > 50 tasks', () => {
// We're not using the api here to create the task in order to verify the flow
const {tasks} = seedTasks()
const newTaskTitle = 'New Task'
cy.visit('/')
cy.visit(`/lists/${tasks[0].list_id}/list`)
cy.get('.task-add textarea')
.type(newTaskTitle+'{enter}')
cy.visit('/')
cy.get('[data-cy="showTasks"] .card .task')
.last()
.should('not.contain.text', newTaskTitle)
})
it('Should show a new task without a date at the bottom when there are < 50 tasks', () => {
seedTasks(40)
const newTaskTitle = 'New Task'
TaskFactory.create(1, {
id: 999,
title: newTaskTitle,
}, false)
cy.visit('/')
cy.get('[data-cy="showTasks"] .card .task')
.last()
.should('contain.text', newTaskTitle)
})
it('Should show a task without a due date added via default list at the bottom', () => {
const {list} = seedTasks(40)
updateUserSettings({
default_list_id: list.id,
})
const newTaskTitle = 'New Task'
cy.visit('/')
cy.get('.add-task-textarea')
.type(`${newTaskTitle}{enter}`)
cy.get('[data-cy="showTasks"] .card .task')
.last()
.should('contain.text', newTaskTitle)
})
})

View File

@ -6,13 +6,13 @@ import {TaskCommentFactory} from '../../factories/task_comment'
import {UserFactory} from '../../factories/user'
import {NamespaceFactory} from '../../factories/namespace'
import {UserListFactory} from '../../factories/users_list'
import '../../support/authenticateUser'
import {TaskAssigneeFactory} from '../../factories/task_assignee'
import {LabelFactory} from '../../factories/labels'
import {LabelTaskFactory} from '../../factories/label_task'
import {BucketFactory} from '../../factories/bucket'
import '../../support/authenticateUser'
describe('Task', () => {
let namespaces
let lists

View File

@ -0,0 +1,26 @@
export function updateUserSettings(settings) {
const token = `Bearer ${window.localStorage.getItem('token')}`
return cy.request({
method: 'GET',
url: `${Cypress.env('API_URL')}/user`,
headers: {
'Authorization': token,
},
})
.its('body')
.then(oldSettings => {
return cy.request({
method: 'POST',
url: `${Cypress.env('API_URL')}/user/settings/general`,
headers: {
'Authorization': token,
},
body: {
...oldSettings,
...settings,
},
})
})
}

View File

@ -1,5 +1,5 @@
<template>
<div class="is-max-width-desktop has-text-left ">
<div class="is-max-width-desktop has-text-left" v-cy="'showTasks'">
<h3 class="mb-2 title">
{{ pageTitle }}
</h3>
@ -32,9 +32,8 @@
>
<div class="p-2">
<single-task-in-list
v-for="t in tasksSorted"
v-for="t in tasks"
:key="t.id"
class="task"
:show-list="true"
:the-task="t"
@taskUpdated="updateTasks"/>
@ -104,28 +103,6 @@ const pageTitle = computed(() => {
until: formatDate(dateTo, 'PPP'),
})
})
const tasksSorted = computed(() => {
// Sort all tasks to put those with a due date before the ones without a due date, the
// soonest before the later ones.
// We can't use the api sorting here because that sorts tasks with a due date after
// ones without a due date.
const tasksWithDueDate = [...tasks.value]
.filter(t => t.dueDate !== null)
.sort((a, b) => {
const sortByDueDate = a.dueDate - b.dueDate
return sortByDueDate === 0
? b.id - a.id
: sortByDueDate
})
const tasksWithoutDueDate = [...tasks.value]
.filter(t => t.dueDate === null)
return [
...tasksWithDueDate,
...tasksWithoutDueDate,
]
})
const hasTasks = computed(() => tasks.value && tasks.value.length > 0)
const userAuthenticated = computed(() => store.state.auth.authenticated)
const loading = computed(() => store.state[LOADING] && store.state[LOADING_MODULE] === 'tasks')
@ -178,7 +155,7 @@ async function loadPendingTasks(from: string, to: string) {
const params = {
sortBy: ['due_date', 'id'],
orderBy: ['desc', 'desc'],
orderBy: ['asc', 'desc'],
filterBy: ['done'],
filterValue: ['false'],
filterComparator: ['equals'],