diff --git a/packages/design-system/src/components/OcRecipient/OcRecipient.spec.ts b/packages/design-system/src/components/OcRecipient/OcRecipient.spec.ts index 49dffeb6ab..9cfb67ba02 100644 --- a/packages/design-system/src/components/OcRecipient/OcRecipient.spec.ts +++ b/packages/design-system/src/components/OcRecipient/OcRecipient.spec.ts @@ -3,8 +3,18 @@ import Recipient from './OcRecipient.vue' import { Recipient as RecipientType } from '../../helpers' describe('OcRecipient', () => { - function getWrapper(props: Partial = undefined, slot: string = undefined) { - const slots = slot ? { append: slot } : {} + function getWrapper( + props: Partial = undefined, + avatarSlot: string = undefined, + appendSlot: string = undefined + ) { + const slots: { append?: string; avatar?: string } = {} + if (appendSlot) { + slots.append = appendSlot + } + if (avatarSlot) { + slots.avatar = avatarSlot + } return shallowMount(Recipient, { props: { @@ -12,7 +22,7 @@ describe('OcRecipient', () => { name: 'alice', avatar: 'avatar.jpg', hasAvatar: true, - isLoadingAvatar: false, + icon: { name: 'user', label: 'User' }, ...props } }, @@ -30,35 +40,18 @@ describe('OcRecipient', () => { it('displays avatar', () => { const wrapper = getWrapper() - expect(wrapper.find('[data-testid="recipient-avatar"]').attributes('src')).toEqual('avatar.jpg') + expect(wrapper.find('oc-avatar-item-stub').attributes('icon')).toEqual('user') }) - it('displays a spinner if avatar has not been loaded yet', () => { - const wrapper = getWrapper({ - isLoadingAvatar: true - }) - - expect(wrapper.find('[data-testid="recipient-avatar-spinner"]').exists()).toBeTruthy() - }) - - it('displays an icon if avatar is not enabled', () => { - const wrapper = getWrapper({ - icon: { - name: 'person', - label: 'User' - }, - hasAvatar: false - }) - - const icon = wrapper.find('[data-testid="recipient-icon"]') + it('display content in the avatar slot', () => { + const wrapper = getWrapper({}, 'Hello world') - expect(icon.exists()).toBeTruthy() - expect(icon.attributes().accessiblelabel).toEqual('User') + expect(wrapper.find('#avatar-slot').exists()).toBeTruthy() }) it('display content in the append slot', () => { - const wrapper = getWrapper({}, 'Hello world') + const wrapper = getWrapper({}, '', ' Hello world ') - expect(wrapper.find('#test-slot').exists()).toBeTruthy() + expect(wrapper.find('#append-slot').exists()).toBeTruthy() }) }) diff --git a/packages/design-system/src/components/OcRecipient/OcRecipient.vue b/packages/design-system/src/components/OcRecipient/OcRecipient.vue index 1e7d65f80b..829047d12c 100644 --- a/packages/design-system/src/components/OcRecipient/OcRecipient.vue +++ b/packages/design-system/src/components/OcRecipient/OcRecipient.vue @@ -1,31 +1,14 @@ diff --git a/packages/web-pkg/src/components/Avatars/index.ts b/packages/web-pkg/src/components/Avatars/index.ts new file mode 100644 index 0000000000..325a4c05f5 --- /dev/null +++ b/packages/web-pkg/src/components/Avatars/index.ts @@ -0,0 +1,2 @@ +export { default as UserAvatar } from './UserAvatar.vue' +export { default as AvatarUpload } from './AvatarUpload.vue' diff --git a/packages/web-pkg/src/components/index.ts b/packages/web-pkg/src/components/index.ts index 59cf904ff0..2132389b9a 100644 --- a/packages/web-pkg/src/components/index.ts +++ b/packages/web-pkg/src/components/index.ts @@ -8,6 +8,7 @@ export * from './SideBar' export * from './Search' export * from './Spaces' export * from './TextEditor' +export * from './Avatars' export { default as AppLoadingSpinner } from './AppLoadingSpinner.vue' export { default as AppTopBar } from './AppTopBar.vue' @@ -26,4 +27,3 @@ export { default as ViewOptions } from './ViewOptions.vue' export { default as PortalTarget } from './PortalTarget.vue' export { default as CreateShortcutModal } from './CreateShortcutModal.vue' export { default as CreateLinkModal } from './CreateLinkModal.vue' -export { default as AvatarUpload } from './AvatarUpload.vue' diff --git a/packages/web-pkg/src/composables/piniaStores/avatars.ts b/packages/web-pkg/src/composables/piniaStores/avatars.ts index 823edea86f..d269c191b0 100644 --- a/packages/web-pkg/src/composables/piniaStores/avatars.ts +++ b/packages/web-pkg/src/composables/piniaStores/avatars.ts @@ -1,16 +1,11 @@ -import { defineStore, storeToRefs } from 'pinia' +import { defineStore } from 'pinia' import { ref, unref } from 'vue' -import { useUserStore } from './user' export const useAvatarsStore = defineStore('avatars', () => { const avatarMap = ref>({}) - const userAvatar = ref() - - const userStore = useUserStore() - const { user } = storeToRefs(userStore) const addAvatar = (userId: string, avatar: string) => { - avatar[userId] = avatar + avatarMap.value[userId] = avatar } const getAvatar = (userId: string) => { @@ -18,32 +13,18 @@ export const useAvatarsStore = defineStore('avatars', () => { } const removeAvatar = (userId: string) => { - unref(avatarMap)[userId] = null - } - - const setUserAvatar = (avatar: string) => { - userAvatar.value = avatar - avatarMap[unref(user).id] = avatar - } - - const removeUserAvatar = () => { - userAvatar.value = null - avatarMap.value[unref(user).id] = null + avatarMap.value[userId] = null } const reset = () => { avatarMap.value = {} - userAvatar.value = null } return { - userAvatar, avatarMap, getAvatar, addAvatar, removeAvatar, - setUserAvatar, - removeUserAvatar, reset } }) diff --git a/packages/web-pkg/tests/unit/components/AvatarUpload.spec.ts b/packages/web-pkg/tests/unit/components/Avatars/AvatarUpload.spec.ts similarity index 95% rename from packages/web-pkg/tests/unit/components/AvatarUpload.spec.ts rename to packages/web-pkg/tests/unit/components/Avatars/AvatarUpload.spec.ts index 4ea924d95b..ca2ed5efc4 100644 --- a/packages/web-pkg/tests/unit/components/AvatarUpload.spec.ts +++ b/packages/web-pkg/tests/unit/components/Avatars/AvatarUpload.spec.ts @@ -1,4 +1,4 @@ -import AvatarUpload from '../../../src/components/AvatarUpload.vue' +import AvatarUpload from '../../../../src/components/Avatars/AvatarUpload.vue' import { createMockFile, defaultComponentMocks, @@ -6,7 +6,7 @@ import { mount, nextTicks } from '@opencloud-eu/web-test-helpers' -import { useMessages } from '../../../src' +import { useMessages } from '../../../../src' import { describe } from 'vitest' vi.mock('cropperjs', () => { @@ -163,7 +163,7 @@ const getWrapper = ({ userHasAvatar = true } = {}) => { ...defaultPlugins({ piniaOptions: { avatarsStore: { - userAvatar: userHasAvatar ? 'https://localhost:9201/some-object-url' : null + avatarMap: userHasAvatar ? { '1': 'https://localhost:9201/some-object-url' } : {} } } }) diff --git a/packages/web-pkg/tests/unit/components/Avatars/UserAvatar.spec.ts b/packages/web-pkg/tests/unit/components/Avatars/UserAvatar.spec.ts new file mode 100644 index 0000000000..925d183158 --- /dev/null +++ b/packages/web-pkg/tests/unit/components/Avatars/UserAvatar.spec.ts @@ -0,0 +1,59 @@ +import UserAvatar from '../../../../src/components/Avatars/UserAvatar.vue' +import { + defaultComponentMocks, + defaultPlugins, + mount, + nextTicks +} from '@opencloud-eu/web-test-helpers' +import { ClientService } from '../../../../src' +import { mock, mockDeep } from 'vitest-mock-extended' + +const blobUrl = 'blob:http://localhost:9200/12345678-1234-1234-1234-123456789012' + +describe('UserAvatar', () => { + window.URL.createObjectURL = vi.fn(() => blobUrl) + + it('should show an image when set', async () => { + const clientService = mockDeep() + clientService.graphAuthenticated.photos.getUserPhoto.mockResolvedValueOnce(mock()) + const { wrapper } = getWrapper({ clientService }) + + await nextTicks(2) + + expect(wrapper.find('img').attributes().src).toEqual(blobUrl) + expect(wrapper.find('.avatar-initials').exists()).toBe(false) + }) + + it('should show initials when image not set', async () => { + const clientService = mockDeep() + clientService.graphAuthenticated.photos.getUserPhoto.mockRejectedValueOnce(new Error('')) + const { wrapper } = getWrapper({ clientService }) + + await nextTicks(2) + + expect(wrapper.find('.avatar-initials').exists()).toBe(true) + expect(wrapper.find('img').exists()).toBe(false) + }) +}) + +const getWrapper = ({ clientService }: { clientService?: ClientService } = {}) => { + const mocks = { + ...defaultComponentMocks(), + $clientService: clientService + } + + return { + mocks, + wrapper: mount(UserAvatar, { + props: { + userId: '1', + userName: 'bär houdini' + }, + global: { + plugins: [...defaultPlugins({ piniaOptions: { stubActions: false } })], + mocks, + provide: mocks + } + }) + } +} diff --git a/packages/web-runtime/src/components/Topbar/Notifications.vue b/packages/web-runtime/src/components/Topbar/Notifications.vue index 55a8ac3202..88548049aa 100644 --- a/packages/web-runtime/src/components/Topbar/Notifications.vue +++ b/packages/web-runtime/src/components/Topbar/Notifications.vue @@ -39,10 +39,9 @@ class="oc-flex oc-flex-middle oc-my-xs" :to="el.computedLink" > -
@@ -84,7 +83,8 @@ import { createFileRouteOptions, formatDateFromISO, formatRelativeDateFromISO, - useClientService + useClientService, + UserAvatar } from '@opencloud-eu/web-pkg' import NotificationBell from './NotificationBell.vue' import { Notification } from '../../helpers/notifications' @@ -98,6 +98,7 @@ const POLLING_INTERVAL = 30000 export default { components: { + UserAvatar, NotificationBell }, setup() { diff --git a/packages/web-runtime/src/components/Topbar/UserMenu.vue b/packages/web-runtime/src/components/Topbar/UserMenu.vue index 3e11ef78cb..dcd40f9f7d 100644 --- a/packages/web-runtime/src/components/Topbar/UserMenu.vue +++ b/packages/web-runtime/src/components/Topbar/UserMenu.vue @@ -9,15 +9,13 @@ no-hover :aria-label="$gettext('My Account')" > -