efed128f03
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 https://kolaente.dev/vikunja/api/pulls/1177 Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/1997 Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
420 lines
11 KiB
JavaScript
420 lines
11 KiB
JavaScript
import {formatISO} from 'date-fns'
|
|
|
|
import {TaskFactory} from '../../factories/task'
|
|
import {ListFactory} from '../../factories/list'
|
|
import {TaskCommentFactory} from '../../factories/task_comment'
|
|
import {UserFactory} from '../../factories/user'
|
|
import {NamespaceFactory} from '../../factories/namespace'
|
|
import {UserListFactory} from '../../factories/users_list'
|
|
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
|
|
|
|
beforeEach(() => {
|
|
UserFactory.create(1)
|
|
namespaces = NamespaceFactory.create(1)
|
|
lists = ListFactory.create(1)
|
|
TaskFactory.truncate()
|
|
UserListFactory.truncate()
|
|
})
|
|
|
|
it('Should be created new', () => {
|
|
cy.visit('/lists/1/list')
|
|
cy.get('.input[placeholder="Add a new task…"')
|
|
.type('New Task')
|
|
cy.get('.button')
|
|
.contains('Add')
|
|
.click()
|
|
cy.get('.tasks .task .tasktext')
|
|
.first()
|
|
.should('contain', 'New Task')
|
|
})
|
|
|
|
it('Inserts new tasks at the top of the list', () => {
|
|
TaskFactory.create(1)
|
|
|
|
cy.visit('/lists/1/list')
|
|
cy.get('.list-is-empty-notice')
|
|
.should('not.exist')
|
|
cy.get('.input[placeholder="Add a new task…"')
|
|
.type('New Task')
|
|
cy.get('.button')
|
|
.contains('Add')
|
|
.click()
|
|
|
|
cy.wait(1000) // Wait for the request
|
|
cy.get('.tasks .task .tasktext')
|
|
.first()
|
|
.should('contain', 'New Task')
|
|
})
|
|
|
|
it('Marks a task as done', () => {
|
|
TaskFactory.create(1)
|
|
|
|
cy.visit('/lists/1/list')
|
|
cy.get('.tasks .task .fancycheckbox label.check')
|
|
.first()
|
|
.click()
|
|
cy.get('.global-notification')
|
|
.should('contain', 'Success')
|
|
})
|
|
|
|
it('Can add a task to favorites', () => {
|
|
TaskFactory.create(1)
|
|
|
|
cy.visit('/lists/1/list')
|
|
cy.get('.tasks .task .favorite')
|
|
.first()
|
|
.click()
|
|
cy.get('.menu.namespaces-lists')
|
|
.should('contain', 'Favorites')
|
|
})
|
|
|
|
describe('Task Detail View', () => {
|
|
beforeEach(() => {
|
|
TaskCommentFactory.truncate()
|
|
})
|
|
|
|
it('Shows all task details', () => {
|
|
const tasks = TaskFactory.create(1, {
|
|
id: 1,
|
|
index: 1,
|
|
description: 'Lorem ipsum dolor sit amet.'
|
|
})
|
|
cy.visit(`/tasks/${tasks[0].id}`)
|
|
|
|
cy.get('.task-view h1.title.input')
|
|
.should('contain', tasks[0].title)
|
|
cy.get('.task-view h1.title.task-id')
|
|
.should('contain', '#1')
|
|
cy.get('.task-view h6.subtitle')
|
|
.should('contain', namespaces[0].title)
|
|
.should('contain', lists[0].title)
|
|
cy.get('.task-view .details.content.description')
|
|
.should('contain', tasks[0].description)
|
|
cy.get('.task-view .action-buttons p.created')
|
|
.should('contain', 'Created')
|
|
})
|
|
|
|
it('Shows a done label for done tasks', () => {
|
|
const tasks = TaskFactory.create(1, {
|
|
id: 1,
|
|
index: 1,
|
|
done: true,
|
|
done_at: formatISO(new Date())
|
|
})
|
|
cy.visit(`/tasks/${tasks[0].id}`)
|
|
|
|
cy.get('.task-view .heading .is-done')
|
|
.should('be.visible')
|
|
.should('contain', 'Done')
|
|
cy.get('.task-view .action-buttons p.created')
|
|
.scrollIntoView()
|
|
.should('be.visible')
|
|
.should('contain', 'Done')
|
|
})
|
|
|
|
it('Can mark a task as done', () => {
|
|
const tasks = TaskFactory.create(1, {
|
|
id: 1,
|
|
done: false,
|
|
})
|
|
cy.visit(`/tasks/${tasks[0].id}`)
|
|
|
|
cy.get('.task-view .action-buttons .button')
|
|
.contains('Mark task done!')
|
|
.click()
|
|
|
|
cy.get('.task-view .heading .is-done')
|
|
.should('exist')
|
|
.should('contain', 'Done')
|
|
cy.get('.global-notification')
|
|
.should('contain', 'Success')
|
|
cy.get('.task-view .action-buttons .button')
|
|
.should('contain', 'Mark as undone')
|
|
})
|
|
|
|
it('Shows a task identifier since the list has one', () => {
|
|
const lists = ListFactory.create(1, {
|
|
id: 1,
|
|
identifier: 'TEST',
|
|
})
|
|
const tasks = TaskFactory.create(1, {
|
|
id: 1,
|
|
list_id: lists[0].id,
|
|
index: 1,
|
|
})
|
|
|
|
cy.visit(`/tasks/${tasks[0].id}`)
|
|
|
|
cy.get('.task-view h1.title.task-id')
|
|
.should('contain', `${lists[0].identifier}-${tasks[0].index}`)
|
|
})
|
|
|
|
it('Can edit the description', () => {
|
|
const tasks = TaskFactory.create(1, {
|
|
id: 1,
|
|
description: 'Lorem ipsum dolor sit amet.'
|
|
})
|
|
cy.visit(`/tasks/${tasks[0].id}`)
|
|
|
|
cy.get('.task-view .details.content.description .editor a')
|
|
.click()
|
|
cy.get('.task-view .details.content.description .editor .vue-easymde .EasyMDEContainer .CodeMirror-scroll')
|
|
.type('{selectall}New Description')
|
|
cy.get('[data-cy="saveEditor"]')
|
|
.contains('Save')
|
|
.click()
|
|
|
|
cy.get('.task-view .details.content.description h3 span.is-small.has-text-success')
|
|
.contains('Saved!')
|
|
.should('exist')
|
|
})
|
|
|
|
it('Can add a new comment', () => {
|
|
const tasks = TaskFactory.create(1, {
|
|
id: 1,
|
|
})
|
|
cy.visit(`/tasks/${tasks[0].id}`)
|
|
|
|
cy.get('.task-view .comments .media.comment .editor .vue-easymde .EasyMDEContainer .CodeMirror-scroll')
|
|
.should('be.visible')
|
|
.type('{selectall}New Comment')
|
|
cy.get('.task-view .comments .media.comment .button:not([disabled])')
|
|
.contains('Comment')
|
|
.should('be.visible')
|
|
.click()
|
|
|
|
cy.get('.task-view .comments .media.comment .editor')
|
|
.should('contain', 'New Comment')
|
|
cy.get('.global-notification')
|
|
.should('contain', 'Success')
|
|
})
|
|
|
|
it('Can move a task to another list', () => {
|
|
const lists = ListFactory.create(2)
|
|
BucketFactory.create(2, {
|
|
list_id: '{increment}'
|
|
})
|
|
const tasks = TaskFactory.create(1, {
|
|
id: 1,
|
|
list_id: lists[0].id,
|
|
})
|
|
cy.visit(`/tasks/${tasks[0].id}`)
|
|
|
|
cy.get('.task-view .action-buttons .button')
|
|
.contains('Move')
|
|
.click()
|
|
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
|
|
.type(`${lists[1].title}{enter}`)
|
|
// The requests happen with a 200ms timeout. Because of that, the results are not yet there when cypress
|
|
// presses enter and we can't simulate pressing on enter to select the item.
|
|
cy.get('.task-view .content.details .field .multiselect.control .search-results')
|
|
.children()
|
|
.first()
|
|
.click()
|
|
|
|
cy.get('.task-view h6.subtitle')
|
|
.should('contain', namespaces[0].title)
|
|
.should('contain', lists[1].title)
|
|
cy.get('.global-notification')
|
|
.should('contain', 'Success')
|
|
})
|
|
|
|
it('Can delete a task', () => {
|
|
const tasks = TaskFactory.create(1, {
|
|
id: 1,
|
|
list_id: 1,
|
|
})
|
|
cy.visit(`/tasks/${tasks[0].id}`)
|
|
|
|
cy.get('.task-view .action-buttons .button')
|
|
.should('be.visible')
|
|
.contains('Delete')
|
|
.click()
|
|
cy.get('.modal-mask .modal-container .modal-content .header')
|
|
.should('contain', 'Delete this task')
|
|
cy.get('.modal-mask .modal-container .modal-content .actions .button')
|
|
.contains('Do it!')
|
|
.click()
|
|
|
|
cy.get('.global-notification')
|
|
.should('contain', 'Success')
|
|
cy.url()
|
|
.should('contain', `/lists/${tasks[0].list_id}/`)
|
|
})
|
|
|
|
it('Can add an assignee to a task', () => {
|
|
const users = UserFactory.create(5)
|
|
const tasks = TaskFactory.create(1, {
|
|
id: 1,
|
|
list_id: 1,
|
|
})
|
|
UserListFactory.create(5, {
|
|
list_id: 1,
|
|
user_id: '{increment}',
|
|
})
|
|
|
|
cy.visit(`/tasks/${tasks[0].id}`)
|
|
|
|
cy.get('[data-cy="taskDetail.assign"]')
|
|
.click()
|
|
cy.get('.task-view .column.assignees .multiselect input')
|
|
.type(users[1].username)
|
|
cy.get('.task-view .column.assignees .multiselect .search-results')
|
|
.children()
|
|
.first()
|
|
.click()
|
|
|
|
cy.get('.global-notification')
|
|
.should('contain', 'Success')
|
|
cy.get('.task-view .column.assignees .multiselect .input-wrapper span.assignee')
|
|
.should('exist')
|
|
})
|
|
|
|
it('Can remove an assignee from a task', () => {
|
|
const users = UserFactory.create(2)
|
|
const tasks = TaskFactory.create(1, {
|
|
id: 1,
|
|
list_id: 1,
|
|
})
|
|
UserListFactory.create(5, {
|
|
list_id: 1,
|
|
user_id: '{increment}',
|
|
})
|
|
TaskAssigneeFactory.create(1, {
|
|
task_id: tasks[0].id,
|
|
user_id: users[1].id,
|
|
})
|
|
|
|
cy.visit(`/tasks/${tasks[0].id}`)
|
|
|
|
cy.get('.task-view .column.assignees .multiselect .input-wrapper span.assignee')
|
|
.get('a.remove-assignee')
|
|
.click()
|
|
|
|
cy.get('.global-notification')
|
|
.should('contain', 'Success')
|
|
cy.get('.task-view .column.assignees .multiselect .input-wrapper span.assignee')
|
|
.should('not.exist')
|
|
})
|
|
|
|
it('Can add a new label to a task', () => {
|
|
const tasks = TaskFactory.create(1, {
|
|
id: 1,
|
|
list_id: 1,
|
|
})
|
|
LabelFactory.truncate()
|
|
const newLabelText = 'some new label'
|
|
|
|
cy.visit(`/tasks/${tasks[0].id}`)
|
|
|
|
cy.get('.task-view .action-buttons .button')
|
|
.contains('Add Labels')
|
|
.should('be.visible')
|
|
.click()
|
|
cy.get('.task-view .details.labels-list .multiselect input')
|
|
.type(newLabelText)
|
|
cy.get('.task-view .details.labels-list .multiselect .search-results')
|
|
.children()
|
|
.first()
|
|
.click()
|
|
|
|
cy.get('.global-notification')
|
|
.should('contain', 'Success')
|
|
cy.get('.task-view .details.labels-list .multiselect .input-wrapper span.tag')
|
|
.should('exist')
|
|
.should('contain', newLabelText)
|
|
})
|
|
|
|
it('Can add an existing label to a task', () => {
|
|
const tasks = TaskFactory.create(1, {
|
|
id: 1,
|
|
list_id: 1,
|
|
})
|
|
const labels = LabelFactory.create(1)
|
|
|
|
cy.visit(`/tasks/${tasks[0].id}`)
|
|
|
|
cy.get('.task-view .action-buttons .button')
|
|
.contains('Add Labels')
|
|
.click()
|
|
cy.get('.task-view .details.labels-list .multiselect input')
|
|
.type(labels[0].title)
|
|
cy.get('.task-view .details.labels-list .multiselect .search-results')
|
|
.children()
|
|
.first()
|
|
.click()
|
|
|
|
cy.get('.global-notification', { timeout: 4000 })
|
|
.should('contain', 'Success')
|
|
cy.get('.task-view .details.labels-list .multiselect .input-wrapper span.tag')
|
|
.should('exist')
|
|
.should('contain', labels[0].title)
|
|
})
|
|
|
|
it('Can remove a label from a task', () => {
|
|
const tasks = TaskFactory.create(1, {
|
|
id: 1,
|
|
list_id: 1,
|
|
})
|
|
const labels = LabelFactory.create(1)
|
|
LabelTaskFactory.create(1, {
|
|
task_id: tasks[0].id,
|
|
label_id: labels[0].id,
|
|
})
|
|
|
|
cy.visit(`/tasks/${tasks[0].id}`)
|
|
|
|
cy.getSettled('.task-view .details.labels-list .multiselect .input-wrapper')
|
|
.should('be.visible')
|
|
.should('contain', labels[0].title)
|
|
cy.getSettled('.task-view .details.labels-list .multiselect .input-wrapper')
|
|
.children()
|
|
.first()
|
|
.get('[data-cy="taskDetail.removeLabel"]')
|
|
.click()
|
|
|
|
cy.get('.global-notification')
|
|
.should('contain', 'Success')
|
|
cy.get('.task-view .details.labels-list .multiselect .input-wrapper')
|
|
.should('not.contain', labels[0].title)
|
|
})
|
|
|
|
it('Can set a due date for a task', () => {
|
|
const tasks = TaskFactory.create(1, {
|
|
id: 1,
|
|
done: false,
|
|
})
|
|
cy.visit(`/tasks/${tasks[0].id}`)
|
|
|
|
cy.get('.task-view .action-buttons .button')
|
|
.contains('Set Due Date')
|
|
.click()
|
|
cy.get('.task-view .columns.details .column')
|
|
.contains('Due Date')
|
|
.get('.date-input .datepicker .show')
|
|
.click()
|
|
cy.get('.datepicker .datepicker-popup a')
|
|
.contains('Tomorrow')
|
|
.click()
|
|
cy.get('[data-cy="closeDatepicker"]')
|
|
.contains('Confirm')
|
|
.click()
|
|
|
|
cy.get('.task-view .columns.details .column')
|
|
.contains('Due Date')
|
|
.get('.date-input .datepicker-popup')
|
|
.should('not.exist')
|
|
cy.get('.global-notification')
|
|
.should('contain', 'Success')
|
|
})
|
|
})
|
|
})
|