Коммит f99409d2 создал по автору Peter Hegman's avatar Peter Hegman Зафиксировано автором David Pisek
Просмотр файлов

Add clear status after dropdown to profile set status form

Also refactors to Vue

Changelog: added
владелец 13d56e85
import '~/commons/bootstrap';
import { AwardsHandler } from '~/awards_handler';
class EmojiMenu extends AwardsHandler {
constructor(emoji, toggleButtonSelector, menuClass, selectEmojiCallback) {
super(emoji);
this.selectEmojiCallback = selectEmojiCallback;
this.toggleButtonSelector = toggleButtonSelector;
this.menuClass = menuClass;
}
postEmoji($emojiButton, awardUrl, selectedEmoji, callback) {
this.selectEmojiCallback(selectedEmoji, this.emoji.glEmojiTag(selectedEmoji));
callback();
}
}
export default EmojiMenu;
import emojiRegex from 'emoji-regex';
import $ from 'jquery';
import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
import * as Emoji from '~/emoji';
import createFlash from '~/flash';
import { __ } from '~/locale';
import EmojiMenu from './emoji_menu';
import { initSetStatusForm } from '~/profile/profile';
const defaultStatusEmoji = 'speech_balloon';
const toggleEmojiMenuButtonSelector = '.js-toggle-emoji-menu';
const toggleEmojiMenuButton = document.querySelector(toggleEmojiMenuButtonSelector);
const statusEmojiField = document.getElementById('js-status-emoji-field');
const statusMessageField = document.getElementById('js-status-message-field');
const toggleNoEmojiPlaceholder = (isVisible) => {
const placeholderElement = document.getElementById('js-no-emoji-placeholder');
placeholderElement.classList.toggle('hidden', !isVisible);
};
const findStatusEmoji = () => toggleEmojiMenuButton.querySelector('gl-emoji');
const removeStatusEmoji = () => {
const statusEmoji = findStatusEmoji();
if (statusEmoji) {
statusEmoji.remove();
}
};
const selectEmojiCallback = (emoji, emojiTag) => {
statusEmojiField.value = emoji;
toggleNoEmojiPlaceholder(false);
removeStatusEmoji();
// eslint-disable-next-line no-unsanitized/property
toggleEmojiMenuButton.innerHTML += emojiTag;
};
const clearEmojiButton = document.getElementById('js-clear-user-status-button');
clearEmojiButton.addEventListener('click', () => {
statusEmojiField.value = '';
statusMessageField.value = '';
removeStatusEmoji();
toggleNoEmojiPlaceholder(true);
});
const emojiAutocomplete = new GfmAutoComplete();
emojiAutocomplete.setup($(statusMessageField), { emojis: true });
initSetStatusForm();
const userNameInput = document.getElementById('user_name');
userNameInput.addEventListener('input', () => {
const EMOJI_REGEX = emojiRegex();
if (EMOJI_REGEX.test(userNameInput.value)) {
// set field to invalid so it gets detected by GlFieldErrors
userNameInput.setCustomValidity(__('Invalid field'));
} else {
userNameInput.setCustomValidity('');
}
});
Emoji.initEmojiMap()
.then(() => {
const emojiMenu = new EmojiMenu(
Emoji,
toggleEmojiMenuButtonSelector,
'js-status-emoji-menu',
selectEmojiCallback,
);
emojiMenu.bindEvents();
const defaultEmojiTag = Emoji.glEmojiTag(defaultStatusEmoji);
statusMessageField.addEventListener('input', () => {
const hasStatusMessage = statusMessageField.value.trim() !== '';
const statusEmoji = findStatusEmoji();
if (hasStatusMessage && statusEmoji) {
return;
}
if (hasStatusMessage) {
toggleNoEmojiPlaceholder(false);
// eslint-disable-next-line no-unsanitized/property
toggleEmojiMenuButton.innerHTML += defaultEmojiTag;
} else if (statusEmoji.dataset.name === defaultStatusEmoji) {
toggleNoEmojiPlaceholder(true);
removeStatusEmoji();
}
});
})
.catch(() =>
createFlash({
message: __('Failed to load emoji list.'),
}),
);
if (userNameInput) {
userNameInput.addEventListener('input', () => {
const EMOJI_REGEX = emojiRegex();
if (EMOJI_REGEX.test(userNameInput.value)) {
// set field to invalid so it gets detected by GlFieldErrors
userNameInput.setCustomValidity(__('Invalid field'));
} else {
userNameInput.setCustomValidity('');
}
});
}
import $ from 'jquery';
import Vue from 'vue';
import { VARIANT_DANGER, VARIANT_INFO, createAlert } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { parseBoolean } from '~/lib/utils/common_utils';
import { parseRailsFormFields } from '~/lib/utils/forms';
import { Rails } from '~/lib/utils/rails_ujs';
import TimezoneDropdown, {
formatTimezone,
} from '~/pages/projects/pipeline_schedules/shared/components/timezone_dropdown';
import UserProfileSetStatusWrapper from '~/set_status_modal/user_profile_set_status_wrapper.vue';
export default class Profile {
constructor({ form } = {}) {
......@@ -116,3 +119,24 @@ export default class Profile {
}
}
}
export const initSetStatusForm = () => {
const el = document.getElementById('js-user-profile-set-status-form');
if (!el) {
return null;
}
const fields = parseRailsFormFields(el);
return new Vue({
el,
name: 'UserProfileStatusForm',
provide: {
fields,
},
render(h) {
return h(UserProfileSetStatusWrapper);
},
});
};
import { timeRanges } from '~/vue_shared/constants';
import { __ } from '~/locale';
export const NEVER_TIME_RANGE = {
label: __('Never'),
name: 'never',
};
export const TIME_RANGES_WITH_NEVER = [NEVER_TIME_RANGE, ...timeRanges];
export const AVAILABILITY_STATUS = {
BUSY: 'busy',
NOT_SET: 'not_set',
};
......@@ -9,26 +9,14 @@ import {
GlDropdown,
GlDropdownItem,
GlSprintf,
GlFormGroup,
GlSafeHtmlDirective,
} from '@gitlab/ui';
import $ from 'jquery';
import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
import * as Emoji from '~/emoji';
import { __, s__ } from '~/locale';
import { timeRanges } from '~/vue_shared/constants';
export const AVAILABILITY_STATUS = {
BUSY: 'busy',
NOT_SET: 'not_set',
};
const statusTimeRanges = [
{
label: __('Never'),
name: 'never',
},
...timeRanges,
];
import { s__ } from '~/locale';
import { TIME_RANGES_WITH_NEVER, AVAILABILITY_STATUS } from './constants';
export default {
components: {
......@@ -40,6 +28,7 @@ export default {
GlDropdown,
GlDropdownItem,
GlSprintf,
GlFormGroup,
EmojiPicker: () => import('~/emoji/components/picker.vue'),
},
directives: {
......@@ -136,7 +125,8 @@ export default {
this.clearEmoji();
},
},
statusTimeRanges,
TIME_RANGES_WITH_NEVER,
AVAILABILITY_STATUS,
safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
i18n: {
statusMessagePlaceholder: s__(`SetStatusModal|What's your status?`),
......@@ -153,14 +143,11 @@ export default {
<template>
<div>
<input :value="emoji" class="js-status-emoji-field" type="hidden" name="user[status][emoji]" />
<gl-form-input-group class="gl-mb-5">
<gl-form-input
ref="statusMessageField"
:value="message"
:placeholder="$options.i18n.statusMessagePlaceholder"
class="js-status-message-field"
name="user[status][message]"
@keyup="setDefaultEmoji"
@input="$emit('message-input', $event)"
@keyup.enter.prevent
......@@ -216,28 +203,29 @@ export default {
</template>
</gl-form-checkbox>
<div class="form-group">
<div class="gl-display-flex gl-align-items-baseline">
<span class="gl-mr-3">{{ $options.i18n.clearStatusAfterDropdownLabel }}</span>
<gl-dropdown :text="clearStatusAfter.label" data-testid="clear-status-at-dropdown">
<gl-dropdown-item
v-for="after in $options.statusTimeRanges"
:key="after.name"
:data-testid="after.name"
@click="$emit('clear-status-after-click', after)"
>{{ after.label }}</gl-dropdown-item
>
</gl-dropdown>
</div>
<p
v-if="currentClearStatusAfter.length"
class="gl-mt-3 gl-text-gray-400 gl-font-sm"
data-testid="clear-status-at-message"
<gl-form-group :label="$options.i18n.clearStatusAfterDropdownLabel" class="gl-mb-0">
<gl-dropdown
block
:text="clearStatusAfter.label"
data-testid="clear-status-at-dropdown"
toggle-class="gl-mb-0 gl-form-input-md"
>
<gl-sprintf :message="$options.i18n.clearStatusAfterMessage">
<template #date>{{ currentClearStatusAfter }}</template>
</gl-sprintf>
</p>
</div>
<gl-dropdown-item
v-for="after in $options.TIME_RANGES_WITH_NEVER"
:key="after.name"
:data-testid="after.name"
@click="$emit('clear-status-after-click', after)"
>{{ after.label }}</gl-dropdown-item
>
</gl-dropdown>
<template v-if="currentClearStatusAfter.length" #description>
<span data-testid="clear-status-at-message">
<gl-sprintf :message="$options.i18n.clearStatusAfterMessage">
<template #date>{{ currentClearStatusAfter }}</template>
</gl-sprintf>
</span>
</template>
</gl-form-group>
</div>
</template>
......@@ -3,28 +3,15 @@ import { GlToast, GlTooltipDirective, GlSafeHtmlDirective, GlModal } from '@gitl
import Vue from 'vue';
import createFlash from '~/flash';
import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
import { __, s__ } from '~/locale';
import { s__ } from '~/locale';
import { updateUserStatus } from '~/rest_api';
import { timeRanges } from '~/vue_shared/constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { isUserBusy } from './utils';
import { NEVER_TIME_RANGE, AVAILABILITY_STATUS } from './constants';
import SetStatusForm from './set_status_form.vue';
export const AVAILABILITY_STATUS = {
BUSY: 'busy',
NOT_SET: 'not_set',
};
Vue.use(GlToast);
const statusTimeRanges = [
{
label: __('Never'),
name: 'never',
},
...timeRanges,
];
export default {
components: {
GlModal,
......@@ -67,7 +54,7 @@ export default {
message: this.currentMessage,
modalId: 'set-user-status-modal',
availability: isUserBusy(this.currentAvailability),
clearStatusAfter: statusTimeRanges[0],
clearStatusAfter: NEVER_TIME_RANGE,
};
},
mounted() {
......@@ -91,7 +78,7 @@ export default {
message,
availability: availability ? AVAILABILITY_STATUS.BUSY : AVAILABILITY_STATUS.NOT_SET,
clearStatusAfter:
clearStatusAfter.label === statusTimeRanges[0].label ? null : clearStatusAfter.shortcut,
clearStatusAfter.label === NEVER_TIME_RANGE.label ? null : clearStatusAfter.shortcut,
})
.then(this.onUpdateSuccess)
.catch(this.onUpdateFail);
......@@ -123,7 +110,6 @@ export default {
this.availability = value;
},
},
statusTimeRanges,
safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
actionPrimary: { text: s__('SetStatusModal|Set status') },
actionSecondary: { text: s__('SetStatusModal|Remove status') },
......
<script>
import { secondsToMilliseconds } from '~/lib/utils/datetime_utility';
import dateFormat from '~/lib/dateformat';
import SetStatusForm from './set_status_form.vue';
import { isUserBusy } from './utils';
import { NEVER_TIME_RANGE, AVAILABILITY_STATUS } from './constants';
export default {
components: { SetStatusForm },
inject: ['fields'],
data() {
return {
emoji: this.fields.emoji.value,
message: this.fields.message.value,
availability: isUserBusy(this.fields.availability.value),
clearStatusAfter: NEVER_TIME_RANGE,
currentClearStatusAfter: this.fields.clearStatusAfter.value,
};
},
computed: {
clearStatusAfterInputValue() {
return this.clearStatusAfter.label === NEVER_TIME_RANGE.label
? null
: this.clearStatusAfter.shortcut;
},
availabilityInputValue() {
return this.availability
? this.$options.AVAILABILITY_STATUS.BUSY
: this.$options.AVAILABILITY_STATUS.NOT_SET;
},
},
mounted() {
this.$options.formEl = document.querySelector('form.js-edit-user');
if (!this.$options.formEl) return;
this.$options.formEl.addEventListener('ajax:success', this.handleFormSuccess);
},
beforeDestroy() {
if (!this.$options.formEl) return;
this.$options.formEl.removeEventListener('ajax:success', this.handleFormSuccess);
},
methods: {
handleMessageInput(value) {
this.message = value;
},
handleEmojiClick(emoji) {
this.emoji = emoji;
},
handleClearStatusAfterClick(after) {
this.clearStatusAfter = after;
},
handleAvailabilityInput(value) {
this.availability = value;
},
handleFormSuccess() {
if (!this.clearStatusAfter?.duration?.seconds) {
this.currentClearStatusAfter = '';
return;
}
const now = new Date();
const currentClearStatusAfterDate = new Date(
now.getTime() + secondsToMilliseconds(this.clearStatusAfter.duration.seconds),
);
this.currentClearStatusAfter = dateFormat(
currentClearStatusAfterDate,
"UTC:yyyy-mm-dd HH:MM:ss 'UTC'",
);
this.clearStatusAfter = NEVER_TIME_RANGE;
},
},
AVAILABILITY_STATUS,
formEl: null,
};
</script>
<template>
<div>
<input :value="emoji" type="hidden" :name="fields.emoji.name" />
<input :value="message" type="hidden" :name="fields.message.name" />
<input :value="availabilityInputValue" type="hidden" :name="fields.availability.name" />
<input :value="clearStatusAfterInputValue" type="hidden" :name="fields.clearStatusAfter.name" />
<set-status-form
default-emoji="speech_balloon"
:emoji="emoji"
:message="message"
:availability="availability"
:clear-status-after="clearStatusAfter"
:current-clear-status-after="currentClearStatusAfter"
@message-input="handleMessageInput"
@emoji-click="handleEmojiClick"
@clear-status-after-click="handleClearStatusAfterClick"
@availability-input="handleAvailabilityInput"
/>
</div>
</template>
export const AVAILABILITY_STATUS = {
BUSY: 'busy',
NOT_SET: 'not_set',
};
import { AVAILABILITY_STATUS } from './constants';
export const isUserBusy = (status = '') =>
Boolean(status.length && status.toLowerCase().trim() === AVAILABILITY_STATUS.BUSY);
......@@ -137,7 +137,7 @@ def user_params_attributes
:pronouns,
:pronunciation,
:validation_password,
status: [:emoji, :message, :availability]
status: [:emoji, :message, :availability, :clear_status_after]
]
end
......
......@@ -29,6 +29,10 @@ class UserStatus < ApplicationRecord
cache_markdown_field :message, pipeline: :emoji
def clear_status_after
clear_status_at
end
def clear_status_after=(value)
self.clear_status_at = CLEAR_STATUS_QUICK_OPTIONS[value]&.from_now
end
......
......@@ -2,8 +2,6 @@
- page_title s_("Profiles|Edit Profile")
- @content_class = "limit-container-width" unless fluid_layout
- gravatar_link = link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host
- availability = availability_values
- custom_emoji = @user.status&.customized?
= gitlab_ui_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user js-edit-user gl-mt-3 js-quick-submit gl-show-field-errors js-password-prompt-form', remote: true }, authenticity_token: true do |f|
.row.js-search-settings-section
......@@ -43,39 +41,12 @@
%h4.gl-mt-0= s_("Profiles|Current status")
%p= s_("Profiles|This emoji and message will appear on your profile and throughout the interface.")
.col-lg-8
= f.fields_for :status, @user.status do |status_form|
- emoji_button = render Pajamas::ButtonComponent.new(button_options: { title: s_("Profiles|Add status emoji"),
class: 'js-toggle-emoji-menu emoji-menu-toggle-button has-tooltip' } ) do
- if custom_emoji
= emoji_icon(@user.status.emoji, class: 'gl-mr-0!')
%span#js-no-emoji-placeholder.no-emoji-placeholder{ class: ('hidden' if custom_emoji) }
= sprite_icon('slight-smile', css_class: 'award-control-icon-neutral')
= sprite_icon('smiley', css_class: 'award-control-icon-positive')
= sprite_icon('smile', css_class: 'award-control-icon-super-positive')
- reset_message_button = render Pajamas::ButtonComponent.new(icon: 'close',
button_options: { id: 'js-clear-user-status-button',
class: 'has-tooltip',
title: s_("Profiles|Clear status") } )
= status_form.hidden_field :emoji, id: 'js-status-emoji-field'
.form-group.gl-form-group
= status_form.label :message, s_("Profiles|Your status")
.input-group{ role: 'group' }
.input-group-prepend
= emoji_button
= status_form.text_field :message,
id: 'js-status-message-field',
class: 'form-control gl-form-input input-lg',
placeholder: s_("Profiles|What's your status?")
.input-group-append
= reset_message_button
.form-group.gl-form-group
= status_form.gitlab_ui_checkbox_component :availability,
s_("Profiles|Busy"),
help_text: s_('Profiles|An indicator appears next to your name and avatar.'),
checkbox_options: { data: { testid: "user-availability-checkbox" } },
checked_value: availability["busy"],
unchecked_value: availability["not_set"]
#js-user-profile-set-status-form
= f.fields_for :status, @user.status do |status_form|
= status_form.hidden_field :emoji, data: { js_name: 'emoji' }
= status_form.hidden_field :message, data: { js_name: 'message' }
= status_form.hidden_field :availability, data: { js_name: 'availability' }
= status_form.hidden_field :clear_status_after, data: { js_name: 'clearStatusAfter' }
.col-lg-12
%hr
.row.user-time-preferences.js-search-settings-section
......
......@@ -205,7 +205,7 @@ To set your current status:
1. Select a value from the **Clear status after** dropdown list.
1. Select **Set status**. Alternatively, you can select **Remove status** to remove your user status entirely.
You can also set your current status by [using the API](../../api/users.md#user-status).
You can also set your current status from [your user settings](#access-your-user-settings) or by [using the API](../../api/users.md#user-status).
If you select the **Busy** checkbox, remember to clear it when you become available again.
......
......@@ -4,7 +4,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import GitlabTeamMemberBadge from 'ee/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue';
import NoteHeader from '~/notes/components/note_header.vue';
import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
import { AVAILABILITY_STATUS } from '~/set_status_modal/constants';
Vue.use(Vuex);
......
......@@ -16080,9 +16080,6 @@ msgstr ""
msgid "Failed to load deploy keys."
msgstr ""
 
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
 
......@@ -30166,15 +30163,9 @@ msgstr ""
msgid "Profiles|Add key"
msgstr ""
 
msgid "Profiles|Add status emoji"
msgstr ""
msgid "Profiles|An error occurred while updating your username, please try again."
msgstr ""
 
msgid "Profiles|An indicator appears next to your name and avatar."
msgstr ""
msgid "Profiles|Avatar cropper"
msgstr ""
 
......@@ -30187,9 +30178,6 @@ msgstr ""
msgid "Profiles|Bio"
msgstr ""
 
msgid "Profiles|Busy"
msgstr ""
msgid "Profiles|Change username"
msgstr ""
 
......@@ -30205,9 +30193,6 @@ msgstr ""
msgid "Profiles|City, country"
msgstr ""
 
msgid "Profiles|Clear status"
msgstr ""
msgid "Profiles|Commit email"
msgstr ""
 
......@@ -30457,9 +30442,6 @@ msgstr ""
msgid "Profiles|Website url"
msgstr ""
 
msgid "Profiles|What's your status?"
msgstr ""
msgid "Profiles|Who you represent or work for."
msgstr ""
 
......@@ -30505,9 +30487,6 @@ msgstr ""
msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you."
msgstr ""
 
msgid "Profiles|Your status"
msgstr ""
msgid "Profiles|https://website.com"
msgstr ""
 
......@@ -82,13 +82,17 @@
expect(ldap_user.location).to eq('City, Country')
end
it 'allows setting a user status' do
it 'allows setting a user status', :freeze_time do
sign_in(user)
put :update, params: { user: { status: { message: 'Working hard!', availability: 'busy' } } }
put(
:update,
params: { user: { status: { message: 'Working hard!', availability: 'busy', clear_status_after: '8_hours' } } }
)
expect(user.reload.status.message).to eq('Working hard!')
expect(user.reload.status.availability).to eq('busy')
expect(user.reload.status.clear_status_after).to eq(8.hours.from_now)
expect(response).to have_gitlab_http_status(:found)
end
......
......@@ -180,7 +180,7 @@ def select_emoji(emoji_name, is_modal = false)
end
it 'adds emoji to user status' do
emoji = 'biohazard'
emoji = 'basketball'
select_emoji(emoji)
submit_settings
......@@ -193,7 +193,7 @@ def select_emoji(emoji_name, is_modal = false)
it 'adds message to user status' do
message = 'I have something to say'
fill_in 'js-status-message-field', with: message
fill_in s_("SetStatusModal|What's your status?"), with: message
submit_settings
visit_user
......@@ -208,7 +208,7 @@ def select_emoji(emoji_name, is_modal = false)
emoji = '8ball'
message = 'Playing outside'
select_emoji(emoji)
fill_in 'js-status-message-field', with: message
fill_in s_("SetStatusModal|What's your status?"), with: message
submit_settings
visit_user
......@@ -230,7 +230,7 @@ def select_emoji(emoji_name, is_modal = false)
end
visit(profile_path)
click_button 'js-clear-user-status-button'
click_button s_('SetStatusModal|Clear status')
submit_settings
visit_user
......@@ -240,9 +240,9 @@ def select_emoji(emoji_name, is_modal = false)
it 'displays a default emoji if only message is entered' do
message = 'a status without emoji'
fill_in 'js-status-message-field', with: message
fill_in s_("SetStatusModal|What's your status?"), with: message
within('.js-toggle-emoji-menu') do
within('.emoji-menu-toggle-button') do
expect(page).to have_emoji('speech_balloon')
end
end
......@@ -406,7 +406,7 @@ def set_user_status_in_modal
it 'adds message to user status' do
message = 'I have something to say'
open_user_status_modal
find('.js-status-message-field').native.send_keys(message)
find_field(s_("SetStatusModal|What's your status?")).native.send_keys(message)
set_user_status_in_modal
visit_user
......@@ -422,7 +422,7 @@ def set_user_status_in_modal
message = 'Playing outside'
open_user_status_modal
select_emoji(emoji, true)
find('.js-status-message-field').native.send_keys(message)
find_field(s_("SetStatusModal|What's your status?")).native.send_keys(message)
set_user_status_in_modal
visit_user
......@@ -446,7 +446,7 @@ def set_user_status_in_modal
open_edit_status_modal
find('.js-clear-user-status-button').click
click_button s_('SetStatusModal|Clear status')
set_user_status_in_modal
visit_user
......@@ -491,7 +491,7 @@ def set_user_status_in_modal
it 'displays a default emoji if only message is entered' do
message = 'a status without emoji'
open_user_status_modal
find('.js-status-message-field').native.send_keys(message)
find_field(s_("SetStatusModal|What's your status?")).native.send_keys(message)
expect(page).to have_emoji('speech_balloon')
end
......
import { GlAvatarLink, GlBadge } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import UserAvatar from '~/members/components/avatars/user_avatar.vue';
import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
import { AVAILABILITY_STATUS } from '~/set_status_modal/constants';
import { member as memberMock, member2faEnabled, orphanedMember } from '../../mock_data';
......
......@@ -3,7 +3,7 @@ import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import NoteHeader from '~/notes/components/note_header.vue';
import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
import { AVAILABILITY_STATUS } from '~/set_status_modal/constants';
import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
Vue.use(Vuex);
......
import $ from 'jquery';
import { TEST_HOST } from 'helpers/test_constants';
import axios from '~/lib/utils/axios_utils';
import EmojiMenu from '~/pages/profiles/show/emoji_menu';
describe('EmojiMenu', () => {
const dummyEmojiTag = '<dummy></tag>';
const dummyToggleButtonSelector = '.toggle-button-selector';
const dummyMenuClass = 'dummy-menu-class';
let emojiMenu;
let dummySelectEmojiCallback;
let dummyEmojiList;
beforeEach(() => {
dummySelectEmojiCallback = jest.fn().mockName('dummySelectEmojiCallback');
dummyEmojiList = {
glEmojiTag() {
return dummyEmojiTag;
},
normalizeEmojiName(emoji) {
return emoji;
},
isEmojiNameValid() {
return true;
},
getEmojiCategoryMap() {
return { dummyCategory: [] };
},
};
emojiMenu = new EmojiMenu(
dummyEmojiList,
dummyToggleButtonSelector,
dummyMenuClass,
dummySelectEmojiCallback,
);
});
afterEach(() => {
emojiMenu.destroy();
});
describe('addAward', () => {
const dummyAwardUrl = `${TEST_HOST}/award/url`;
const dummyEmoji = 'tropical_fish';
const dummyVotesBlock = () => $('<div />');
it('calls selectEmojiCallback', async () => {
expect(dummySelectEmojiCallback).not.toHaveBeenCalled();
await emojiMenu.addAward(dummyVotesBlock(), dummyAwardUrl, dummyEmoji, false);
expect(dummySelectEmojiCallback).toHaveBeenCalledWith(dummyEmoji, dummyEmojiTag);
});
it('does not make an axios request', async () => {
jest.spyOn(axios, 'request').mockReturnValue();
await emojiMenu.addAward(dummyVotesBlock(), dummyAwardUrl, dummyEmoji, false);
expect(axios.request).not.toHaveBeenCalled();
});
});
describe('bindEvents', () => {
beforeEach(() => {
jest.spyOn(emojiMenu, 'registerEventListener').mockReturnValue();
});
it('binds event listeners to custom toggle button', () => {
emojiMenu.bindEvents();
expect(emojiMenu.registerEventListener).toHaveBeenCalledWith(
'one',
expect.anything(),
'mouseenter focus',
dummyToggleButtonSelector,
'mouseenter focus',
expect.anything(),
);
expect(emojiMenu.registerEventListener).toHaveBeenCalledWith(
'on',
expect.anything(),
'click',
dummyToggleButtonSelector,
expect.anything(),
);
});
it('binds event listeners to custom menu class', () => {
emojiMenu.bindEvents();
expect(emojiMenu.registerEventListener).toHaveBeenCalledWith(
'on',
expect.anything(),
'click',
`.js-awards-block .js-emoji-btn, .${dummyMenuClass} .js-emoji-btn`,
expect.anything(),
);
});
});
describe('createEmojiMenu', () => {
it('renders the menu with custom menu class', () => {
const menuElement = () =>
document.body.querySelector(`.emoji-menu.${dummyMenuClass} .emoji-menu-content`);
expect(menuElement()).toBe(null);
emojiMenu.createEmojiMenu();
expect(menuElement()).not.toBe(null);
});
});
});
import { GlModal, GlFormCheckbox } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { initEmojiMock, clearEmojiMock } from 'helpers/emoji';
import * as UserApi from '~/api/user_api';
import EmojiPicker from '~/emoji/components/picker.vue';
import createFlash from '~/flash';
import stubChildren from 'helpers/stub_children';
import SetStatusModalWrapper, {
AVAILABILITY_STATUS,
} from '~/set_status_modal/set_status_modal_wrapper.vue';
import SetStatusModalWrapper from '~/set_status_modal/set_status_modal_wrapper.vue';
import { AVAILABILITY_STATUS } from '~/set_status_modal/constants';
import SetStatusForm from '~/set_status_modal/set_status_form.vue';
jest.mock('~/flash');
......@@ -34,7 +33,7 @@ describe('SetStatusModalWrapper', () => {
};
const createComponent = (props = {}) => {
return mount(SetStatusModalWrapper, {
return mountExtended(SetStatusModalWrapper, {
propsData: {
...defaultProps,
...props,
......@@ -53,7 +52,8 @@ describe('SetStatusModalWrapper', () => {
};
const findModal = () => wrapper.find(GlModal);
const findFormField = (field) => wrapper.find(`[name="user[status][${field}]"]`);
const findMessageField = () =>
wrapper.findByPlaceholderText(SetStatusForm.i18n.statusMessagePlaceholder);
const findClearStatusButton = () => wrapper.find('.js-clear-user-status-button');
const findAvailabilityCheckbox = () => wrapper.find(GlFormCheckbox);
const findClearStatusAtMessage = () => wrapper.find('[data-testid="clear-status-at-message"]');
......@@ -83,14 +83,8 @@ describe('SetStatusModalWrapper', () => {
return initModal();
});
it('sets the hidden status emoji field', () => {
const field = findFormField('emoji');
expect(field.exists()).toBe(true);
expect(field.element.value).toBe(defaultEmoji);
});
it('sets the message field', () => {
const field = findFormField('message');
const field = findMessageField();
expect(field.exists()).toBe(true);
expect(field.element.value).toBe(defaultMessage);
});
......@@ -135,7 +129,7 @@ describe('SetStatusModalWrapper', () => {
});
it('does not set the message field', () => {
expect(findFormField('message').element.value).toBe('');
expect(findMessageField().element.value).toBe('');
});
it('hides the clear status button', () => {
......@@ -143,18 +137,6 @@ describe('SetStatusModalWrapper', () => {
});
});
describe('with no currentEmoji set', () => {
beforeEach(async () => {
await initEmojiMock();
wrapper = createComponent({ currentEmoji: '' });
return initModal();
});
it('does not set the hidden status emoji field', () => {
expect(findFormField('emoji').element.value).toBe('');
});
});
describe('with currentClearStatusAfter set', () => {
beforeEach(async () => {
await initEmojiMock();
......@@ -184,8 +166,7 @@ describe('SetStatusModalWrapper', () => {
findModal().vm.$emit('secondary');
await nextTick();
expect(findFormField('message').element.value).toBe('');
expect(findFormField('emoji').element.value).toBe('');
expect(findMessageField().element.value).toBe('');
});
it('clicking "setStatus" submits the user status', async () => {
......
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать