chore: move frontend files
This commit is contained in:
150
frontend/cypress/e2e/task/overview.spec.ts
Normal file
150
frontend/cypress/e2e/task/overview.spec.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
import {ProjectFactory} from '../../factories/project'
|
||||
import {seed} from '../../support/seed'
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {BucketFactory} from '../../factories/bucket'
|
||||
import {updateUserSettings} from '../../support/updateUserSettings'
|
||||
|
||||
function seedTasks(numberOfTasks = 50, startDueDate = new Date()) {
|
||||
const project = ProjectFactory.create()[0]
|
||||
BucketFactory.create(1, {
|
||||
project_id: project.id,
|
||||
})
|
||||
const tasks = []
|
||||
let dueDate = startDueDate
|
||||
for (let i = 0; i < numberOfTasks; i++) {
|
||||
const now = new Date()
|
||||
dueDate = new Date(new Date(dueDate).setDate(dueDate.getDate() + 2))
|
||||
tasks.push({
|
||||
id: i + 1,
|
||||
project_id: project.id,
|
||||
done: false,
|
||||
created_by_id: 1,
|
||||
title: 'Test Task ' + i,
|
||||
index: i + 1,
|
||||
due_date: dueDate.toISOString(),
|
||||
created: now.toISOString(),
|
||||
updated: now.toISOString(),
|
||||
})
|
||||
}
|
||||
seed(TaskFactory.table, tasks)
|
||||
return {tasks, project}
|
||||
}
|
||||
|
||||
describe('Home Page Task Overview', () => {
|
||||
createFakeUserAndLogin()
|
||||
|
||||
it('Should show tasks with a near due date first on the home page overview', () => {
|
||||
const taskCount = 50
|
||||
const {tasks} = seedTasks(taskCount)
|
||||
|
||||
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 now = new Date()
|
||||
const oldDate = new Date(new Date(now).setDate(now.getDate() - 14))
|
||||
const taskCount = 50
|
||||
const {tasks} = seedTasks(taskCount, 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: new Date().toISOString(),
|
||||
}, false)
|
||||
|
||||
cy.visit(`/projects/${tasks[0].project_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(100)
|
||||
const newTaskTitle = 'New Task'
|
||||
|
||||
cy.visit('/')
|
||||
|
||||
cy.visit(`/projects/${tasks[0].project_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 project at the bottom', () => {
|
||||
const {project} = seedTasks(40)
|
||||
updateUserSettings({
|
||||
default_project_id: project.id,
|
||||
overdue_tasks_reminders_time: '9:00',
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
it('Should show the cta buttons for new project when there are no tasks', () => {
|
||||
TaskFactory.truncate()
|
||||
|
||||
cy.visit('/')
|
||||
|
||||
cy.get('.home.app-content .content')
|
||||
.should('contain.text', 'Import your projects and tasks from other services into Vikunja:')
|
||||
})
|
||||
|
||||
it('Should not show the cta buttons for new project when there are tasks', () => {
|
||||
seedTasks()
|
||||
|
||||
cy.visit('/')
|
||||
|
||||
cy.get('.home.app-content .content')
|
||||
.should('not.contain.text', 'You can create a new project for your new tasks:')
|
||||
.should('not.contain.text', 'Or import your projects and tasks from other services into Vikunja:')
|
||||
})
|
||||
})
|
968
frontend/cypress/e2e/task/task.spec.ts
Normal file
968
frontend/cypress/e2e/task/task.spec.ts
Normal file
@ -0,0 +1,968 @@
|
||||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {ProjectFactory} from '../../factories/project'
|
||||
import {TaskCommentFactory} from '../../factories/task_comment'
|
||||
import {UserFactory} from '../../factories/user'
|
||||
import {UserProjectFactory} from '../../factories/users_project'
|
||||
import {TaskAssigneeFactory} from '../../factories/task_assignee'
|
||||
import {LabelFactory} from '../../factories/labels'
|
||||
import {LabelTaskFactory} from '../../factories/label_task'
|
||||
import {BucketFactory} from '../../factories/bucket'
|
||||
|
||||
import {TaskAttachmentFactory} from '../../factories/task_attachments'
|
||||
import {TaskReminderFactory} from '../../factories/task_reminders'
|
||||
|
||||
function addLabelToTaskAndVerify(labelTitle: string) {
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Add Labels')
|
||||
.click()
|
||||
cy.get('.task-view .details.labels-list .multiselect input')
|
||||
.type(labelTitle)
|
||||
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', labelTitle)
|
||||
}
|
||||
|
||||
function uploadAttachmentAndVerify(taskId: number) {
|
||||
cy.intercept(`${Cypress.env('API_URL')}/tasks/${taskId}/attachments`).as('uploadAttachment')
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Add Attachments')
|
||||
.click()
|
||||
cy.get('input[type=file]#files', {timeout: 1000})
|
||||
.selectFile('cypress/fixtures/image.jpg', {force: true}) // The input is not visible, but on purpose
|
||||
cy.wait('@uploadAttachment')
|
||||
|
||||
cy.get('.attachments .attachments .files a.attachment')
|
||||
.should('exist')
|
||||
}
|
||||
|
||||
describe('Task', () => {
|
||||
createFakeUserAndLogin()
|
||||
|
||||
let projects
|
||||
let buckets
|
||||
|
||||
beforeEach(() => {
|
||||
// UserFactory.create(1)
|
||||
projects = ProjectFactory.create(1)
|
||||
buckets = BucketFactory.create(1, {
|
||||
project_id: projects[0].id,
|
||||
})
|
||||
TaskFactory.truncate()
|
||||
UserProjectFactory.truncate()
|
||||
})
|
||||
|
||||
it('Should be created new', () => {
|
||||
cy.visit('/projects/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 project', () => {
|
||||
TaskFactory.create(1)
|
||||
|
||||
cy.visit('/projects/1/list')
|
||||
cy.get('.project-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('/projects/1/list')
|
||||
cy.get('.tasks .task .fancycheckbox')
|
||||
.first()
|
||||
.click()
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
})
|
||||
|
||||
it('Can add a task to favorites', () => {
|
||||
TaskFactory.create(1)
|
||||
|
||||
cy.visit('/projects/1/list')
|
||||
cy.get('.tasks .task .favorite')
|
||||
.first()
|
||||
.click()
|
||||
cy.get('.menu-container')
|
||||
.should('contain', 'Favorites')
|
||||
})
|
||||
|
||||
it('Should show a task description icon if the task has a description', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/tasks**').as('loadTasks')
|
||||
TaskFactory.create(1, {
|
||||
description: 'Lorem Ipsum',
|
||||
})
|
||||
|
||||
cy.visit('/projects/1/list')
|
||||
cy.wait('@loadTasks')
|
||||
|
||||
cy.get('.tasks .task .project-task-icon')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Should not show a task description icon if the task has an empty description', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/tasks**').as('loadTasks')
|
||||
TaskFactory.create(1, {
|
||||
description: '',
|
||||
})
|
||||
|
||||
cy.visit('/projects/1/list')
|
||||
cy.wait('@loadTasks')
|
||||
|
||||
cy.get('.tasks .task .project-task-icon')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
it('Should not show a task description icon if the task has a description containing only an empty p tag', () => {
|
||||
cy.intercept(Cypress.env('API_URL') + '/projects/1/tasks**').as('loadTasks')
|
||||
TaskFactory.create(1, {
|
||||
description: '<p></p>',
|
||||
})
|
||||
|
||||
cy.visit('/projects/1/list')
|
||||
cy.wait('@loadTasks')
|
||||
|
||||
cy.get('.tasks .task .project-task-icon')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
describe('Task Detail View', () => {
|
||||
beforeEach(() => {
|
||||
TaskCommentFactory.truncate()
|
||||
LabelTaskFactory.truncate()
|
||||
TaskAttachmentFactory.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', projects[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: new Date().toISOString(),
|
||||
})
|
||||
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 project has one', () => {
|
||||
const projects = ProjectFactory.create(1, {
|
||||
id: 1,
|
||||
identifier: 'TEST',
|
||||
})
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
project_id: projects[0].id,
|
||||
index: 1,
|
||||
})
|
||||
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view h1.title.task-id')
|
||||
.should('contain', `${projects[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 .tiptap button.done-edit')
|
||||
.click()
|
||||
cy.get('.task-view .details.content.description .tiptap__editor .tiptap.ProseMirror')
|
||||
.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('Shows an empty editor when the description of a task is empty', () => {
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
description: '',
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .details.content.description .tiptap.ProseMirror p')
|
||||
.should('have.attr', 'data-placeholder')
|
||||
cy.get('.task-view .details.content.description .tiptap button.done-edit')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
it('Shows a preview editor when the description of a task is not empty', () => {
|
||||
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 .tiptap.ProseMirror p')
|
||||
.should('not.have.attr', 'data-placeholder')
|
||||
cy.get('.task-view .details.content.description .tiptap button.done-edit')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Shows a preview editor when the description of a task contains html', () => {
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
description: '<p>Lorem Ipsum dolor sit amet</p>',
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .details.content.description .tiptap.ProseMirror p')
|
||||
.should('not.have.attr', 'data-placeholder')
|
||||
cy.get('.task-view .details.content.description .tiptap button.done-edit')
|
||||
.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 .tiptap__editor .tiptap.ProseMirror')
|
||||
.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 .tiptap__editor')
|
||||
.should('contain', 'New Comment')
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
})
|
||||
|
||||
it('Can move a task to another project', () => {
|
||||
const projects = ProjectFactory.create(2)
|
||||
BucketFactory.create(2, {
|
||||
project_id: '{increment}',
|
||||
})
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
project_id: projects[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(`${projects[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', projects[1].title)
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
})
|
||||
|
||||
it('Can delete a task', () => {
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
project_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', `/projects/${tasks[0].project_id}/`)
|
||||
})
|
||||
|
||||
it('Can add an assignee to a task', () => {
|
||||
const users = UserFactory.create(5)
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
project_id: 1,
|
||||
})
|
||||
UserProjectFactory.create(5, {
|
||||
project_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,
|
||||
project_id: 1,
|
||||
})
|
||||
UserProjectFactory.create(5, {
|
||||
project_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('.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,
|
||||
project_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,
|
||||
project_id: 1,
|
||||
})
|
||||
const labels = LabelFactory.create(1)
|
||||
LabelTaskFactory.truncate()
|
||||
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
addLabelToTaskAndVerify(labels[0].title)
|
||||
})
|
||||
|
||||
it('Can add a label to a task and it shows up on the kanban board afterwards', () => {
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
project_id: projects[0].id,
|
||||
bucket_id: buckets[0].id,
|
||||
})
|
||||
const labels = LabelFactory.create(1)
|
||||
LabelTaskFactory.truncate()
|
||||
|
||||
cy.visit(`/projects/${projects[0].id}/kanban`)
|
||||
|
||||
cy.get('.bucket .task')
|
||||
.contains(tasks[0].title)
|
||||
.click()
|
||||
|
||||
addLabelToTaskAndVerify(labels[0].title)
|
||||
|
||||
cy.get('.modal-content .close')
|
||||
.click()
|
||||
|
||||
cy.get('.bucket .task')
|
||||
.should('contain.text', labels[0].title)
|
||||
})
|
||||
|
||||
it('Can remove a label from a task', () => {
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
project_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.get('.task-view .details.labels-list .multiselect .input-wrapper')
|
||||
.should('be.visible')
|
||||
.should('contain', labels[0].title)
|
||||
cy.get('.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 button')
|
||||
.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')
|
||||
})
|
||||
|
||||
it('Can set a due date to a specific 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-popup .flatpickr-innerContainer .flatpickr-days .flatpickr-day.today')
|
||||
.click()
|
||||
cy.get('[data-cy="closeDatepicker"]')
|
||||
.contains('Confirm')
|
||||
.click()
|
||||
|
||||
const today = new Date()
|
||||
const day = today.toLocaleString('default', {day: 'numeric'})
|
||||
const month = today.toLocaleString('default', {month: 'short'})
|
||||
const year = today.toLocaleString('default', {year: 'numeric'})
|
||||
const date = `${day} ${month} ${year}, 12:00:00`
|
||||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Due Date')
|
||||
.get('.date-input .datepicker-popup')
|
||||
.should('not.exist')
|
||||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Due Date')
|
||||
.get('.date-input')
|
||||
.should('contain.text', date)
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
})
|
||||
|
||||
it('Can change a due date to a specific date for a task', () => {
|
||||
const dueDate = new Date()
|
||||
dueDate.setHours(12)
|
||||
dueDate.setMinutes(0)
|
||||
dueDate.setSeconds(0)
|
||||
dueDate.setDate(1)
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
done: false,
|
||||
due_date: dueDate.toISOString(),
|
||||
})
|
||||
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-popup .flatpickr-innerContainer .flatpickr-days .flatpickr-day.today')
|
||||
.click()
|
||||
cy.get('[data-cy="closeDatepicker"]')
|
||||
.contains('Confirm')
|
||||
.click()
|
||||
|
||||
const today = new Date()
|
||||
const day = today.toLocaleString('default', {day: 'numeric'})
|
||||
const month = today.toLocaleString('default', {month: 'short'})
|
||||
const year = today.toLocaleString('default', {year: 'numeric'})
|
||||
const date = `${day} ${month} ${year}, 12:00:00`
|
||||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Due Date')
|
||||
.get('.date-input .datepicker-popup')
|
||||
.should('not.exist')
|
||||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Due Date')
|
||||
.get('.date-input')
|
||||
.should('contain.text', date)
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
})
|
||||
|
||||
it('Can set a reminder', () => {
|
||||
TaskReminderFactory.truncate()
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
done: false,
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Set Reminders')
|
||||
.click()
|
||||
cy.get('.task-view .columns.details .column button')
|
||||
.contains('Add a new reminder')
|
||||
.click()
|
||||
cy.get('.datepicker__quick-select-date')
|
||||
.contains('Tomorrow')
|
||||
.click()
|
||||
|
||||
cy.get('.reminder-options-popup')
|
||||
.should('not.be.visible')
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
})
|
||||
|
||||
it('Allows to set a relative reminder when the task already has a due date', () => {
|
||||
TaskReminderFactory.truncate()
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
done: false,
|
||||
due_date: (new Date()).toISOString(),
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Set Reminders')
|
||||
.click()
|
||||
cy.get('.task-view .columns.details .column button')
|
||||
.contains('Add a new reminder')
|
||||
.click()
|
||||
cy.get('.datepicker__quick-select-date')
|
||||
.should('not.exist')
|
||||
cy.get('.reminder-options-popup .card-content')
|
||||
.should('contain', '1 day before Due Date')
|
||||
cy.get('.reminder-options-popup .card-content')
|
||||
.contains('1 day before Due Date')
|
||||
.click()
|
||||
|
||||
cy.get('.reminder-options-popup')
|
||||
.should('not.be.visible')
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
})
|
||||
|
||||
it('Allows to set a relative reminder when the task already has a start date', () => {
|
||||
TaskReminderFactory.truncate()
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
done: false,
|
||||
start_date: (new Date()).toISOString(),
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Set Reminders')
|
||||
.click()
|
||||
cy.get('.task-view .columns.details .column button')
|
||||
.contains('Add a new reminder')
|
||||
.click()
|
||||
cy.get('.datepicker__quick-select-date')
|
||||
.should('not.exist')
|
||||
cy.get('.reminder-options-popup .card-content')
|
||||
.should('contain', '1 day before Start Date')
|
||||
cy.get('.reminder-options-popup .card-content')
|
||||
.contains('1 day before Start Date')
|
||||
.click()
|
||||
|
||||
cy.get('.reminder-options-popup')
|
||||
.should('not.be.visible')
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
})
|
||||
|
||||
it('Allows to set a custom relative reminder when the task already has a due date', () => {
|
||||
TaskReminderFactory.truncate()
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
done: false,
|
||||
due_date: (new Date()).toISOString(),
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Set Reminders')
|
||||
.click()
|
||||
cy.get('.task-view .columns.details .column button')
|
||||
.contains('Add a new reminder')
|
||||
.click()
|
||||
cy.get('.datepicker__quick-select-date')
|
||||
.should('not.exist')
|
||||
cy.get('.reminder-options-popup .card-content')
|
||||
.contains('Custom')
|
||||
.click()
|
||||
cy.get('.reminder-options-popup .card-content .reminder-period input')
|
||||
.first()
|
||||
.type('{selectall}10')
|
||||
cy.get('.reminder-options-popup .card-content .reminder-period select')
|
||||
.first()
|
||||
.select('days')
|
||||
cy.get('.reminder-options-popup .card-content button')
|
||||
.contains('Confirm')
|
||||
.click()
|
||||
|
||||
cy.get('.reminder-options-popup')
|
||||
.should('not.be.visible')
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
})
|
||||
|
||||
it('Allows to set a fixed reminder when the task already has a due date', () => {
|
||||
TaskReminderFactory.truncate()
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
done: false,
|
||||
due_date: (new Date()).toISOString(),
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Set Reminders')
|
||||
.click()
|
||||
cy.get('.task-view .columns.details .column button')
|
||||
.contains('Add a new reminder')
|
||||
.click()
|
||||
cy.get('.datepicker__quick-select-date')
|
||||
.should('not.exist')
|
||||
cy.get('.reminder-options-popup .card-content')
|
||||
.contains('Date and time')
|
||||
.click()
|
||||
cy.get('.datepicker__quick-select-date')
|
||||
.contains('Tomorrow')
|
||||
.click()
|
||||
|
||||
cy.get('.reminder-options-popup')
|
||||
.should('not.be.visible')
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
})
|
||||
|
||||
it('Can set a priority for a task', () => {
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Set Priority')
|
||||
.click()
|
||||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Priority')
|
||||
.get('.select select')
|
||||
.select('Urgent')
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
|
||||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Priority')
|
||||
.get('.select select')
|
||||
.should('have.value', '4')
|
||||
})
|
||||
|
||||
it('Can set the progress for a task', () => {
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .action-buttons .button')
|
||||
.contains('Set Progress')
|
||||
.click()
|
||||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Progress')
|
||||
.get('.select select')
|
||||
.select('50%')
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
|
||||
cy.wait(200)
|
||||
|
||||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Progress')
|
||||
.get('.select select')
|
||||
.should('be.visible')
|
||||
.should('have.value', '0.5')
|
||||
})
|
||||
|
||||
it('Can add an attachment to a task', () => {
|
||||
TaskAttachmentFactory.truncate()
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
uploadAttachmentAndVerify(tasks[0].id)
|
||||
})
|
||||
|
||||
it('Can add an attachment to a task and see it appearing on kanban', () => {
|
||||
TaskAttachmentFactory.truncate()
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
project_id: projects[0].id,
|
||||
bucket_id: buckets[0].id,
|
||||
})
|
||||
const labels = LabelFactory.create(1)
|
||||
LabelTaskFactory.truncate()
|
||||
|
||||
cy.visit(`/projects/${projects[0].id}/kanban`)
|
||||
|
||||
cy.get('.bucket .task')
|
||||
.contains(tasks[0].title)
|
||||
.click()
|
||||
|
||||
uploadAttachmentAndVerify(tasks[0].id)
|
||||
|
||||
cy.get('.modal-content .close')
|
||||
.click()
|
||||
|
||||
cy.get('.bucket .task .footer .icon svg.fa-paperclip')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Can check items off a checklist', () => {
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
description: `
|
||||
<ul data-type="taskList">
|
||||
<li data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label>
|
||||
<div><p>First Item</p></div>
|
||||
</li>
|
||||
<li data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label>
|
||||
<div><p>Second Item</p></div>
|
||||
</li>
|
||||
<li data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label>
|
||||
<div><p>Third Item</p></div>
|
||||
</li>
|
||||
<li data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label>
|
||||
<div><p>Fourth Item</p></div>
|
||||
</li>
|
||||
<li data-checked="true" data-type="taskItem"><label><input type="checkbox"><span></span></label>
|
||||
<div><p>Fifth Item</p></div>
|
||||
</li>
|
||||
</ul>`,
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.task-view .checklist-summary')
|
||||
.should('contain.text', '1 of 5 tasks')
|
||||
cy.get('.tiptap__editor ul > li input[type=checkbox]')
|
||||
.eq(2)
|
||||
.click()
|
||||
|
||||
cy.get('.task-view .details.content.description h3 span.is-small.has-text-success')
|
||||
.contains('Saved!')
|
||||
.should('exist')
|
||||
cy.get('.tiptap__editor ul > li input[type=checkbox]')
|
||||
.eq(2)
|
||||
.should('be.checked')
|
||||
cy.get('.tiptap__editor input[type=checkbox]')
|
||||
.should('have.length', 5)
|
||||
cy.get('.task-view .checklist-summary')
|
||||
.should('contain.text', '2 of 5 tasks')
|
||||
})
|
||||
|
||||
it('Should use the editor to render description', () => {
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
description: `
|
||||
<h1>Lorem Ipsum</h1>
|
||||
<p>Dolor sit amet</p>
|
||||
<ul data-type="taskList">
|
||||
<li data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label>
|
||||
<div><p>First Item</p></div>
|
||||
</li>
|
||||
<li data-checked="false" data-type="taskItem"><label><input type="checkbox"><span></span></label>
|
||||
<div><p>Second Item</p></div>
|
||||
</li>
|
||||
</ul>`,
|
||||
})
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.tiptap__editor ul > li input[type=checkbox]')
|
||||
.should('exist')
|
||||
cy.get('.tiptap__editor h1')
|
||||
.contains('Lorem Ipsum')
|
||||
.should('exist')
|
||||
cy.get('.tiptap__editor p')
|
||||
.contains('Dolor sit amet')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Should render an image from attachment', async () => {
|
||||
|
||||
TaskAttachmentFactory.truncate()
|
||||
|
||||
const tasks = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
description: '',
|
||||
})
|
||||
|
||||
cy.readFile('cypress/fixtures/image.jpg', null).then(file => {
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append('files', new Blob([file]), 'image.jpg')
|
||||
|
||||
cy.request({
|
||||
method: 'PUT',
|
||||
url: `${Cypress.env('API_URL')}/tasks/${tasks[0].id}/attachments`,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${window.localStorage.getItem('token')}`,
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
body: formData,
|
||||
})
|
||||
.then(({body}) => {
|
||||
const dec = new TextDecoder('utf-8')
|
||||
const {success} = JSON.parse(dec.decode(body))
|
||||
|
||||
TaskFactory.create(1, {
|
||||
id: 1,
|
||||
description: `<img src="${Cypress.env('API_URL')}/tasks/${tasks[0].id}/attachments/${success[0].id}" alt="test image">`,
|
||||
})
|
||||
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
cy.get('.tiptap__editor img')
|
||||
.should('be.visible')
|
||||
.and(($img) => {
|
||||
// "naturalWidth" and "naturalHeight" are set when the image loads
|
||||
expect($img[0].naturalWidth).to.be.greaterThan(0)
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
Reference in New Issue
Block a user