Skip to content

Commit bbbf140

Browse files
christian-byrnegithub-actions
andauthored
Handle user avatar error (#3735)
Co-authored-by: github-actions <[email protected]>
1 parent 754eb80 commit bbbf140

File tree

12 files changed

+155
-23
lines changed

12 files changed

+155
-23
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { mount } from '@vue/test-utils'
2+
import Avatar from 'primevue/avatar'
3+
import PrimeVue from 'primevue/config'
4+
import { beforeEach, describe, expect, it } from 'vitest'
5+
import { createApp, nextTick } from 'vue'
6+
import { createI18n } from 'vue-i18n'
7+
8+
import UserAvatar from './UserAvatar.vue'
9+
10+
const i18n = createI18n({
11+
legacy: false,
12+
locale: 'en',
13+
messages: {
14+
en: {
15+
auth: {
16+
login: {
17+
userAvatar: 'User Avatar'
18+
}
19+
}
20+
}
21+
}
22+
})
23+
24+
describe('UserAvatar', () => {
25+
beforeEach(() => {
26+
const app = createApp({})
27+
app.use(PrimeVue)
28+
})
29+
30+
const mountComponent = (props: any = {}) => {
31+
return mount(UserAvatar, {
32+
global: {
33+
plugins: [PrimeVue, i18n],
34+
components: { Avatar }
35+
},
36+
props
37+
})
38+
}
39+
40+
it('renders correctly with photo Url', async () => {
41+
const wrapper = mountComponent({
42+
photoUrl: 'https://example.com/avatar.jpg'
43+
})
44+
45+
const avatar = wrapper.findComponent(Avatar)
46+
expect(avatar.exists()).toBe(true)
47+
expect(avatar.props('image')).toBe('https://example.com/avatar.jpg')
48+
expect(avatar.props('icon')).toBeNull()
49+
})
50+
51+
it('renders with default icon when no photo Url is provided', () => {
52+
const wrapper = mountComponent({
53+
photoUrl: undefined
54+
})
55+
56+
const avatar = wrapper.findComponent(Avatar)
57+
expect(avatar.exists()).toBe(true)
58+
expect(avatar.props('image')).toBeNull()
59+
expect(avatar.props('icon')).toBe('pi pi-user')
60+
})
61+
62+
it('renders with default icon when provided photo Url is null', () => {
63+
const wrapper = mountComponent({
64+
photoUrl: null
65+
})
66+
67+
const avatar = wrapper.findComponent(Avatar)
68+
expect(avatar.exists()).toBe(true)
69+
expect(avatar.props('image')).toBeNull()
70+
expect(avatar.props('icon')).toBe('pi pi-user')
71+
})
72+
73+
it('falls back to icon when image fails to load', async () => {
74+
const wrapper = mountComponent({
75+
photoUrl: 'https://example.com/broken-image.jpg'
76+
})
77+
78+
const avatar = wrapper.findComponent(Avatar)
79+
expect(avatar.props('icon')).toBeNull()
80+
81+
// Simulate image load error
82+
avatar.vm.$emit('error')
83+
await nextTick()
84+
85+
expect(avatar.props('icon')).toBe('pi pi-user')
86+
})
87+
88+
it('uses provided ariaLabel', () => {
89+
const wrapper = mountComponent({
90+
photoUrl: 'https://example.com/avatar.jpg',
91+
ariaLabel: 'Custom Label'
92+
})
93+
94+
const avatar = wrapper.findComponent(Avatar)
95+
expect(avatar.attributes('aria-label')).toBe('Custom Label')
96+
})
97+
98+
it('falls back to i18n translation when no ariaLabel is provided', () => {
99+
const wrapper = mountComponent({
100+
photoUrl: 'https://example.com/avatar.jpg'
101+
})
102+
103+
const avatar = wrapper.findComponent(Avatar)
104+
expect(avatar.attributes('aria-label')).toBe('User Avatar')
105+
})
106+
})
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<template>
2+
<Avatar
3+
:image="photoUrl ?? undefined"
4+
:icon="hasAvatar ? undefined : 'pi pi-user'"
5+
shape="circle"
6+
:aria-label="ariaLabel ?? $t('auth.login.userAvatar')"
7+
@error="handleImageError"
8+
/>
9+
</template>
10+
11+
<script setup lang="ts">
12+
import Avatar from 'primevue/avatar'
13+
import { computed, ref } from 'vue'
14+
15+
const { photoUrl, ariaLabel } = defineProps<{
16+
photoUrl?: string | null
17+
ariaLabel?: string
18+
}>()
19+
20+
const imageError = ref(false)
21+
const handleImageError = () => {
22+
imageError.value = true
23+
}
24+
const hasAvatar = computed(() => photoUrl && !imageError.value)
25+
</script>

src/components/dialog/content/setting/UserPanel.vue

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@
55
<Divider class="mb-3" />
66

77
<div v-if="user" class="flex flex-col gap-2">
8-
<Avatar
8+
<UserAvatar
99
v-if="user.photoURL"
10-
:image="user.photoURL"
10+
:photo-url="user.photoURL"
1111
shape="circle"
1212
size="large"
13-
aria-label="User Avatar"
1413
/>
1514

1615
<div class="flex flex-col gap-0.5">
@@ -87,13 +86,13 @@
8786
</template>
8887

8988
<script setup lang="ts">
90-
import Avatar from 'primevue/avatar'
9189
import Button from 'primevue/button'
9290
import Divider from 'primevue/divider'
9391
import ProgressSpinner from 'primevue/progressspinner'
9492
import TabPanel from 'primevue/tabpanel'
9593
import { computed } from 'vue'
9694
95+
import UserAvatar from '@/components/common/UserAvatar.vue'
9796
import { useDialogService } from '@/services/dialogService'
9897
import { useCommandStore } from '@/stores/commandStore'
9998
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'

src/components/topbar/CurrentUserButton.vue

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,7 @@
1212
<div
1313
class="flex items-center rounded-full bg-[var(--p-content-background)]"
1414
>
15-
<Avatar
16-
:image="photoURL"
17-
:icon="photoURL ? undefined : 'pi pi-user'"
18-
shape="circle"
19-
aria-label="User Avatar"
20-
/>
15+
<UserAvatar :photo-url="photoURL" />
2116

2217
<i class="pi pi-chevron-down px-1" :style="{ fontSize: '0.5rem' }" />
2318
</div>
@@ -30,11 +25,11 @@
3025
</template>
3126

3227
<script setup lang="ts">
33-
import Avatar from 'primevue/avatar'
3428
import Button from 'primevue/button'
3529
import Popover from 'primevue/popover'
3630
import { computed, ref } from 'vue'
3731
32+
import UserAvatar from '@/components/common/UserAvatar.vue'
3833
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
3934
4035
import CurrentUserPopover from './CurrentUserPopover.vue'

src/components/topbar/CurrentUserPopover.vue

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
<!-- User Info Section -->
55
<div class="p-3">
66
<div class="flex flex-col items-center">
7-
<Avatar
7+
<UserAvatar
88
class="mb-3"
9-
:image="user?.photoURL ?? undefined"
10-
:icon="user?.photoURL ? undefined : 'pi pi-user !text-2xl'"
11-
shape="circle"
9+
:photo-url="user?.photoURL"
10+
:pt:icon:class="{
11+
'!text-2xl': !user?.photoURL
12+
}"
1213
size="large"
13-
aria-label="User Avatar"
1414
/>
1515

1616
<!-- User Details -->
@@ -50,11 +50,11 @@
5050
</template>
5151

5252
<script setup lang="ts">
53-
import Avatar from 'primevue/avatar'
5453
import Button from 'primevue/button'
5554
import Divider from 'primevue/divider'
5655
import { computed, onMounted } from 'vue'
5756
57+
import UserAvatar from '@/components/common/UserAvatar.vue'
5858
import UserCredit from '@/components/common/UserCredit.vue'
5959
import { useDialogService } from '@/services/dialogService'
6060
import { useFirebaseAuthService } from '@/services/firebaseAuthService'

src/locales/en/main.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,7 @@
12361236
"passwordResetSent": "Password reset email sent",
12371237
"passwordResetSentDetail": "Please check your email for a link to reset your password.",
12381238
"newUser": "New here?",
1239+
"userAvatar": "User Avatar",
12391240
"signUp": "Sign up",
12401241
"emailLabel": "Email",
12411242
"emailPlaceholder": "Enter your email",

src/locales/es/main.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@
5555
"success": "Inicio de sesión exitoso",
5656
"termsLink": "Términos de uso",
5757
"termsText": "Al hacer clic en \"Siguiente\" o \"Registrarse\", aceptas nuestros",
58-
"title": "Inicia sesión en tu cuenta"
58+
"title": "Inicia sesión en tu cuenta",
59+
"userAvatar": "Avatar de usuario"
5960
},
6061
"passwordUpdate": {
6162
"success": "Contraseña actualizada",

src/locales/fr/main.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@
5555
"success": "Connexion réussie",
5656
"termsLink": "Conditions d'utilisation",
5757
"termsText": "En cliquant sur \"Suivant\" ou \"S'inscrire\", vous acceptez nos",
58-
"title": "Connectez-vous à votre compte"
58+
"title": "Connectez-vous à votre compte",
59+
"userAvatar": "Avatar utilisateur"
5960
},
6061
"passwordUpdate": {
6162
"success": "Mot de passe mis à jour",

src/locales/ja/main.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@
5555
"success": "ログイン成功",
5656
"termsLink": "利用規約",
5757
"termsText": "「次へ」または「サインアップ」をクリックすると、私たちの",
58-
"title": "アカウントにログインする"
58+
"title": "アカウントにログインする",
59+
"userAvatar": "ユーザーアバター"
5960
},
6061
"passwordUpdate": {
6162
"success": "パスワードが更新されました",

src/locales/ko/main.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@
5555
"success": "로그인 성공",
5656
"termsLink": "이용 약관",
5757
"termsText": "\"다음\" 또는 \"가입하기\"를 클릭하면 우리의",
58-
"title": "계정에 로그인"
58+
"title": "계정에 로그인",
59+
"userAvatar": "사용자 아바타"
5960
},
6061
"passwordUpdate": {
6162
"success": "비밀번호가 업데이트되었습니다",

0 commit comments

Comments
 (0)