mirror of
https://github.com/Ocelot-Social-Community/Ocelot-Social.git
synced 2025-12-13 07:46:06 +00:00
Support use of initials, add tests, move component
- if there is no user.avatar, we show a user's initials - up to 3
characters unless there is no name or the name is 'Anonymous'. This is
to support users who on the old alpha were allowed to be anonymous (do
we still want to support this?)
- Add test cases for ☝️
- Refactor to not use any styleguide components and move UserAvatar to
generic directory
This commit is contained in:
parent
068e2b4417
commit
905f34c827
@ -11,7 +11,7 @@
|
||||
"
|
||||
@click.prevent="toggleMenu"
|
||||
>
|
||||
<user-avatar :image="user && user.avatar" />
|
||||
<user-avatar :user="user" />
|
||||
<base-icon class="dropdown-arrow" name="angle-down" />
|
||||
</a>
|
||||
</template>
|
||||
@ -49,7 +49,7 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
import UserAvatar from '~/components/UserAvatar/UserAvatar.vue'
|
||||
import UserAvatar from '~/components/_new/generic/UserAvatar/UserAvatar'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
<template slot="default" slot-scope="{ openMenu, closeMenu, isOpen }">
|
||||
<nuxt-link :to="userLink" :class="['user', isOpen && 'active']">
|
||||
<div @mouseover="showPopover ? openMenu(true) : () => {}" @mouseleave="closeMenu(true)">
|
||||
<user-avatar v-if="showAvatar" class="avatar" :image="user && user.avatar" />
|
||||
<user-avatar v-if="showAvatar" class="avatar" :user="user" />
|
||||
<div>
|
||||
<ds-text class="userinfo">
|
||||
<b>{{ userSlug }}</b>
|
||||
@ -89,7 +89,7 @@ import { mapGetters } from 'vuex'
|
||||
import HcRelativeDateTime from '~/components/RelativeDateTime'
|
||||
import HcFollowButton from '~/components/FollowButton'
|
||||
import HcBadges from '~/components/Badges'
|
||||
import UserAvatar from '~/components/UserAvatar/UserAvatar.vue'
|
||||
import UserAvatar from '~/components/_new/generic/UserAvatar/UserAvatar'
|
||||
import Dropdown from '~/components/Dropdown'
|
||||
|
||||
export default {
|
||||
|
||||
@ -1,68 +0,0 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Avatar from './Avatar.vue'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('Avatar.vue', () => {
|
||||
let propsData = {}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(Avatar, { propsData, localVue })
|
||||
}
|
||||
|
||||
it('renders no image', () => {
|
||||
expect(
|
||||
Wrapper()
|
||||
.find('img')
|
||||
.exists(),
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
// this is testing the style guide
|
||||
it('renders an icon', () => {
|
||||
expect(
|
||||
Wrapper()
|
||||
.find('.ds-icon')
|
||||
.exists(),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
describe('given a user', () => {
|
||||
describe('with a relative avatar url', () => {
|
||||
beforeEach(() => {
|
||||
propsData = {
|
||||
user: {
|
||||
avatar: '/avatar.jpg',
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
it('adds a prefix to load the image from the uploads service', () => {
|
||||
expect(
|
||||
Wrapper()
|
||||
.find('img')
|
||||
.attributes('src'),
|
||||
).toBe('/api/avatar.jpg')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with an absolute avatar url', () => {
|
||||
beforeEach(() => {
|
||||
propsData = {
|
||||
user: {
|
||||
avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/sawalazar/128.jpg',
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps the avatar URL as is', () => {
|
||||
// e.g. our seeds have absolute image URLs
|
||||
expect(
|
||||
Wrapper()
|
||||
.find('img')
|
||||
.attributes('src'),
|
||||
).toBe('https://s3.amazonaws.com/uifaces/faces/twitter/sawalazar/128.jpg')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
98
webapp/components/_new/generic/UserAvatar/UserAvatar.spec.js
Normal file
98
webapp/components/_new/generic/UserAvatar/UserAvatar.spec.js
Normal file
@ -0,0 +1,98 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import UserAvatar from './UserAvatar.vue'
|
||||
import BaseIcon from '~/components/_new/generic/BaseIcon/BaseIcon'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('UserAvatar.vue', () => {
|
||||
let propsData, wrapper
|
||||
beforeEach(() => {
|
||||
propsData = {}
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(UserAvatar, { propsData, localVue })
|
||||
}
|
||||
|
||||
it('renders no image', () => {
|
||||
expect(wrapper.find('img').exists()).toBe(false)
|
||||
})
|
||||
|
||||
// this is testing the style guide
|
||||
it('renders an icon', () => {
|
||||
expect(wrapper.find(BaseIcon).exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('given a user', () => {
|
||||
describe('with no image', () => {
|
||||
beforeEach(() => {
|
||||
propsData = {
|
||||
user: {
|
||||
name: 'Matt Rider',
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
describe('no user name', () => {
|
||||
it('renders an icon', () => {
|
||||
propsData = { user: { name: null } }
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find(BaseIcon).exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("user name is 'Anonymous'", () => {
|
||||
it('renders an icon', () => {
|
||||
propsData = { user: { name: 'Anonymous' } }
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find(BaseIcon).exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it('displays user initials', () => {
|
||||
expect(wrapper.find('.no-image').text()).toEqual('MR')
|
||||
})
|
||||
|
||||
it('displays no more than 3 initials', () => {
|
||||
propsData = { user: { name: 'Ana Paula Nunes Marques' } }
|
||||
wrapper = Wrapper()
|
||||
expect(wrapper.find('.no-image').text()).toEqual('APN')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with a relative avatar url', () => {
|
||||
beforeEach(() => {
|
||||
propsData = {
|
||||
user: {
|
||||
avatar: '/avatar.jpg',
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('adds a prefix to load the image from the uploads service', () => {
|
||||
expect(wrapper.find('img').attributes('src')).toBe('/api/avatar.jpg')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with an absolute avatar url', () => {
|
||||
beforeEach(() => {
|
||||
propsData = {
|
||||
user: {
|
||||
avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/sawalazar/128.jpg',
|
||||
},
|
||||
}
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('keeps the avatar URL as is', () => {
|
||||
// e.g. our seeds have absolute image URLs
|
||||
expect(wrapper.find('img').attributes('src')).toBe(
|
||||
'https://s3.amazonaws.com/uifaces/faces/twitter/sawalazar/128.jpg',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<div :class="[`size-${this.size}`, 'user-avatar']">
|
||||
<ds-flex v-if="!hasImage || error" style="height: 100%">
|
||||
<ds-flex-item centered>
|
||||
<template v-if="isAnonymus">
|
||||
<span v-if="!hasImage || error" class="no-image">
|
||||
<span class="flex-item">
|
||||
<template v-if="isAnonymous">
|
||||
<base-icon name="eye-slash" />
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ userInitials }}
|
||||
</template>
|
||||
</ds-flex-item>
|
||||
</ds-flex>
|
||||
<img v-if="image && !error" :src="image | proxyApiUrl" @error="onError" />
|
||||
</span>
|
||||
</span>
|
||||
<img v-if="user && user.avatar && !error" :src="user.avatar | proxyApiUrl" @error="onError" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -18,11 +18,6 @@
|
||||
export default {
|
||||
name: 'UserAvatar',
|
||||
props: {
|
||||
name: { type: String, default: 'Anonymus' },
|
||||
/**
|
||||
* The size used for the avatar.
|
||||
* @options small|base|large
|
||||
*/
|
||||
size: {
|
||||
type: String,
|
||||
default: 'base',
|
||||
@ -30,7 +25,7 @@ export default {
|
||||
return value.match(/(small|base|large|x-large)/)
|
||||
},
|
||||
},
|
||||
image: { type: String, default: null },
|
||||
user: { type: Object, default: null },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -38,11 +33,19 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isAnonymus() {
|
||||
return !this.name || this.name.toLowerCase() === 'anonymus'
|
||||
isAnonymous() {
|
||||
return !this.user || !this.user.name || this.user.name.toLowerCase() === 'anonymous'
|
||||
},
|
||||
hasImage() {
|
||||
return Boolean(this.image) && !this.error
|
||||
return Boolean(this.user && this.user.avatar) && !this.error
|
||||
},
|
||||
userInitials() {
|
||||
const { name } = this.user || 'Anonymous'
|
||||
const namesArray = name.split(/[ -]/)
|
||||
let initials = ''
|
||||
for (var i = 0; i < namesArray.length; i++) initials += namesArray[i].charAt(0)
|
||||
if (initials.length > 3 && /[A-Z]/.test(initials)) initials = initials.replace(/[a-z]+/g, '')
|
||||
return initials.substr(0, 3).toUpperCase()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@ -56,7 +59,6 @@ export default {
|
||||
.user-avatar {
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
object-fit: cover;
|
||||
@ -84,5 +86,21 @@ export default {
|
||||
width: $size-avatar-x-large;
|
||||
height: $size-avatar-x-large;
|
||||
}
|
||||
.no-image {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
border-radius: 50%;
|
||||
background-color: $background-color-secondary;
|
||||
color: $text-color-primary-inverse;
|
||||
}
|
||||
|
||||
.no-image .flex-item {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0 auto;
|
||||
align-self: center;
|
||||
display: table;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -11,13 +11,9 @@
|
||||
style="position: relative; height: auto;"
|
||||
>
|
||||
<hc-upload v-if="myProfile" :user="user">
|
||||
<user-avatar
|
||||
:image="user && user.avatar"
|
||||
class="profile-avatar"
|
||||
size="x-large"
|
||||
></user-avatar>
|
||||
<user-avatar :user="user" class="profile-avatar" size="x-large"></user-avatar>
|
||||
</hc-upload>
|
||||
<user-avatar v-else :image="user && user.avatar" class="profile-avatar" size="x-large" />
|
||||
<user-avatar v-else :user="user" class="profile-avatar" size="x-large" />
|
||||
<!-- Menu -->
|
||||
<client-only>
|
||||
<content-menu
|
||||
@ -283,7 +279,7 @@ import HcBadges from '~/components/Badges.vue'
|
||||
import HcEmpty from '~/components/Empty/Empty'
|
||||
import ContentMenu from '~/components/ContentMenu/ContentMenu'
|
||||
import HcUpload from '~/components/Upload'
|
||||
import UserAvatar from '~/components/UserAvatar/UserAvatar.vue'
|
||||
import UserAvatar from '~/components/_new/generic/UserAvatar/UserAvatar'
|
||||
import MasonryGrid from '~/components/MasonryGrid/MasonryGrid.vue'
|
||||
import MasonryGridItem from '~/components/MasonryGrid/MasonryGridItem.vue'
|
||||
import { profilePagePosts } from '~/graphql/PostQuery'
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
params: { id: scope.row.id, slug: scope.row.slug },
|
||||
}"
|
||||
>
|
||||
<user-avatar :image="scope.row && scope.row.avatar" size="small" />
|
||||
<user-avatar :user="scope.row" size="small" />
|
||||
</nuxt-link>
|
||||
</template>
|
||||
<template slot="name" slot-scope="scope">
|
||||
@ -79,7 +79,7 @@
|
||||
|
||||
<script>
|
||||
import { BlockedUsers, Unblock } from '~/graphql/settings/BlockedUsers'
|
||||
import UserAvatar from '~/components/UserAvatar/UserAvatar.vue'
|
||||
import UserAvatar from '~/components/_new/generic/UserAvatar/UserAvatar'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user