Commit f21a1107 authored by Olena Horal-Koretska's avatar Olena Horal-Koretska

Merge branch '333042-add-pronouns-to-snapshot-view-of-gitlab-user-profile' into 'master'

Display gender pronouns in user popover

See merge request gitlab-org/gitlab!63611
parents 0ec28f1b 9f022f4a
......@@ -22,8 +22,16 @@ export default {
required: false,
default: '',
},
pronouns: {
type: String,
required: false,
default: '',
},
},
computed: {
hasPronouns() {
return this.pronouns !== null && this.pronouns.trim() !== '';
},
isBusy() {
return isUserBusy(this.availability);
},
......@@ -32,9 +40,18 @@ export default {
</script>
<template>
<span :class="containerClasses">
<gl-sprintf v-if="isBusy" :message="s__('UserAvailability|%{author} (Busy)')">
<template #author>{{ name }}</template>
<gl-sprintf :message="s__('UserAvailability|%{author} %{spanStart}(Busy)%{spanEnd}')">
<template #author
>{{ name }}
<span v-if="hasPronouns" class="gl-text-gray-500 gl-font-sm gl-font-weight-normal"
>({{ pronouns }})</span
></template
>
<template #span="{ content }"
><span v-if="isBusy" class="gl-text-gray-500 gl-font-sm gl-font-weight-normal">{{
content
}}</span>
</template>
</gl-sprintf>
<template v-else>{{ name }}</template>
</span>
</template>
......@@ -44,6 +44,7 @@ const populateUserInfo = (user) => {
bioHtml: sanitize(userData.bio_html),
workInformation: userData.work_information,
websiteUrl: userData.website_url,
pronouns: userData.pronouns,
loaded: true,
});
}
......
......@@ -72,7 +72,11 @@ export default {
<template v-else>
<div class="gl-mb-3">
<h5 class="gl-m-0">
<user-name-with-status :name="user.name" :availability="availabilityStatus" />
<user-name-with-status
:name="user.name"
:availability="availabilityStatus"
:pronouns="user.pronouns"
/>
</h5>
<span class="gl-text-gray-500">@{{ user.username }}</span>
</div>
......
......@@ -37,6 +37,9 @@ exports[`Event Item with action buttons renders the action buttons 1`] = `
class="note-header-author-name gl-font-weight-bold"
>
Tanuki
<!---->
</span>
</a>
......
......@@ -5,7 +5,7 @@ module API
class User < UserBasic
include UsersHelper
expose :created_at, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) }
expose :bio, :bio_html, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title
expose :bio, :bio_html, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title, :pronouns
expose :bot?, as: :bot
expose :work_information do |user|
work_information(user)
......
......@@ -35577,6 +35577,9 @@ msgstr ""
msgid "User was successfully updated."
msgstr ""
msgid "UserAvailability|%{author} %{spanStart}(Busy)%{spanEnd}"
msgstr ""
msgid "UserAvailability|%{author} (Busy)"
msgstr ""
......
......@@ -5,9 +5,9 @@ require 'spec_helper'
RSpec.describe 'User sees user popover', :js do
include Spec::Support::Helpers::Features::NotesHelpers
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user, pronouns: 'they/them') }
let_it_be(:project) { create(:project, :repository, creator: user) }
let(:user) { project.creator }
let(:merge_request) do
create(:merge_request, source_project: project, target_project: project)
end
......@@ -32,7 +32,7 @@ RSpec.describe 'User sees user popover', :js do
expect(page).to have_css(popover_selector, visible: true)
page.within(popover_selector) do
expect(page).to have_content(user.name)
expect(page).to have_content("#{user.name} (they/them)")
end
end
......
import { shallowMount } from '@vue/test-utils';
import { mount } from '@vue/test-utils';
import AssigneeAvatar from '~/sidebar/components/assignees/assignee_avatar.vue';
import CollapsedAssignee from '~/sidebar/components/assignees/collapsed_assignee.vue';
import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
import userDataMock from '../../user_data_mock';
const TEST_USER = userDataMock();
......@@ -17,11 +16,8 @@ describe('CollapsedAssignee assignee component', () => {
...props,
};
wrapper = shallowMount(CollapsedAssignee, {
wrapper = mount(CollapsedAssignee, {
propsData,
stubs: {
UserNameWithStatus,
},
});
}
......
import { GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { mount } from '@vue/test-utils';
import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
const name = 'Goku';
const name = 'Administrator';
const containerClasses = 'gl-cool-class gl-over-9000';
describe('UserNameWithStatus', () => {
let wrapper;
function createComponent(props = {}) {
return shallowMount(UserNameWithStatus, {
wrapper = mount(UserNameWithStatus, {
propsData: { name, containerClasses, ...props },
stubs: {
GlSprintf,
},
});
}
beforeEach(() => {
wrapper = createComponent();
createComponent();
});
afterEach(() => {
......@@ -41,11 +37,39 @@ describe('UserNameWithStatus', () => {
describe(`with availability="${AVAILABILITY_STATUS.BUSY}"`, () => {
beforeEach(() => {
wrapper = createComponent({ availability: AVAILABILITY_STATUS.BUSY });
createComponent({ availability: AVAILABILITY_STATUS.BUSY });
});
it('will render "Busy"', () => {
expect(wrapper.html()).toContain('Goku (Busy)');
expect(wrapper.text()).toContain('(Busy)');
});
});
describe('when user has pronouns set', () => {
const pronouns = 'they/them';
beforeEach(() => {
createComponent({ pronouns });
});
it("renders user's name with pronouns", () => {
expect(wrapper.text()).toMatchInterpolatedText(`${name} (${pronouns})`);
});
});
describe('when user does not have pronouns set', () => {
describe.each`
pronouns
${undefined}
${null}
${''}
${' '}
`('when `pronouns` prop is $pronouns', ({ pronouns }) => {
it("renders only the user's name", () => {
createComponent({ pronouns });
expect(wrapper.text()).toMatchInterpolatedText(name);
});
});
});
});
import { GlSkeletonLoader, GlSprintf, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { GlSkeletonLoader, GlIcon } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
import UserPopover from '~/vue_shared/components/user_popover/user_popover.vue';
......@@ -13,6 +13,7 @@ const DEFAULT_PROPS = {
bio: null,
workInformation: null,
status: null,
pronouns: 'they/them',
loaded: true,
},
};
......@@ -30,23 +31,18 @@ describe('User Popover Component', () => {
wrapper.destroy();
});
const findByTestId = (testid) => wrapper.find(`[data-testid="${testid}"]`);
const findUserStatus = () => wrapper.find('.js-user-status');
const findTarget = () => document.querySelector('.js-user-link');
const findUserName = () => wrapper.find(UserNameWithStatus);
const findSecurityBotDocsLink = () => findByTestId('user-popover-bot-docs-link');
const findSecurityBotDocsLink = () => wrapper.findByTestId('user-popover-bot-docs-link');
const createWrapper = (props = {}, options = {}) => {
wrapper = shallowMount(UserPopover, {
wrapper = mountExtended(UserPopover, {
propsData: {
...DEFAULT_PROPS,
target: findTarget(),
...props,
},
stubs: {
GlSprintf,
UserNameWithStatus,
},
...options,
});
};
......@@ -232,6 +228,12 @@ describe('User Popover Component', () => {
expect(wrapper.text()).not.toContain('(Busy)');
});
it('passes `pronouns` prop to `UserNameWithStatus` component', () => {
createWrapper();
expect(findUserName().props('pronouns')).toBe('they/them');
});
});
describe('bot user', () => {
......
......@@ -9,7 +9,7 @@ RSpec.describe API::Entities::User do
subject { described_class.new(user, current_user: current_user).as_json }
it 'exposes correct attributes' do
expect(subject).to include(:bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title, :work_information)
expect(subject).to include(:bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title, :work_information, :pronouns)
end
it 'exposes created_at if the current user can read the user profile' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment