|
| 1 | +import { test, expect } from '@playwright/test'; |
| 2 | +import { register, login, generateUniqueUser } from './helpers/auth'; |
| 3 | +import { registerUserViaAPI, updateUserViaAPI, createArticleViaAPI } from './helpers/api'; |
| 4 | +import { createArticle, generateUniqueArticle } from './helpers/articles'; |
| 5 | +import { addComment } from './helpers/comments'; |
| 6 | + |
| 7 | +/** |
| 8 | + * Tests for null/empty image and bio field handling. |
| 9 | + * Verifies that a default avatar SVG is shown when image is null or empty, |
| 10 | + * and that bio fields never render the literal text "null". |
| 11 | + */ |
| 12 | + |
| 13 | +test.describe('Null/Empty Image and Bio Handling', () => { |
| 14 | + // Brief cooldown between tests to avoid backend rate limiting |
| 15 | + test.afterEach(async ({ context }) => { |
| 16 | + await context.close(); |
| 17 | + await new Promise(resolve => setTimeout(resolve, 100)); |
| 18 | + }); |
| 19 | + |
| 20 | + test('newly registered user should show default avatar on profile page', async ({ page }) => { |
| 21 | + const user = generateUniqueUser(); |
| 22 | + await register(page, user.username, user.email, user.password); |
| 23 | + await page.goto(`/profile/${user.username}`, { waitUntil: 'load' }); |
| 24 | + await page.waitForSelector('.user-img'); |
| 25 | + const profileImg = page.locator('.user-img'); |
| 26 | + await expect(profileImg).toBeVisible(); |
| 27 | + const src = await profileImg.getAttribute('src'); |
| 28 | + expect(src).toContain('default-avatar.svg'); |
| 29 | + }); |
| 30 | + |
| 31 | + test('newly registered user should show default avatar in navbar', async ({ page }) => { |
| 32 | + const user = generateUniqueUser(); |
| 33 | + await register(page, user.username, user.email, user.password); |
| 34 | + const navImg = page.locator('nav .user-pic'); |
| 35 | + await expect(navImg).toBeVisible(); |
| 36 | + const src = await navImg.getAttribute('src'); |
| 37 | + expect(src).toContain('default-avatar.svg'); |
| 38 | + }); |
| 39 | + |
| 40 | + test('newly registered user should show default avatar on article meta', async ({ page }) => { |
| 41 | + const user = generateUniqueUser(); |
| 42 | + await register(page, user.username, user.email, user.password); |
| 43 | + const article = generateUniqueArticle(); |
| 44 | + await createArticle(page, article); |
| 45 | + const articleMetaImg = page.locator('.article-meta img').first(); |
| 46 | + await expect(articleMetaImg).toBeVisible(); |
| 47 | + const src = await articleMetaImg.getAttribute('src'); |
| 48 | + expect(src).toContain('default-avatar.svg'); |
| 49 | + }); |
| 50 | + |
| 51 | + test('newly registered user should show default avatar in comment section', async ({ page }) => { |
| 52 | + const user = generateUniqueUser(); |
| 53 | + await register(page, user.username, user.email, user.password); |
| 54 | + const article = generateUniqueArticle(); |
| 55 | + await createArticle(page, article); |
| 56 | + await addComment(page, 'Test comment for avatar check'); |
| 57 | + // Comment form author image |
| 58 | + const commentFormImg = page.locator('.comment-form .comment-author-img'); |
| 59 | + await expect(commentFormImg).toBeVisible(); |
| 60 | + const formSrc = await commentFormImg.getAttribute('src'); |
| 61 | + expect(formSrc).toContain('default-avatar.svg'); |
| 62 | + // Posted comment author image |
| 63 | + const commentImg = page.locator('.card:not(.comment-form) .comment-author-img').first(); |
| 64 | + await expect(commentImg).toBeVisible(); |
| 65 | + const commentSrc = await commentImg.getAttribute('src'); |
| 66 | + expect(commentSrc).toContain('default-avatar.svg'); |
| 67 | + }); |
| 68 | + |
| 69 | + test('setting image should display custom avatar on profile page', async ({ page, request }) => { |
| 70 | + const user = generateUniqueUser(); |
| 71 | + const token = await registerUserViaAPI(request, user); |
| 72 | + const testImage = 'https://api.realworld.io/images/smiley-cyrus.jpeg'; |
| 73 | + await updateUserViaAPI(request, token, { image: testImage }); |
| 74 | + await login(page, user.email, user.password); |
| 75 | + await page.goto(`/profile/${user.username}`, { waitUntil: 'load' }); |
| 76 | + await page.waitForSelector('.user-img'); |
| 77 | + const profileImg = page.locator('.user-img'); |
| 78 | + await expect(profileImg).toHaveAttribute('src', testImage); |
| 79 | + }); |
| 80 | + |
| 81 | + test('clearing image to empty string should restore default avatar', async ({ page, request }) => { |
| 82 | + const user = generateUniqueUser(); |
| 83 | + const token = await registerUserViaAPI(request, user); |
| 84 | + // Set then clear |
| 85 | + await updateUserViaAPI(request, token, { image: 'https://api.realworld.io/images/smiley-cyrus.jpeg' }); |
| 86 | + await updateUserViaAPI(request, token, { image: '' }); |
| 87 | + await login(page, user.email, user.password); |
| 88 | + await page.goto(`/profile/${user.username}`, { waitUntil: 'load' }); |
| 89 | + await page.waitForSelector('.user-img'); |
| 90 | + const profileImg = page.locator('.user-img'); |
| 91 | + const src = await profileImg.getAttribute('src'); |
| 92 | + expect(src).toContain('default-avatar.svg'); |
| 93 | + }); |
| 94 | + |
| 95 | + test('null bio should not render as literal "null" on profile page', async ({ page }) => { |
| 96 | + const user = generateUniqueUser(); |
| 97 | + await register(page, user.username, user.email, user.password); |
| 98 | + await page.goto(`/profile/${user.username}`, { waitUntil: 'load' }); |
| 99 | + await page.waitForSelector('.user-info'); |
| 100 | + const bioText = await page.locator('.user-info p').textContent(); |
| 101 | + expect(bioText?.trim()).not.toBe('null'); |
| 102 | + expect(bioText?.trim()).toBe(''); |
| 103 | + }); |
| 104 | + |
| 105 | + test('setting then clearing bio should not show stale data', async ({ page, request }) => { |
| 106 | + const user = generateUniqueUser(); |
| 107 | + const token = await registerUserViaAPI(request, user); |
| 108 | + const testBio = 'This is a test bio'; |
| 109 | + await updateUserViaAPI(request, token, { bio: testBio }); |
| 110 | + await updateUserViaAPI(request, token, { bio: '' }); |
| 111 | + await login(page, user.email, user.password); |
| 112 | + await page.goto(`/profile/${user.username}`, { waitUntil: 'load' }); |
| 113 | + await page.waitForSelector('.user-info'); |
| 114 | + const bioText = await page.locator('.user-info p').textContent(); |
| 115 | + expect(bioText?.trim()).not.toBe(testBio); |
| 116 | + expect(bioText?.trim()).not.toBe('null'); |
| 117 | + }); |
| 118 | + |
| 119 | + test('settings form should show empty string for null image', async ({ page }) => { |
| 120 | + const user = generateUniqueUser(); |
| 121 | + await register(page, user.username, user.email, user.password); |
| 122 | + await page.goto('/settings', { waitUntil: 'load' }); |
| 123 | + await expect(page.locator('input[formControlName="image"]')).toHaveValue(''); |
| 124 | + }); |
| 125 | + |
| 126 | + test('settings form should show empty string for null bio', async ({ page }) => { |
| 127 | + const user = generateUniqueUser(); |
| 128 | + await register(page, user.username, user.email, user.password); |
| 129 | + await page.goto('/settings', { waitUntil: 'load' }); |
| 130 | + await expect(page.locator('textarea[formControlName="bio"]')).toHaveValue(''); |
| 131 | + }); |
| 132 | + |
| 133 | + test('default avatar should display on other user articles in feed', async ({ page, request }) => { |
| 134 | + // Create a user with no image who has an article |
| 135 | + const author = generateUniqueUser(); |
| 136 | + const token = await registerUserViaAPI(request, author); |
| 137 | + const uniqueId = Date.now(); |
| 138 | + await createArticleViaAPI(request, token, { |
| 139 | + title: `Null avatar test ${uniqueId}`, |
| 140 | + description: `Description ${uniqueId}`, |
| 141 | + body: `Body content ${uniqueId}`, |
| 142 | + }); |
| 143 | + // View the article as a different user and check the author avatar |
| 144 | + const viewer = generateUniqueUser(); |
| 145 | + await register(page, viewer.username, viewer.email, viewer.password); |
| 146 | + await page.goto('/', { waitUntil: 'load' }); |
| 147 | + // Find the article in the global feed |
| 148 | + await page.locator('a.nav-link', { hasText: 'Global Feed' }).click(); |
| 149 | + await page.waitForSelector('.article-preview', { timeout: 10000 }); |
| 150 | + const articlePreview = page.locator('.article-preview', { hasText: `Null avatar test ${uniqueId}` }); |
| 151 | + await expect(articlePreview).toBeVisible(); |
| 152 | + // The author avatar in the article preview should be the default |
| 153 | + const authorImg = articlePreview.locator('.article-meta img'); |
| 154 | + const src = await authorImg.getAttribute('src'); |
| 155 | + expect(src).toContain('default-avatar.svg'); |
| 156 | + }); |
| 157 | +}); |
0 commit comments