mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-07-19 08:13:10 -05:00
Uploads two different avatars in sequence and verifies the header avatar src changes after the second upload. This confirms both the backend cache (via DelPrefix) and the frontend cache are properly invalidated when a new avatar is uploaded.
113 lines
4.6 KiB
TypeScript
113 lines
4.6 KiB
TypeScript
import {test, expect} from '../../support/fixtures'
|
|
|
|
test.describe('User Settings', () => {
|
|
test('Changes the user avatar', async ({authenticatedPage: page}) => {
|
|
await page.goto('/user/settings/avatar')
|
|
await page.waitForLoadState('networkidle')
|
|
|
|
// Wait for the avatar settings content to be visible
|
|
const uploadRadio = page.locator('input[name=avatarProvider][value=upload]')
|
|
await expect(uploadRadio).toBeVisible({timeout: 5000})
|
|
|
|
await uploadRadio.click()
|
|
|
|
// Set the file directly on the (hidden) file input
|
|
const fileInput = page.locator('input[type=file]')
|
|
await fileInput.setInputFiles('tests/fixtures/image.jpg')
|
|
|
|
// Wait for the cropper to be visible (the image needs to be loaded)
|
|
const cropper = page.locator('.vue-advanced-cropper')
|
|
await expect(cropper).toBeVisible({timeout: 10000})
|
|
|
|
// After cropper appears, there's a new "Upload Avatar" button with data-cy attribute
|
|
const uploadButton = page.locator('[data-cy="uploadAvatar"]')
|
|
await expect(uploadButton).toBeVisible()
|
|
|
|
// Wait for the cropper to be ready (button becomes enabled when canvas is ready)
|
|
await expect(uploadButton).toBeEnabled({timeout: 10000})
|
|
|
|
// Set up response waiter before clicking
|
|
const avatarUploadPromise = page.waitForResponse(response =>
|
|
response.url().includes('avatar') && response.request().method() === 'PUT',
|
|
)
|
|
|
|
await uploadButton.click()
|
|
|
|
// Wait for the avatar upload response and verify it succeeded
|
|
const response = await avatarUploadPromise
|
|
expect(response.ok()).toBe(true)
|
|
|
|
await expect(page.locator('.global-notification')).toContainText('Success', {timeout: 10000})
|
|
})
|
|
|
|
test('Invalidates avatar cache when uploading a new avatar', async ({authenticatedPage: page}) => {
|
|
await page.goto('/user/settings/avatar')
|
|
await page.waitForLoadState('networkidle')
|
|
|
|
const uploadRadio = page.locator('input[name=avatarProvider][value=upload]')
|
|
await expect(uploadRadio).toBeVisible({timeout: 5000})
|
|
await uploadRadio.click()
|
|
|
|
const fileInput = page.locator('input[type=file]')
|
|
const uploadButton = page.locator('[data-cy="uploadAvatar"]')
|
|
const headerAvatar = page.locator('.username-dropdown-trigger img.avatar')
|
|
const notification = page.locator('.global-notification')
|
|
|
|
// Upload first avatar (image.jpg)
|
|
await fileInput.setInputFiles('tests/fixtures/image.jpg')
|
|
await expect(uploadButton).toBeEnabled({timeout: 10000})
|
|
|
|
const firstUploadPromise = page.waitForResponse(response =>
|
|
response.url().includes('avatar') && response.request().method() === 'PUT',
|
|
)
|
|
await uploadButton.click()
|
|
const firstResponse = await firstUploadPromise
|
|
expect(firstResponse.ok()).toBe(true)
|
|
await expect(notification).toContainText('Success', {timeout: 10000})
|
|
|
|
// Wait for the header avatar to update and capture its src
|
|
await expect(headerAvatar).toHaveAttribute('src', /blob:|data:/, {timeout: 10000})
|
|
const firstAvatarSrc = await headerAvatar.getAttribute('src')
|
|
|
|
// Wait for the notification to disappear before uploading again
|
|
await expect(notification).not.toBeVisible({timeout: 10000})
|
|
|
|
// Upload second avatar (image-blue.png)
|
|
await fileInput.setInputFiles('tests/fixtures/image-blue.png')
|
|
await expect(uploadButton).toBeEnabled({timeout: 10000})
|
|
|
|
const secondUploadPromise = page.waitForResponse(response =>
|
|
response.url().includes('avatar') && response.request().method() === 'PUT',
|
|
)
|
|
await uploadButton.click()
|
|
const secondResponse = await secondUploadPromise
|
|
expect(secondResponse.ok()).toBe(true)
|
|
await expect(notification).toContainText('Success', {timeout: 10000})
|
|
|
|
// Verify the header avatar changed to a different blob URL
|
|
await expect(headerAvatar).not.toHaveAttribute('src', firstAvatarSrc!, {timeout: 10000})
|
|
})
|
|
|
|
test('Updates the name', async ({authenticatedPage: page}) => {
|
|
await page.goto('/user/settings/general')
|
|
await page.waitForLoadState('networkidle')
|
|
|
|
// Wait for the settings page to be fully loaded and the input to be enabled
|
|
const nameInput = page.locator('.general-settings input.input').first()
|
|
await expect(nameInput).toBeVisible({timeout: 10000})
|
|
await expect(nameInput).toBeEnabled()
|
|
|
|
// Clear and type to ensure Vue's reactivity is triggered
|
|
await nameInput.clear()
|
|
await nameInput.pressSequentially('Lorem Ipsum', {delay: 10})
|
|
|
|
// The save button only appears when isDirty becomes true (settings changed)
|
|
const saveButton = page.locator('[data-cy="saveGeneralSettings"]')
|
|
await expect(saveButton).toBeVisible({timeout: 10000})
|
|
await saveButton.click()
|
|
|
|
await expect(page.locator('.global-notification')).toContainText('Success')
|
|
await expect(page.locator('.navbar .username-dropdown-trigger .username')).toContainText('Lorem Ipsum')
|
|
})
|
|
})
|