mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-03-11 17:48:44 -05:00
feat(kanban): add setting to always show bucket task count (#1966)
Added "Always show task count on Kanban buckets" setting in user preferences to control the visibility of task counts on Kanban bucket headers
This commit is contained in:
@@ -5,6 +5,7 @@ import {TaskFactory} from '../../factories/task'
|
||||
import {ProjectViewFactory} from '../../factories/project_view'
|
||||
import {TaskBucketFactory} from '../../factories/task_buckets'
|
||||
import {createTasksWithPriorities, createTasksWithSearch} from '../../support/filterTestHelpers'
|
||||
import {updateUserSettings} from '../../support/updateUserSettings'
|
||||
|
||||
async function createSingleTaskInBucket(count = 1, attrs = {}) {
|
||||
const projects = await ProjectFactory.create(1)
|
||||
@@ -312,4 +313,60 @@ test.describe('Project View Kanban', () => {
|
||||
// Verify only one task is shown (the search result) - count task headings
|
||||
await expect(page.locator('main h2')).toHaveCount(1)
|
||||
})
|
||||
|
||||
test('Should not show task count by default when bucket has no limit', async ({authenticatedPage: page}) => {
|
||||
await createTaskWithBuckets(buckets, 5)
|
||||
await page.goto('/projects/1/4')
|
||||
|
||||
// Wait for buckets to load
|
||||
await expect(page.locator('.kanban .bucket .title').filter({hasText: buckets[0].title})).toBeVisible()
|
||||
|
||||
// Verify the task count span is not visible when no limit is set
|
||||
await expect(page.locator('.kanban .bucket .bucket-header span.limit').first()).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('Should show task count when alwaysShowBucketTaskCount setting is enabled', async ({authenticatedPage: page, apiContext, userToken}) => {
|
||||
await createTaskWithBuckets(buckets, 5)
|
||||
|
||||
// Enable the alwaysShowBucketTaskCount setting
|
||||
await updateUserSettings(apiContext, userToken, {
|
||||
frontendSettings: {
|
||||
alwaysShowBucketTaskCount: true,
|
||||
},
|
||||
})
|
||||
|
||||
await page.goto('/projects/1/4')
|
||||
|
||||
// Wait for buckets to load
|
||||
await expect(page.locator('.kanban .bucket .title').filter({hasText: buckets[0].title})).toBeVisible()
|
||||
|
||||
// Verify the task count is shown (without limit, just the count)
|
||||
const limitSpan = page.locator('.kanban .bucket .bucket-header span.limit').first()
|
||||
await expect(limitSpan).toBeVisible()
|
||||
// Should show just the count (5) without a limit
|
||||
await expect(limitSpan).toContainText('5')
|
||||
// Should not contain a slash (no limit set)
|
||||
await expect(limitSpan).not.toContainText('/')
|
||||
})
|
||||
|
||||
test('Should show count/limit format when bucket has a limit set', async ({authenticatedPage: page}) => {
|
||||
// Create tasks in the bucket
|
||||
await createTaskWithBuckets(buckets, 2)
|
||||
await page.goto('/projects/1/4')
|
||||
|
||||
// Set a bucket limit
|
||||
const bucketDropdown = page.locator('.kanban .bucket .bucket-header .dropdown.options').first()
|
||||
await bucketDropdown.locator('.dropdown-trigger').click()
|
||||
await bucketDropdown.locator('.dropdown-menu .dropdown-item').filter({hasText: 'Limit: Not Set'}).click()
|
||||
await bucketDropdown.locator('.dropdown-menu .field input.input').fill('10')
|
||||
await bucketDropdown.locator('.dropdown-menu .field .control .button').click()
|
||||
|
||||
// Wait for the limit to be saved
|
||||
await expect(page.locator('.global-notification')).toContainText('Success')
|
||||
|
||||
// Verify the count/limit format is shown (2/10)
|
||||
const limitSpan = page.locator('.kanban .bucket .bucket-header span.limit').first()
|
||||
await expect(limitSpan).toBeVisible()
|
||||
await expect(limitSpan).toContainText('2/10')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,8 +4,9 @@ import {TEST_PASSWORD} from './constants'
|
||||
|
||||
/**
|
||||
* This authenticates a user and puts the token in local storage which allows us to perform authenticated requests.
|
||||
* Returns the user and token for use in tests that need to make authenticated API calls.
|
||||
*/
|
||||
export async function login(page: Page, apiContext: APIRequestContext, user?: any) {
|
||||
export async function login(page: Page | null, apiContext: APIRequestContext, user?: any) {
|
||||
if (!user) {
|
||||
throw new Error('Needs user')
|
||||
}
|
||||
@@ -25,12 +26,14 @@ export async function login(page: Page, apiContext: APIRequestContext, user?: an
|
||||
const body = await response.json()
|
||||
const token = body.token
|
||||
|
||||
// Set token in localStorage before navigating
|
||||
await page.addInitScript((token) => {
|
||||
window.localStorage.setItem('token', token)
|
||||
}, token)
|
||||
// Set token in localStorage before navigating (only if page is provided)
|
||||
if (page) {
|
||||
await page.addInitScript((token) => {
|
||||
window.localStorage.setItem('token', token)
|
||||
}, token)
|
||||
}
|
||||
|
||||
return user
|
||||
return {user, token}
|
||||
}
|
||||
|
||||
export async function createFakeUser() {
|
||||
|
||||
@@ -6,13 +6,14 @@ export const test = base.extend<{
|
||||
apiContext: APIRequestContext;
|
||||
authenticatedPage: Page;
|
||||
currentUser: any;
|
||||
userToken: string;
|
||||
}>({
|
||||
apiContext: async ({playwright}, use) => {
|
||||
const baseURL = process.env.API_URL || 'http://localhost:3456/api/v1/'
|
||||
const apiContext = await playwright.request.newContext({
|
||||
baseURL,
|
||||
})
|
||||
|
||||
|
||||
Factory.setRequestContext(apiContext)
|
||||
await use(apiContext)
|
||||
await apiContext.dispose()
|
||||
@@ -23,8 +24,13 @@ export const test = base.extend<{
|
||||
await use(user)
|
||||
},
|
||||
|
||||
userToken: async ({apiContext, currentUser}, use) => {
|
||||
const {token} = await login(null, apiContext, currentUser)
|
||||
await use(token)
|
||||
},
|
||||
|
||||
authenticatedPage: async ({page, apiContext, currentUser}, use) => {
|
||||
await login(page, apiContext, currentUser)
|
||||
const {token} = await login(page, apiContext, currentUser)
|
||||
await use(page)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type {APIRequestContext} from '@playwright/test'
|
||||
import {objectToSnakeCase} from '../../src/helpers/case'
|
||||
|
||||
export async function updateUserSettings(apiContext: APIRequestContext, token: string, settings: any) {
|
||||
const apiUrl = process.env.API_URL || 'http://localhost:3456/api/v1'
|
||||
@@ -9,15 +10,30 @@ export async function updateUserSettings(apiContext: APIRequestContext, token: s
|
||||
},
|
||||
})
|
||||
|
||||
const oldSettings = await userResponse.json()
|
||||
const userData = await userResponse.json()
|
||||
// GET /user returns { settings: { frontend_settings: ... }, ... }
|
||||
// POST /user/settings/general expects { frontend_settings: ... } at the top level
|
||||
const oldSettings = userData.settings || {}
|
||||
|
||||
const snakeSettings = objectToSnakeCase(settings)
|
||||
|
||||
// Deep merge frontend_settings if provided
|
||||
const mergedSettings = {
|
||||
...oldSettings,
|
||||
...snakeSettings,
|
||||
}
|
||||
|
||||
if (snakeSettings.frontend_settings) {
|
||||
mergedSettings.frontend_settings = {
|
||||
...(oldSettings.frontend_settings || {}),
|
||||
...snakeSettings.frontend_settings,
|
||||
}
|
||||
}
|
||||
|
||||
await apiContext.post(`${apiUrl}/user/settings/general`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
data: {
|
||||
...oldSettings,
|
||||
...settings,
|
||||
},
|
||||
data: mergedSettings,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user