Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-11-17 18:09:28 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-11-17 18:09:28 +0300
commit6535cf9c79362862c31ea7d26c61541b84db18d9 (patch)
tree6d646edcf11d38e8ac23bceed1340ff8907b850d /spec/frontend
parent9a8f801d7352b7965fe690a599410fb50005ce67 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/alerts_settings/alerts_integrations_list_spec.js17
-rw-r--r--spec/frontend/alerts_settings/alerts_settings_wrapper_spec.js2
-rw-r--r--spec/frontend/gfm_auto_complete_spec.js17
-rw-r--r--spec/frontend/notes/components/note_header_spec.js12
-rw-r--r--spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js257
-rw-r--r--spec/frontend/set_status_modal/user_availability_status_spec.js31
-rw-r--r--spec/frontend/vue_shared/components/user_popover/user_popover_spec.js30
7 files changed, 363 insertions, 3 deletions
diff --git a/spec/frontend/alerts_settings/alerts_integrations_list_spec.js b/spec/frontend/alerts_settings/alerts_integrations_list_spec.js
index 051caf0e49d..90bb38f0c2b 100644
--- a/spec/frontend/alerts_settings/alerts_integrations_list_spec.js
+++ b/spec/frontend/alerts_settings/alerts_integrations_list_spec.js
@@ -1,5 +1,6 @@
import { GlTable, GlIcon, GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import Tracking from '~/tracking';
import AlertIntegrationsList, {
i18n,
@@ -23,6 +24,7 @@ const mockIntegrations = [
describe('AlertIntegrationsList', () => {
let wrapper;
+ const { trigger: triggerIntersection } = useMockIntersectionObserver();
function mountComponent({ data = {}, props = {} } = {}) {
wrapper = mount(AlertIntegrationsList, {
@@ -100,12 +102,23 @@ describe('AlertIntegrationsList', () => {
describe('Snowplow tracking', () => {
beforeEach(() => {
- jest.spyOn(Tracking, 'event');
mountComponent();
+ jest.spyOn(Tracking, 'event');
+ });
+
+ it('should NOT track alert list page views when list is collapsed', () => {
+ triggerIntersection(wrapper.vm.$el, { entry: { isIntersecting: false } });
+
+ expect(Tracking.event).not.toHaveBeenCalled();
});
- it('should track alert list page views', () => {
+ it('should track alert list page views only once when list is expanded', () => {
+ triggerIntersection(wrapper.vm.$el, { entry: { isIntersecting: true } });
+ triggerIntersection(wrapper.vm.$el, { entry: { isIntersecting: true } });
+ triggerIntersection(wrapper.vm.$el, { entry: { isIntersecting: true } });
+
const { category, action } = trackAlertIntegrationsViewsOptions;
+ expect(Tracking.event).toHaveBeenCalledTimes(1);
expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
});
diff --git a/spec/frontend/alerts_settings/alerts_settings_wrapper_spec.js b/spec/frontend/alerts_settings/alerts_settings_wrapper_spec.js
index c24a76f1df0..7384cf9a095 100644
--- a/spec/frontend/alerts_settings/alerts_settings_wrapper_spec.js
+++ b/spec/frontend/alerts_settings/alerts_settings_wrapper_spec.js
@@ -3,6 +3,7 @@ import { mount, createLocalVue } from '@vue/test-utils';
import createMockApollo from 'jest/helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
+import { useMockIntersectionObserver } from 'helpers/mock_dom_observer';
import AlertsSettingsWrapper from '~/alerts_settings/components/alerts_settings_wrapper.vue';
import AlertsSettingsFormOld from '~/alerts_settings/components/alerts_settings_form_old.vue';
import AlertsSettingsFormNew from '~/alerts_settings/components/alerts_settings_form_new.vue';
@@ -47,6 +48,7 @@ describe('AlertsSettingsWrapper', () => {
let wrapper;
let fakeApollo;
let destroyIntegrationHandler;
+ useMockIntersectionObserver();
const findLoader = () => wrapper.find(IntegrationsList).find(GlLoadingIcon);
const findIntegrations = () => wrapper.find(IntegrationsList).findAll('table tbody tr');
diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js
index 8da4320d993..e7025955e8e 100644
--- a/spec/frontend/gfm_auto_complete_spec.js
+++ b/spec/frontend/gfm_auto_complete_spec.js
@@ -378,6 +378,7 @@ describe('GfmAutoComplete', () => {
username: 'my-group',
title: '',
icon: '',
+ availabilityStatus: '',
}),
).toBe('<li>IMG my-group <small></small> </li>');
});
@@ -389,6 +390,7 @@ describe('GfmAutoComplete', () => {
username: 'my-group',
title: '',
icon: '<i class="icon"/>',
+ availabilityStatus: '',
}),
).toBe('<li>IMG my-group <small></small> <i class="icon"/></li>');
});
@@ -400,9 +402,24 @@ describe('GfmAutoComplete', () => {
username: 'my-group',
title: 'MyGroup+',
icon: '<i class="icon"/>',
+ availabilityStatus: '',
}),
).toBe('<li>IMG my-group <small>MyGroup+</small> <i class="icon"/></li>');
});
+
+ it('should add user availability status if availabilityStatus is set', () => {
+ expect(
+ GfmAutoComplete.Members.templateFunction({
+ avatarTag: 'IMG',
+ username: 'my-group',
+ title: '',
+ icon: '<i class="icon"/>',
+ availabilityStatus: '<span class="gl-text-gray-500"> (Busy)</span>',
+ }),
+ ).toBe(
+ '<li>IMG my-group <small><span class="gl-text-gray-500"> (Busy)</span></small> <i class="icon"/></li>',
+ );
+ });
});
describe('labels', () => {
diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js
index 1cc4a2fa853..69aab0d051e 100644
--- a/spec/frontend/notes/components/note_header_spec.js
+++ b/spec/frontend/notes/components/note_header_spec.js
@@ -1,7 +1,9 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vuex from 'vuex';
+import { GlSprintf } from '@gitlab/ui';
import NoteHeader from '~/notes/components/note_header.vue';
+import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
const localVue = createLocalVue();
localVue.use(Vuex);
@@ -28,6 +30,9 @@ describe('NoteHeader component', () => {
path: '/root',
state: 'active',
username: 'root',
+ status: {
+ availability: '',
+ },
};
const createComponent = props => {
@@ -37,6 +42,7 @@ describe('NoteHeader component', () => {
actions,
}),
propsData: { ...props },
+ stubs: { GlSprintf },
});
};
@@ -97,6 +103,12 @@ describe('NoteHeader component', () => {
expect(wrapper.find('.js-user-link').exists()).toBe(true);
});
+ it('renders busy status if author availability is set', () => {
+ createComponent({ author: { ...author, status: { availability: AVAILABILITY_STATUS.BUSY } } });
+
+ expect(wrapper.find('.js-user-link').text()).toContain('(Busy)');
+ });
+
it('renders deleted user text if author is not passed as a prop', () => {
createComponent();
diff --git a/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js b/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js
new file mode 100644
index 00000000000..fad23aa05a4
--- /dev/null
+++ b/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js
@@ -0,0 +1,257 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlModal, GlFormCheckbox } from '@gitlab/ui';
+import { initEmojiMock } from 'helpers/emoji';
+import Api from '~/api';
+import { deprecatedCreateFlash as createFlash } from '~/flash';
+import SetStatusModalWrapper, {
+ AVAILABILITY_STATUS,
+} from '~/set_status_modal/set_status_modal_wrapper.vue';
+
+jest.mock('~/api');
+jest.mock('~/flash');
+
+describe('SetStatusModalWrapper', () => {
+ let wrapper;
+ let mockEmoji;
+ const $toast = {
+ show: jest.fn(),
+ };
+
+ const defaultEmoji = 'speech_balloon';
+ const defaultMessage = "They're comin' in too fast!";
+
+ const defaultProps = {
+ currentEmoji: defaultEmoji,
+ currentMessage: defaultMessage,
+ defaultEmoji,
+ canSetUserAvailability: true,
+ };
+
+ const createComponent = (props = {}) => {
+ return shallowMount(SetStatusModalWrapper, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ mocks: {
+ $toast,
+ },
+ });
+ };
+
+ const findModal = () => wrapper.find(GlModal);
+ const findFormField = field => wrapper.find(`[name="user[status][${field}]"]`);
+ const findClearStatusButton = () => wrapper.find('.js-clear-user-status-button');
+ const findNoEmojiPlaceholder = () => wrapper.find('.js-no-emoji-placeholder');
+ const findToggleEmojiButton = () => wrapper.find('.js-toggle-emoji-menu');
+ const findAvailabilityCheckbox = () => wrapper.find(GlFormCheckbox);
+
+ const initModal = ({ mockOnUpdateSuccess = true, mockOnUpdateFailure = true } = {}) => {
+ const modal = findModal();
+ // mock internal emoji methods
+ wrapper.vm.showEmojiMenu = jest.fn();
+ wrapper.vm.hideEmojiMenu = jest.fn();
+ if (mockOnUpdateSuccess) wrapper.vm.onUpdateSuccess = jest.fn();
+ if (mockOnUpdateFailure) wrapper.vm.onUpdateFail = jest.fn();
+
+ modal.vm.$emit('shown');
+ return wrapper.vm.$nextTick();
+ };
+
+ beforeEach(async () => {
+ mockEmoji = await initEmojiMock();
+ wrapper = createComponent();
+ return initModal();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ mockEmoji.restore();
+ });
+
+ describe('with minimum props', () => {
+ 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');
+ expect(field.exists()).toBe(true);
+ expect(field.element.value).toBe(defaultMessage);
+ });
+
+ it('sets the availability field to false', () => {
+ const field = findAvailabilityCheckbox();
+ expect(field.exists()).toBe(true);
+ expect(field.element.checked).toBeUndefined();
+ });
+
+ it('has a clear status button', () => {
+ expect(findClearStatusButton().isVisible()).toBe(true);
+ });
+
+ it('clicking the toggle emoji button displays the emoji list', () => {
+ expect(wrapper.vm.showEmojiMenu).not.toHaveBeenCalled();
+ findToggleEmojiButton().trigger('click');
+ expect(wrapper.vm.showEmojiMenu).toHaveBeenCalled();
+ });
+ });
+
+ describe('with no currentMessage set', () => {
+ beforeEach(async () => {
+ mockEmoji = await initEmojiMock();
+ wrapper = createComponent({ currentMessage: '' });
+ return initModal();
+ });
+
+ it('does not set the message field', () => {
+ expect(findFormField('message').element.value).toBe('');
+ });
+
+ it('hides the clear status button', () => {
+ expect(findClearStatusButton().isVisible()).toBe(false);
+ });
+
+ it('shows the placeholder emoji', () => {
+ expect(findNoEmojiPlaceholder().isVisible()).toBe(true);
+ });
+ });
+
+ describe('with no currentEmoji set', () => {
+ beforeEach(async () => {
+ mockEmoji = await initEmojiMock();
+ wrapper = createComponent({ currentEmoji: '' });
+ return initModal();
+ });
+
+ it('does not set the hidden status emoji field', () => {
+ expect(findFormField('emoji').element.value).toBe('');
+ });
+
+ it('hides the placeholder emoji', () => {
+ expect(findNoEmojiPlaceholder().isVisible()).toBe(false);
+ });
+
+ describe('with no currentMessage set', () => {
+ beforeEach(async () => {
+ mockEmoji = await initEmojiMock();
+ wrapper = createComponent({ currentEmoji: '', currentMessage: '' });
+ return initModal();
+ });
+
+ it('shows the placeholder emoji', () => {
+ expect(findNoEmojiPlaceholder().isVisible()).toBe(true);
+ });
+ });
+ });
+
+ describe('update status', () => {
+ describe('succeeds', () => {
+ beforeEach(() => {
+ jest.spyOn(Api, 'postUserStatus').mockResolvedValue();
+ });
+
+ it('clicking "removeStatus" clears the emoji and message fields', async () => {
+ findModal().vm.$emit('cancel');
+ await wrapper.vm.$nextTick();
+
+ expect(findFormField('message').element.value).toBe('');
+ expect(findFormField('emoji').element.value).toBe('');
+ });
+
+ it('clicking "setStatus" submits the user status', async () => {
+ findModal().vm.$emit('ok');
+ await wrapper.vm.$nextTick();
+
+ // set the availability status
+ findAvailabilityCheckbox().vm.$emit('input', true);
+
+ findModal().vm.$emit('ok');
+ await wrapper.vm.$nextTick();
+
+ const commonParams = { emoji: defaultEmoji, message: defaultMessage };
+
+ expect(Api.postUserStatus).toHaveBeenCalledTimes(2);
+ expect(Api.postUserStatus).toHaveBeenNthCalledWith(1, {
+ availability: AVAILABILITY_STATUS.NOT_SET,
+ ...commonParams,
+ });
+ expect(Api.postUserStatus).toHaveBeenNthCalledWith(2, {
+ availability: AVAILABILITY_STATUS.BUSY,
+ ...commonParams,
+ });
+ });
+
+ it('calls the "onUpdateSuccess" handler', async () => {
+ findModal().vm.$emit('ok');
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.onUpdateSuccess).toHaveBeenCalled();
+ });
+ });
+
+ describe('success message', () => {
+ beforeEach(async () => {
+ mockEmoji = await initEmojiMock();
+ wrapper = createComponent({ currentEmoji: '', currentMessage: '' });
+ jest.spyOn(Api, 'postUserStatus').mockResolvedValue();
+ return initModal({ mockOnUpdateSuccess: false });
+ });
+
+ it('displays a toast success message', async () => {
+ findModal().vm.$emit('ok');
+ await wrapper.vm.$nextTick();
+
+ expect($toast.show).toHaveBeenCalledWith('Status updated', {
+ position: 'top-center',
+ type: 'success',
+ });
+ });
+ });
+
+ describe('with errors', () => {
+ beforeEach(() => {
+ jest.spyOn(Api, 'postUserStatus').mockRejectedValue();
+ });
+
+ it('calls the "onUpdateFail" handler', async () => {
+ findModal().vm.$emit('ok');
+ await wrapper.vm.$nextTick();
+
+ expect(wrapper.vm.onUpdateFail).toHaveBeenCalled();
+ });
+ });
+
+ describe('error message', () => {
+ beforeEach(async () => {
+ mockEmoji = await initEmojiMock();
+ wrapper = createComponent({ currentEmoji: '', currentMessage: '' });
+ jest.spyOn(Api, 'postUserStatus').mockRejectedValue();
+ return initModal({ mockOnUpdateFailure: false });
+ });
+
+ it('flashes an error message', async () => {
+ findModal().vm.$emit('ok');
+ await wrapper.vm.$nextTick();
+
+ expect(createFlash).toHaveBeenCalledWith(
+ "Sorry, we weren't able to set your status. Please try again later.",
+ );
+ });
+ });
+ });
+
+ describe('with canSetUserAvailability=false', () => {
+ beforeEach(async () => {
+ mockEmoji = await initEmojiMock();
+ wrapper = createComponent({ canSetUserAvailability: false });
+ return initModal();
+ });
+
+ it('hides the set availability checkbox', () => {
+ expect(findAvailabilityCheckbox().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/set_status_modal/user_availability_status_spec.js b/spec/frontend/set_status_modal/user_availability_status_spec.js
new file mode 100644
index 00000000000..95ca0251ce0
--- /dev/null
+++ b/spec/frontend/set_status_modal/user_availability_status_spec.js
@@ -0,0 +1,31 @@
+import { shallowMount } from '@vue/test-utils';
+import UserAvailabilityStatus from '~/set_status_modal/components/user_availability_status.vue';
+import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
+
+describe('UserAvailabilityStatus', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ return shallowMount(UserAvailabilityStatus, {
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('with availability status', () => {
+ it(`set to ${AVAILABILITY_STATUS.BUSY}`, () => {
+ wrapper = createComponent({ availability: AVAILABILITY_STATUS.BUSY });
+ expect(wrapper.text()).toContain('(Busy)');
+ });
+
+ it(`set to ${AVAILABILITY_STATUS.NOT_SET}`, () => {
+ wrapper = createComponent({ availability: AVAILABILITY_STATUS.NOT_SET });
+ expect(wrapper.html()).toBe('');
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
index c208d7b0226..7d58a865ba3 100644
--- a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
+++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
@@ -1,6 +1,8 @@
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlSprintf, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import UserPopover from '~/vue_shared/components/user_popover/user_popover.vue';
+import UserAvailabilityStatus from '~/set_status_modal/components/user_availability_status.vue';
+import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
const DEFAULT_PROPS = {
user: {
@@ -34,6 +36,7 @@ describe('User Popover Component', () => {
const findByTestId = testid => wrapper.find(`[data-testid="${testid}"]`);
const findUserStatus = () => wrapper.find('.js-user-status');
const findTarget = () => document.querySelector('.js-user-link');
+ const findAvailabilityStatus = () => wrapper.find(UserAvailabilityStatus);
const createWrapper = (props = {}, options = {}) => {
wrapper = shallowMount(UserPopover, {
@@ -43,7 +46,8 @@ describe('User Popover Component', () => {
...props,
},
stubs: {
- 'gl-sprintf': GlSprintf,
+ GlSprintf,
+ UserAvailabilityStatus,
},
...options,
});
@@ -199,6 +203,30 @@ describe('User Popover Component', () => {
expect(findUserStatus().exists()).toBe(false);
});
+
+ it('should show the busy status if user set to busy', () => {
+ const user = {
+ ...DEFAULT_PROPS.user,
+ status: { availability: AVAILABILITY_STATUS.BUSY },
+ };
+
+ createWrapper({ user });
+
+ expect(findAvailabilityStatus().exists()).toBe(true);
+ expect(wrapper.text()).toContain(user.name);
+ expect(wrapper.text()).toContain('(Busy)');
+ });
+
+ it('should hide the busy status for any other status', () => {
+ const user = {
+ ...DEFAULT_PROPS.user,
+ status: { availability: AVAILABILITY_STATUS.NOT_SET },
+ };
+
+ createWrapper({ user });
+
+ expect(wrapper.text()).not.toContain('(Busy)');
+ });
});
describe('security bot', () => {