From 2e3b2cb770cceeb5bb3969f9797a2e0abb9192aa Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 16 Nov 2025 15:29:50 +0100 Subject: [PATCH] fix: ensure project filters are retained correctly across views (#1643) --- .../e2e/project/project-view-kanban.spec.ts | 44 +++++++ .../e2e/project/project-view-list.spec.ts | 44 +++++++ .../e2e/project/project-view-table.spec.ts | 44 +++++++ frontend/cypress/support/filterTestHelpers.ts | 119 ++++++++++++++++++ .../components/project/views/ProjectTable.vue | 4 - 5 files changed, 251 insertions(+), 4 deletions(-) create mode 100644 frontend/cypress/support/filterTestHelpers.ts diff --git a/frontend/cypress/e2e/project/project-view-kanban.spec.ts b/frontend/cypress/e2e/project/project-view-kanban.spec.ts index 4da9ed2fe..1cb1723e5 100644 --- a/frontend/cypress/e2e/project/project-view-kanban.spec.ts +++ b/frontend/cypress/e2e/project/project-view-kanban.spec.ts @@ -6,6 +6,10 @@ import {TaskFactory} from '../../factories/task' import {prepareProjects} from './prepareProjects' import {ProjectViewFactory} from "../../factories/project_view"; import {TaskBucketFactory} from "../../factories/task_buckets"; +import { + createTasksWithPriorities, + createTasksWithSearch, +} from '../../support/filterTestHelpers' function createSingleTaskInBucket(count = 1, attrs = {}) { const projects = ProjectFactory.create(1) @@ -302,4 +306,44 @@ describe('Project View Kanban', () => { cy.get('.bucket .tasks .task .footer .icon svg') .should('not.exist') }) + + it('Should respect filter query parameter from URL', () => { + const {highPriorityTasks, lowPriorityTasks} = createTasksWithPriorities(buckets) + + cy.visit('/projects/1/4?filter=priority%20>=%204') + + cy.url() + .should('include', 'filter=priority') + + cy.contains('.kanban .bucket', highPriorityTasks[0].title, {timeout: 10000}) + .should('exist') + + cy.get('.kanban .bucket') + .should('contain', highPriorityTasks[0].title) + cy.get('.kanban .bucket') + .should('contain', highPriorityTasks[1].title) + + cy.get('.kanban .bucket') + .should('not.contain', lowPriorityTasks[0].title) + cy.get('.kanban .bucket') + .should('not.contain', lowPriorityTasks[1].title) + }) + + it('Should respect search query parameter from URL', () => { + const {searchableTask} = createTasksWithSearch(buckets) + + cy.visit('/projects/1/4?s=meeting') + + cy.url() + .should('include', 's=meeting') + + cy.contains('.kanban .bucket', searchableTask.title, {timeout: 10000}) + .should('exist') + + cy.get('.kanban .bucket') + .should('contain', searchableTask.title) + + cy.get('.kanban .bucket .tasks .task') + .should('have.length', 1) + }) }) diff --git a/frontend/cypress/e2e/project/project-view-list.spec.ts b/frontend/cypress/e2e/project/project-view-list.spec.ts index 482046d97..a16983815 100644 --- a/frontend/cypress/e2e/project/project-view-list.spec.ts +++ b/frontend/cypress/e2e/project/project-view-list.spec.ts @@ -7,6 +7,10 @@ import {UserFactory} from '../../factories/user' import {ProjectFactory} from '../../factories/project' import {prepareProjects, createProjects} from './prepareProjects' import {BucketFactory} from '../../factories/bucket' +import { + createTasksWithPriorities, + createTasksWithSearch, +} from '../../support/filterTestHelpers' describe('Project View List', () => { createFakeUserAndLogin() @@ -193,4 +197,44 @@ describe('Project View List', () => { cy.get('ul.tasks > div > .subtask-nested') .should('exist') }) + + it('Should respect filter query parameter from URL', () => { + const {highPriorityTasks, lowPriorityTasks} = createTasksWithPriorities() + + cy.visit('/projects/1/1?filter=priority%20>=%204') + + cy.url() + .should('include', 'filter=priority') + + cy.contains('.tasks', highPriorityTasks[0].title, {timeout: 10000}) + .should('exist') + + cy.get('.tasks') + .should('contain', highPriorityTasks[0].title) + cy.get('.tasks') + .should('contain', highPriorityTasks[1].title) + + cy.get('.tasks') + .should('not.contain', lowPriorityTasks[0].title) + cy.get('.tasks') + .should('not.contain', lowPriorityTasks[1].title) + }) + + it('Should respect search query parameter from URL', () => { + const {searchableTask} = createTasksWithSearch() + + cy.visit('/projects/1/1?s=meeting') + + cy.url() + .should('include', 's=meeting') + + cy.contains('.tasks', searchableTask.title, {timeout: 10000}) + .should('exist') + + cy.get('.tasks') + .should('contain', searchableTask.title) + + cy.get('.tasks .task') + .should('have.length', 1) + }) }) diff --git a/frontend/cypress/e2e/project/project-view-table.spec.ts b/frontend/cypress/e2e/project/project-view-table.spec.ts index a418ca060..2f56f9b7b 100644 --- a/frontend/cypress/e2e/project/project-view-table.spec.ts +++ b/frontend/cypress/e2e/project/project-view-table.spec.ts @@ -2,6 +2,10 @@ import {createFakeUserAndLogin} from '../../support/authenticateUser' import {TaskFactory} from '../../factories/task' import {prepareProjects} from './prepareProjects' +import { + createTasksWithPriorities, + createTasksWithSearch, +} from '../../support/filterTestHelpers' describe('Project View Table', () => { createFakeUserAndLogin() @@ -53,4 +57,44 @@ describe('Project View Table', () => { cy.url() .should('contain', `/tasks/${tasks[0].id}`) }) + + it('Should respect filter query parameter from URL', () => { + const {highPriorityTasks, lowPriorityTasks} = createTasksWithPriorities() + + cy.visit('/projects/1/3?filter=priority%20>=%204') + + cy.url() + .should('include', 'filter=priority') + + cy.contains('.project-table table.table', highPriorityTasks[0].title, {timeout: 10000}) + .should('exist') + + cy.get('.project-table table.table') + .should('contain', highPriorityTasks[0].title) + cy.get('.project-table table.table') + .should('contain', highPriorityTasks[1].title) + + cy.get('.project-table table.table') + .should('not.contain', lowPriorityTasks[0].title) + cy.get('.project-table table.table') + .should('not.contain', lowPriorityTasks[1].title) + }) + + it('Should respect search query parameter from URL', () => { + const {searchableTask} = createTasksWithSearch() + + cy.visit('/projects/1/3?s=meeting') + + cy.url() + .should('include', 's=meeting') + + cy.contains('.project-table table.table', searchableTask.title, {timeout: 10000}) + .should('exist') + + cy.get('.project-table table.table') + .should('contain', searchableTask.title) + + cy.get('.project-table table.table tbody tr') + .should('have.length', 1) + }) }) diff --git a/frontend/cypress/support/filterTestHelpers.ts b/frontend/cypress/support/filterTestHelpers.ts new file mode 100644 index 000000000..773e2b696 --- /dev/null +++ b/frontend/cypress/support/filterTestHelpers.ts @@ -0,0 +1,119 @@ +import {TaskFactory} from '../factories/task' +import {TaskBucketFactory} from '../factories/task_buckets' + +export function createTasksWithPriorities(buckets?: any[]) { + TaskFactory.truncate() + + const highPriorityTask1 = TaskFactory.create(1, { + id: 1, + project_id: 1, + priority: 4, + title: 'High Priority Task 1', + }, false)[0] + + const highPriorityTask2 = TaskFactory.create(1, { + id: 2, + project_id: 1, + priority: 4, + title: 'High Priority Task 2', + }, false)[0] + + const lowPriorityTask1 = TaskFactory.create(1, { + id: 3, + project_id: 1, + priority: 1, + title: 'Low Priority Task 1', + }, false)[0] + + const lowPriorityTask2 = TaskFactory.create(1, { + id: 4, + project_id: 1, + priority: 1, + title: 'Low Priority Task 2', + }, false)[0] + + // If buckets are provided (for Kanban), add tasks to buckets + if (buckets && buckets.length > 0) { + TaskBucketFactory.truncate() + TaskBucketFactory.create(1, { + task_id: highPriorityTask1.id, + bucket_id: buckets[0].id, + project_view_id: buckets[0].project_view_id, + }, false) + TaskBucketFactory.create(1, { + task_id: highPriorityTask2.id, + bucket_id: buckets[0].id, + project_view_id: buckets[0].project_view_id, + }, false) + TaskBucketFactory.create(1, { + task_id: lowPriorityTask1.id, + bucket_id: buckets[0].id, + project_view_id: buckets[0].project_view_id, + }, false) + TaskBucketFactory.create(1, { + task_id: lowPriorityTask2.id, + bucket_id: buckets[0].id, + project_view_id: buckets[0].project_view_id, + }, false) + } + + return { + highPriorityTasks: [highPriorityTask1, highPriorityTask2], + lowPriorityTasks: [lowPriorityTask1, lowPriorityTask2], + } +} + +export function createTasksWithSearch(buckets?: any[]) { + TaskFactory.truncate() + + const task1 = TaskFactory.create(1, { + id: 1, + project_id: 1, + title: 'Regular task 1', + }, false)[0] + + const task2 = TaskFactory.create(1, { + id: 2, + project_id: 1, + title: 'Regular task 2', + }, false)[0] + + const task3 = TaskFactory.create(1, { + id: 3, + project_id: 1, + title: 'Regular task 3', + }, false)[0] + + const searchableTask = TaskFactory.create(1, { + id: 4, + project_id: 1, + title: 'Meeting notes for project', + }, false)[0] + + // If buckets are provided (for Kanban), add tasks to buckets + if (buckets && buckets.length > 0) { + TaskBucketFactory.truncate() + TaskBucketFactory.create(1, { + task_id: task1.id, + bucket_id: buckets[0].id, + project_view_id: buckets[0].project_view_id, + }, false) + TaskBucketFactory.create(1, { + task_id: task2.id, + bucket_id: buckets[0].id, + project_view_id: buckets[0].project_view_id, + }, false) + TaskBucketFactory.create(1, { + task_id: task3.id, + bucket_id: buckets[0].id, + project_view_id: buckets[0].project_view_id, + }, false) + TaskBucketFactory.create(1, { + task_id: searchableTask.id, + bucket_id: buckets[0].id, + project_view_id: buckets[0].project_view_id, + }, false) + } + + return { searchableTask } +} diff --git a/frontend/src/components/project/views/ProjectTable.vue b/frontend/src/components/project/views/ProjectTable.vue index 5360dab39..9b5001715 100644 --- a/frontend/src/components/project/views/ProjectTable.vue +++ b/frontend/src/components/project/views/ProjectTable.vue @@ -361,10 +361,6 @@ const { } = taskList const tasks: Ref = taskList.tasks -Object.assign(params.value, { - filter: '', -}) - watch( () => activeColumns.value, () => setActiveColumnsSortParam(),