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>2021-12-16 03:15:50 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-16 03:15:50 +0300
commite04431d29efaf17dda9dfbfbd0c5001693b25ee4 (patch)
treef114abad1f4882ef6c9c702e8de3a84334809031 /spec/frontend
parent1c898dc5c10bbedf94386d917259153d73608495 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/__helpers__/emoji.js46
-rw-r--r--spec/frontend/access_tokens/components/token_spec.js65
-rw-r--r--spec/frontend/access_tokens/components/tokens_app_spec.js148
-rw-r--r--spec/frontend/alerts_settings/components/__snapshots__/alerts_form_spec.js.snap4
-rw-r--r--spec/frontend/awards_handler_spec.js10
-rw-r--r--spec/frontend/behaviors/gl_emoji_spec.js15
-rw-r--r--spec/frontend/crm/contact_form_spec.js (renamed from spec/frontend/crm/new_contact_form_spec.js)75
-rw-r--r--spec/frontend/crm/contacts_root_spec.js58
-rw-r--r--spec/frontend/crm/mock_data.js28
-rw-r--r--spec/frontend/emoji/index_spec.js108
-rw-r--r--spec/frontend/gfm_auto_complete_spec.js8
-rw-r--r--spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap3
-rw-r--r--spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap2
-rw-r--r--spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap1
-rw-r--r--spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js25
-rw-r--r--spec/frontend/shortcuts_spec.js9
-rw-r--r--spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap1
-rw-r--r--spec/frontend/vue_shared/components/form/__snapshots__/title_spec.js.snap1
18 files changed, 497 insertions, 110 deletions
diff --git a/spec/frontend/__helpers__/emoji.js b/spec/frontend/__helpers__/emoji.js
index a64135601ae..014a7854024 100644
--- a/spec/frontend/__helpers__/emoji.js
+++ b/spec/frontend/__helpers__/emoji.js
@@ -1,8 +1,7 @@
-import MockAdapter from 'axios-mock-adapter';
import { initEmojiMap, EMOJI_VERSION } from '~/emoji';
-import axios from '~/lib/utils/axios_utils';
+import { CACHE_VERSION_KEY, CACHE_KEY } from '~/emoji/constants';
-export const emojiFixtureMap = {
+export const validEmoji = {
atom: {
moji: '⚛',
description: 'atom symbol',
@@ -49,11 +48,39 @@ export const emojiFixtureMap = {
unicodeVersion: '5.1',
description: 'white medium star',
},
+ gay_pride_flag: {
+ moji: '🏳️‍🌈',
+ unicodeVersion: '7.0',
+ description: 'because it contains a zero width joiner',
+ },
+ family_mmb: {
+ moji: '👨‍👨‍👦',
+ unicodeVersion: '6.0',
+ description: 'because it contains multiple zero width joiners',
+ },
+};
+
+export const invalidEmoji = {
xss: {
moji: '<img src=x onerror=prompt(1)>',
unicodeVersion: '5.1',
description: 'xss',
},
+ non_moji: {
+ moji: 'I am not an emoji...',
+ unicodeVersion: '9.0',
+ description: '...and should be filtered out',
+ },
+ multiple_moji: {
+ moji: '🍂🏭',
+ unicodeVersion: '9.0',
+ description: 'Multiple separate emoji that are not joined by a zero width joiner',
+ },
+};
+
+export const emojiFixtureMap = {
+ ...validEmoji,
+ ...invalidEmoji,
};
export const mockEmojiData = Object.keys(emojiFixtureMap).reduce((acc, k) => {
@@ -63,11 +90,14 @@ export const mockEmojiData = Object.keys(emojiFixtureMap).reduce((acc, k) => {
return acc;
}, {});
-export async function initEmojiMock(mockData = mockEmojiData) {
- const mock = new MockAdapter(axios);
- mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, JSON.stringify(mockData));
+export function clearEmojiMock() {
+ localStorage.clear();
+ initEmojiMap.promise = null;
+}
+export async function initEmojiMock(mockData = mockEmojiData) {
+ clearEmojiMock();
+ localStorage.setItem(CACHE_VERSION_KEY, EMOJI_VERSION);
+ localStorage.setItem(CACHE_KEY, JSON.stringify(mockData));
await initEmojiMap();
-
- return mock;
}
diff --git a/spec/frontend/access_tokens/components/token_spec.js b/spec/frontend/access_tokens/components/token_spec.js
new file mode 100644
index 00000000000..1af21aaa8cd
--- /dev/null
+++ b/spec/frontend/access_tokens/components/token_spec.js
@@ -0,0 +1,65 @@
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+
+import Token from '~/access_tokens/components/token.vue';
+import InputCopyToggleVisibility from '~/vue_shared/components/form/input_copy_toggle_visibility.vue';
+
+describe('Token', () => {
+ let wrapper;
+
+ const defaultPropsData = {
+ token: 'az4a2l5f8ssa0zvdfbhidbzlx',
+ inputId: 'feed_token',
+ inputLabel: 'Feed token',
+ copyButtonTitle: 'Copy feed token',
+ };
+
+ const defaultSlots = {
+ title: 'Feed token title',
+ description: 'Feed token description',
+ 'input-description': 'Feed token input description',
+ };
+
+ const createComponent = () => {
+ wrapper = mountExtended(Token, { propsData: defaultPropsData, slots: defaultSlots });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders title slot', () => {
+ createComponent();
+
+ expect(wrapper.findByText(defaultSlots.title, { selector: 'h4' }).exists()).toBe(true);
+ });
+
+ it('renders description slot', () => {
+ createComponent();
+
+ expect(wrapper.findByText(defaultSlots.description).exists()).toBe(true);
+ });
+
+ it('renders input description slot', () => {
+ createComponent();
+
+ expect(wrapper.findByText(defaultSlots['input-description']).exists()).toBe(true);
+ });
+
+ it('correctly passes props to `InputCopyToggleVisibility` component', () => {
+ createComponent();
+
+ const inputCopyToggleVisibilityComponent = wrapper.findComponent(InputCopyToggleVisibility);
+
+ expect(inputCopyToggleVisibilityComponent.props()).toMatchObject({
+ formInputGroupProps: {
+ id: defaultPropsData.inputId,
+ },
+ value: defaultPropsData.token,
+ copyButtonTitle: defaultPropsData.copyButtonTitle,
+ });
+ expect(inputCopyToggleVisibilityComponent.attributes()).toMatchObject({
+ label: defaultPropsData.inputLabel,
+ 'label-for': defaultPropsData.inputId,
+ });
+ });
+});
diff --git a/spec/frontend/access_tokens/components/tokens_app_spec.js b/spec/frontend/access_tokens/components/tokens_app_spec.js
new file mode 100644
index 00000000000..d7acfbb47eb
--- /dev/null
+++ b/spec/frontend/access_tokens/components/tokens_app_spec.js
@@ -0,0 +1,148 @@
+import { merge } from 'lodash';
+
+import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
+
+import TokensApp from '~/access_tokens/components/tokens_app.vue';
+import { FEED_TOKEN, INCOMING_EMAIL_TOKEN, STATIC_OBJECT_TOKEN } from '~/access_tokens/constants';
+
+describe('TokensApp', () => {
+ let wrapper;
+
+ const defaultProvide = {
+ tokenTypes: {
+ [FEED_TOKEN]: {
+ enabled: true,
+ token: 'DUKu345VD73Py7zz3z89',
+ resetPath: '/-/profile/reset_feed_token',
+ },
+ [INCOMING_EMAIL_TOKEN]: {
+ enabled: true,
+ token: 'az4a2l5f8ssa0zvdfbhidbzlx',
+ resetPath: '/-/profile/reset_incoming_email_token',
+ },
+ [STATIC_OBJECT_TOKEN]: {
+ enabled: true,
+ token: 'QHXwGHYioHTgxQnAcyZ-',
+ resetPath: '/-/profile/reset_static_object_token',
+ },
+ },
+ };
+
+ const createComponent = (options = {}) => {
+ wrapper = mountExtended(TokensApp, merge({}, { provide: defaultProvide }, options));
+ };
+
+ const expectTokenRendered = ({
+ testId,
+ expectedLabel,
+ expectedDescription,
+ expectedInputDescription,
+ expectedResetPath,
+ expectedResetConfirmMessage,
+ expectedProps,
+ }) => {
+ const container = extendedWrapper(wrapper.findByTestId(testId));
+
+ expect(container.findByText(expectedLabel, { selector: 'h4' }).exists()).toBe(true);
+ expect(container.findByText(expectedDescription).exists()).toBe(true);
+ expect(container.findByText(expectedInputDescription, { exact: false }).exists()).toBe(true);
+ expect(container.findByText('reset this token').attributes()).toMatchObject({
+ 'data-confirm': expectedResetConfirmMessage,
+ 'data-method': 'put',
+ href: expectedResetPath,
+ });
+ expect(container.props()).toMatchObject(expectedProps);
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders all enabled tokens', () => {
+ createComponent();
+
+ expectTokenRendered({
+ testId: TokensApp.htmlAttributes[FEED_TOKEN].containerTestId,
+ expectedLabel: TokensApp.i18n[FEED_TOKEN].label,
+ expectedDescription: TokensApp.i18n[FEED_TOKEN].description,
+ expectedInputDescription:
+ 'Keep this token secret. Anyone who has it can read activity and issue RSS feeds or your calendar feed as if they were you.',
+ expectedResetPath: defaultProvide.tokenTypes[FEED_TOKEN].resetPath,
+ expectedResetConfirmMessage: TokensApp.i18n[FEED_TOKEN].resetConfirmMessage,
+ expectedProps: {
+ token: defaultProvide.tokenTypes[FEED_TOKEN].token,
+ inputId: TokensApp.htmlAttributes[FEED_TOKEN].inputId,
+ inputLabel: TokensApp.i18n[FEED_TOKEN].label,
+ copyButtonTitle: TokensApp.i18n[FEED_TOKEN].copyButtonTitle,
+ },
+ });
+
+ expectTokenRendered({
+ testId: TokensApp.htmlAttributes[INCOMING_EMAIL_TOKEN].containerTestId,
+ expectedLabel: TokensApp.i18n[INCOMING_EMAIL_TOKEN].label,
+ expectedDescription: TokensApp.i18n[INCOMING_EMAIL_TOKEN].description,
+ expectedInputDescription:
+ 'Keep this token secret. Anyone who has it can create issues as if they were you.',
+ expectedResetPath: defaultProvide.tokenTypes[INCOMING_EMAIL_TOKEN].resetPath,
+ expectedResetConfirmMessage: TokensApp.i18n[INCOMING_EMAIL_TOKEN].resetConfirmMessage,
+ expectedProps: {
+ token: defaultProvide.tokenTypes[INCOMING_EMAIL_TOKEN].token,
+ inputId: TokensApp.htmlAttributes[INCOMING_EMAIL_TOKEN].inputId,
+ inputLabel: TokensApp.i18n[INCOMING_EMAIL_TOKEN].label,
+ copyButtonTitle: TokensApp.i18n[INCOMING_EMAIL_TOKEN].copyButtonTitle,
+ },
+ });
+
+ expectTokenRendered({
+ testId: TokensApp.htmlAttributes[STATIC_OBJECT_TOKEN].containerTestId,
+ expectedLabel: TokensApp.i18n[STATIC_OBJECT_TOKEN].label,
+ expectedDescription: TokensApp.i18n[STATIC_OBJECT_TOKEN].description,
+ expectedInputDescription:
+ 'Keep this token secret. Anyone who has it can access repository static objects as if they were you.',
+ expectedResetPath: defaultProvide.tokenTypes[STATIC_OBJECT_TOKEN].resetPath,
+ expectedResetConfirmMessage: TokensApp.i18n[STATIC_OBJECT_TOKEN].resetConfirmMessage,
+ expectedProps: {
+ token: defaultProvide.tokenTypes[STATIC_OBJECT_TOKEN].token,
+ inputId: TokensApp.htmlAttributes[STATIC_OBJECT_TOKEN].inputId,
+ inputLabel: TokensApp.i18n[STATIC_OBJECT_TOKEN].label,
+ copyButtonTitle: TokensApp.i18n[STATIC_OBJECT_TOKEN].copyButtonTitle,
+ },
+ });
+ });
+
+ it("doesn't render disabled tokens", () => {
+ createComponent({
+ provide: {
+ tokenTypes: {
+ [FEED_TOKEN]: {
+ enabled: false,
+ },
+ },
+ },
+ });
+
+ expect(
+ wrapper.findByTestId(TokensApp.htmlAttributes[FEED_TOKEN].containerTestId).exists(),
+ ).toBe(false);
+ });
+
+ describe('when there are tokens missing an `i18n` definition', () => {
+ it('renders without errors', () => {
+ createComponent({
+ provide: {
+ tokenTypes: {
+ fooBar: {
+ enabled: true,
+ token: 'rewjoa58dfm54jfkdlsdf',
+ resetPath: '/-/profile/foo_bar',
+ },
+ },
+ },
+ });
+
+ expect(
+ wrapper.findByTestId(TokensApp.htmlAttributes[FEED_TOKEN].containerTestId).exists(),
+ ).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/alerts_settings/components/__snapshots__/alerts_form_spec.js.snap b/spec/frontend/alerts_settings/components/__snapshots__/alerts_form_spec.js.snap
index f4d3fd97fd8..ec5b6a5597b 100644
--- a/spec/frontend/alerts_settings/components/__snapshots__/alerts_form_spec.js.snap
+++ b/spec/frontend/alerts_settings/components/__snapshots__/alerts_form_spec.js.snap
@@ -12,6 +12,7 @@ exports[`Alert integration settings form default state should match the default
<gl-form-group-stub
class="gl-pl-0"
labeldescription=""
+ optionaltext="(optional)"
>
<gl-form-checkbox-stub
checked="true"
@@ -28,6 +29,7 @@ exports[`Alert integration settings form default state should match the default
label-for="alert-integration-settings-issue-template"
label-size="sm"
labeldescription=""
+ optionaltext="(optional)"
>
<label
class="gl-display-inline-flex"
@@ -83,6 +85,7 @@ exports[`Alert integration settings form default state should match the default
<gl-form-group-stub
class="gl-pl-0 gl-mb-5"
labeldescription=""
+ optionaltext="(optional)"
>
<gl-form-checkbox-stub>
<span>
@@ -94,6 +97,7 @@ exports[`Alert integration settings form default state should match the default
<gl-form-group-stub
class="gl-pl-0 gl-mb-5"
labeldescription=""
+ optionaltext="(optional)"
>
<gl-form-checkbox-stub
checked="true"
diff --git a/spec/frontend/awards_handler_spec.js b/spec/frontend/awards_handler_spec.js
index 09270174674..c4002ec11f3 100644
--- a/spec/frontend/awards_handler_spec.js
+++ b/spec/frontend/awards_handler_spec.js
@@ -1,15 +1,12 @@
-import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import Cookies from 'js-cookie';
+import { initEmojiMock, clearEmojiMock } from 'helpers/emoji';
import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_frame';
import loadAwardsHandler from '~/awards_handler';
-import { EMOJI_VERSION } from '~/emoji';
-import axios from '~/lib/utils/axios_utils';
window.gl = window.gl || {};
window.gon = window.gon || {};
-let mock;
let awardsHandler = null;
const urlRoot = gon.relative_url_root;
@@ -76,8 +73,7 @@ describe('AwardsHandler', () => {
};
beforeEach(async () => {
- mock = new MockAdapter(axios);
- mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData);
+ await initEmojiMock(emojiData);
loadFixtures('snippets/show.html');
@@ -89,7 +85,7 @@ describe('AwardsHandler', () => {
// restore original url root value
gon.relative_url_root = urlRoot;
- mock.restore();
+ clearEmojiMock();
// Undo what we did to the shared <body>
$('body').removeAttr('data-page');
diff --git a/spec/frontend/behaviors/gl_emoji_spec.js b/spec/frontend/behaviors/gl_emoji_spec.js
index d23a0a84997..0f4e2e08dbd 100644
--- a/spec/frontend/behaviors/gl_emoji_spec.js
+++ b/spec/frontend/behaviors/gl_emoji_spec.js
@@ -1,15 +1,13 @@
-import MockAdapter from 'axios-mock-adapter';
+import { initEmojiMock, clearEmojiMock } from 'helpers/emoji';
import waitForPromises from 'helpers/wait_for_promises';
import installGlEmojiElement from '~/behaviors/gl_emoji';
-import { initEmojiMap, EMOJI_VERSION } from '~/emoji';
+import { EMOJI_VERSION } from '~/emoji';
import * as EmojiUnicodeSupport from '~/emoji/support';
-import axios from '~/lib/utils/axios_utils';
jest.mock('~/emoji/support');
describe('gl_emoji', () => {
- let mock;
const emojiData = {
grey_question: {
c: 'symbols',
@@ -38,15 +36,12 @@ describe('gl_emoji', () => {
return div.firstElementChild;
}
- beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData);
-
- return initEmojiMap().catch(() => {});
+ beforeEach(async () => {
+ await initEmojiMock(emojiData);
});
afterEach(() => {
- mock.restore();
+ clearEmojiMock();
document.body.innerHTML = '';
});
diff --git a/spec/frontend/crm/new_contact_form_spec.js b/spec/frontend/crm/contact_form_spec.js
index 1497c348676..b2753ad8cf5 100644
--- a/spec/frontend/crm/new_contact_form_spec.js
+++ b/spec/frontend/crm/contact_form_spec.js
@@ -4,41 +4,49 @@ import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import NewContactForm from '~/crm/components/new_contact_form.vue';
+import ContactForm from '~/crm/components/contact_form.vue';
import createContactMutation from '~/crm/components/queries/create_contact.mutation.graphql';
+import updateContactMutation from '~/crm/components/queries/update_contact.mutation.graphql';
import getGroupContactsQuery from '~/crm/components/queries/get_group_contacts.query.graphql';
import {
createContactMutationErrorResponse,
createContactMutationResponse,
getGroupContactsQueryResponse,
+ updateContactMutationErrorResponse,
+ updateContactMutationResponse,
} from './mock_data';
-describe('Customer relations contacts root app', () => {
+describe('Customer relations contact form component', () => {
Vue.use(VueApollo);
let wrapper;
let fakeApollo;
+ let mutation;
let queryHandler;
- const findCreateNewContactButton = () => wrapper.findByTestId('create-new-contact-button');
+ const findSaveContactButton = () => wrapper.findByTestId('save-contact-button');
const findCancelButton = () => wrapper.findByTestId('cancel-button');
const findForm = () => wrapper.find('form');
const findError = () => wrapper.findComponent(GlAlert);
- const mountComponent = ({ mountFunction = shallowMountExtended } = {}) => {
- fakeApollo = createMockApollo([[createContactMutation, queryHandler]]);
+ const mountComponent = ({ mountFunction = shallowMountExtended, editForm = false } = {}) => {
+ fakeApollo = createMockApollo([[mutation, queryHandler]]);
fakeApollo.clients.defaultClient.cache.writeQuery({
query: getGroupContactsQuery,
variables: { groupFullPath: 'flightjs' },
data: getGroupContactsQueryResponse.data,
});
- wrapper = mountFunction(NewContactForm, {
+ const propsData = { drawerOpen: true };
+ if (editForm)
+ propsData.contact = { firstName: 'First', lastName: 'Last', email: 'email@example.com' };
+ wrapper = mountFunction(ContactForm, {
provide: { groupId: 26, groupFullPath: 'flightjs' },
apolloProvider: fakeApollo,
- propsData: { drawerOpen: true },
+ propsData,
});
};
beforeEach(() => {
+ mutation = createContactMutation;
queryHandler = jest.fn().mockResolvedValue(createContactMutationResponse);
});
@@ -47,14 +55,14 @@ describe('Customer relations contacts root app', () => {
fakeApollo = null;
});
- describe('Create new contact button', () => {
- it('should be disabled by default', () => {
+ describe('Save contact button', () => {
+ it('should be disabled when required fields are empty', () => {
mountComponent();
- expect(findCreateNewContactButton().attributes('disabled')).toBeTruthy();
+ expect(findSaveContactButton().props('disabled')).toBe(true);
});
- it('should not be disabled when first, last and email have values', async () => {
+ it('should not be disabled when required fields have values', async () => {
mountComponent();
wrapper.find('#contact-first-name').vm.$emit('input', 'A');
@@ -62,7 +70,7 @@ describe('Customer relations contacts root app', () => {
wrapper.find('#contact-email').vm.$emit('input', 'C');
await waitForPromises();
- expect(findCreateNewContactButton().attributes('disabled')).toBeFalsy();
+ expect(findSaveContactButton().props('disabled')).toBe(false);
});
});
@@ -74,7 +82,7 @@ describe('Customer relations contacts root app', () => {
expect(wrapper.emitted().close).toBeTruthy();
});
- describe('when query is successful', () => {
+ describe('when create mutation is successful', () => {
it("should emit 'close'", async () => {
mountComponent();
@@ -85,7 +93,7 @@ describe('Customer relations contacts root app', () => {
});
});
- describe('when query fails', () => {
+ describe('when create mutation fails', () => {
it('should show error on reject', async () => {
queryHandler = jest.fn().mockRejectedValue('ERROR');
mountComponent();
@@ -107,4 +115,43 @@ describe('Customer relations contacts root app', () => {
expect(findError().text()).toBe('Phone is invalid.');
});
});
+
+ describe('when update mutation is successful', () => {
+ it("should emit 'close'", async () => {
+ mutation = updateContactMutation;
+ queryHandler = jest.fn().mockResolvedValue(updateContactMutationResponse);
+ mountComponent({ editForm: true });
+
+ findForm().trigger('submit');
+ await waitForPromises();
+
+ expect(wrapper.emitted().close).toBeTruthy();
+ });
+ });
+
+ describe('when update mutation fails', () => {
+ beforeEach(() => {
+ mutation = updateContactMutation;
+ });
+
+ it('should show error on reject', async () => {
+ queryHandler = jest.fn().mockRejectedValue('ERROR');
+ mountComponent({ editForm: true });
+ findForm().trigger('submit');
+ await waitForPromises();
+
+ expect(findError().exists()).toBe(true);
+ });
+
+ it('should show error on error response', async () => {
+ queryHandler = jest.fn().mockResolvedValue(updateContactMutationErrorResponse);
+ mountComponent({ editForm: true });
+
+ findForm().trigger('submit');
+ await waitForPromises();
+
+ expect(findError().exists()).toBe(true);
+ expect(findError().text()).toBe('Email is invalid.');
+ });
+ });
});
diff --git a/spec/frontend/crm/contacts_root_spec.js b/spec/frontend/crm/contacts_root_spec.js
index 18bd2d7c45b..b30349305a3 100644
--- a/spec/frontend/crm/contacts_root_spec.js
+++ b/spec/frontend/crm/contacts_root_spec.js
@@ -6,8 +6,10 @@ import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_help
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import ContactsRoot from '~/crm/components/contacts_root.vue';
-import NewContactForm from '~/crm/components/new_contact_form.vue';
+import ContactForm from '~/crm/components/contact_form.vue';
import getGroupContactsQuery from '~/crm/components/queries/get_group_contacts.query.graphql';
+import { NEW_ROUTE_NAME, EDIT_ROUTE_NAME } from '~/crm/constants';
+import routes from '~/crm/routes';
import { getGroupContactsQueryResponse } from './mock_data';
describe('Customer relations contacts root app', () => {
@@ -21,7 +23,8 @@ describe('Customer relations contacts root app', () => {
const findRowByName = (rowName) => wrapper.findAllByRole('row', { name: rowName });
const findIssuesLinks = () => wrapper.findAllByTestId('issues-link');
const findNewContactButton = () => wrapper.findByTestId('new-contact-button');
- const findNewContactForm = () => wrapper.findComponent(NewContactForm);
+ const findEditContactButton = () => wrapper.findByTestId('edit-contact-button');
+ const findContactForm = () => wrapper.findComponent(ContactForm);
const findError = () => wrapper.findComponent(GlAlert);
const successQueryHandler = jest.fn().mockResolvedValue(getGroupContactsQueryResponse);
@@ -49,7 +52,7 @@ describe('Customer relations contacts root app', () => {
router = new VueRouter({
base: basePath,
mode: 'history',
- routes: [],
+ routes,
});
});
@@ -79,12 +82,12 @@ describe('Customer relations contacts root app', () => {
});
});
- describe('new contact form', () => {
+ describe('contact form', () => {
it('should not exist by default', async () => {
mountComponent();
await waitForPromises();
- expect(findNewContactForm().exists()).toBe(false);
+ expect(findContactForm().exists()).toBe(false);
});
it('should exist when user clicks new contact button', async () => {
@@ -93,25 +96,54 @@ describe('Customer relations contacts root app', () => {
findNewContactButton().vm.$emit('click');
await waitForPromises();
- expect(findNewContactForm().exists()).toBe(true);
+ expect(findContactForm().exists()).toBe(true);
});
- it('should exist when user navigates directly to /new', async () => {
- router.replace({ path: '/new' });
+ it('should exist when user navigates directly to `new` route', async () => {
+ router.replace({ name: NEW_ROUTE_NAME });
mountComponent();
await waitForPromises();
- expect(findNewContactForm().exists()).toBe(true);
+ expect(findContactForm().exists()).toBe(true);
});
- it('should not exist when form emits close', async () => {
- router.replace({ path: '/new' });
+ it('should exist when user clicks edit contact button', async () => {
+ mountComponent({ mountFunction: mountExtended });
+ await waitForPromises();
+
+ findEditContactButton().vm.$emit('click');
+ await waitForPromises();
+
+ expect(findContactForm().exists()).toBe(true);
+ });
+
+ it('should exist when user navigates directly to `edit` route', async () => {
+ router.replace({ name: EDIT_ROUTE_NAME, params: { id: 16 } });
mountComponent();
+ await waitForPromises();
+
+ expect(findContactForm().exists()).toBe(true);
+ });
+
+ it('should not exist when new form emits close', async () => {
+ router.replace({ name: NEW_ROUTE_NAME });
+ mountComponent();
+
+ findContactForm().vm.$emit('close');
+ await waitForPromises();
+
+ expect(findContactForm().exists()).toBe(false);
+ });
+
+ it('should not exist when edit form emits close', async () => {
+ router.replace({ name: EDIT_ROUTE_NAME, params: { id: 16 } });
+ mountComponent();
+ await waitForPromises();
- findNewContactForm().vm.$emit('close');
+ findContactForm().vm.$emit('close');
await waitForPromises();
- expect(findNewContactForm().exists()).toBe(false);
+ expect(findContactForm().exists()).toBe(false);
});
});
diff --git a/spec/frontend/crm/mock_data.js b/spec/frontend/crm/mock_data.js
index e784ac3764d..3abbc488081 100644
--- a/spec/frontend/crm/mock_data.js
+++ b/spec/frontend/crm/mock_data.js
@@ -106,3 +106,31 @@ export const createContactMutationErrorResponse = {
},
},
};
+
+export const updateContactMutationResponse = {
+ data: {
+ customerRelationsContactUpdate: {
+ __typeName: 'CustomerRelationsContactCreatePayload',
+ contact: {
+ __typename: 'CustomerRelationsContact',
+ id: 'gid://gitlab/CustomerRelations::Contact/1',
+ firstName: 'First',
+ lastName: 'Last',
+ email: 'email@example.com',
+ phone: null,
+ description: null,
+ organization: null,
+ },
+ errors: [],
+ },
+ },
+};
+
+export const updateContactMutationErrorResponse = {
+ data: {
+ customerRelationsContactUpdate: {
+ contact: null,
+ errors: ['Email is invalid.'],
+ },
+ },
+};
diff --git a/spec/frontend/emoji/index_spec.js b/spec/frontend/emoji/index_spec.js
index 9652c513671..cc037586496 100644
--- a/spec/frontend/emoji/index_spec.js
+++ b/spec/frontend/emoji/index_spec.js
@@ -1,6 +1,21 @@
-import { emojiFixtureMap, mockEmojiData, initEmojiMock } from 'helpers/emoji';
+import {
+ emojiFixtureMap,
+ mockEmojiData,
+ initEmojiMock,
+ validEmoji,
+ invalidEmoji,
+ clearEmojiMock,
+} from 'helpers/emoji';
import { trimText } from 'helpers/text_helper';
-import { glEmojiTag, searchEmoji, getEmojiInfo, sortEmoji } from '~/emoji';
+import {
+ glEmojiTag,
+ searchEmoji,
+ getEmojiInfo,
+ sortEmoji,
+ initEmojiMap,
+ getAllEmoji,
+} from '~/emoji';
+
import isEmojiUnicodeSupported, {
isFlagEmoji,
isRainbowFlagEmoji,
@@ -9,7 +24,6 @@ import isEmojiUnicodeSupported, {
isHorceRacingSkinToneComboEmoji,
isPersonZwjEmoji,
} from '~/emoji/support/is_emoji_unicode_supported';
-import { sanitize } from '~/lib/dompurify';
const emptySupportMap = {
personZwj: false,
@@ -31,14 +45,55 @@ const emptySupportMap = {
};
describe('emoji', () => {
- let mock;
-
beforeEach(async () => {
- mock = await initEmojiMock();
+ await initEmojiMock();
});
afterEach(() => {
- mock.restore();
+ clearEmojiMock();
+ });
+
+ describe('initEmojiMap', () => {
+ it('should contain valid emoji', async () => {
+ await initEmojiMap();
+
+ const allEmoji = Object.keys(getAllEmoji());
+ Object.keys(validEmoji).forEach((key) => {
+ expect(allEmoji.includes(key)).toBe(true);
+ });
+ });
+
+ it('should not contain invalid emoji', async () => {
+ await initEmojiMap();
+
+ const allEmoji = Object.keys(getAllEmoji());
+ Object.keys(invalidEmoji).forEach((key) => {
+ expect(allEmoji.includes(key)).toBe(false);
+ });
+ });
+
+ it('fixes broken pride emoji', async () => {
+ clearEmojiMock();
+ await initEmojiMock({
+ gay_pride_flag: {
+ c: 'flags',
+ // Without a zero-width joiner
+ e: '🏳🌈',
+ name: 'gay_pride_flag',
+ u: '6.0',
+ },
+ });
+
+ expect(getAllEmoji()).toEqual({
+ gay_pride_flag: {
+ c: 'flags',
+ // With a zero-width joiner
+ e: '🏳️‍🌈',
+ name: 'gay_pride_flag',
+ u: '6.0',
+ },
+ });
+ });
});
describe('glEmojiTag', () => {
@@ -378,32 +433,14 @@ describe('emoji', () => {
});
describe('searchEmoji', () => {
- const emojiFixture = Object.keys(mockEmojiData).reduce((acc, k) => {
- const { name, e, u, d } = mockEmojiData[k];
- acc[k] = { name, e: sanitize(e), u, d };
-
- return acc;
- }, {});
-
it.each([undefined, null, ''])("should return all emoji when the input is '%s'", (input) => {
const search = searchEmoji(input);
- const expected = [
- 'atom',
- 'bomb',
- 'construction_worker_tone5',
- 'five',
- 'grey_question',
- 'black_heart',
- 'heart',
- 'custard',
- 'star',
- 'xss',
- ].map((name) => {
+ const expected = Object.keys(validEmoji).map((name) => {
return {
- emoji: emojiFixture[name],
+ emoji: mockEmojiData[name],
field: 'd',
- fieldValue: emojiFixture[name].d,
+ fieldValue: mockEmojiData[name].d,
score: 0,
};
});
@@ -453,7 +490,7 @@ describe('emoji', () => {
const { field, score, fieldValue, name } = item;
return {
- emoji: emojiFixture[name],
+ emoji: mockEmojiData[name],
field,
fieldValue,
score,
@@ -564,9 +601,9 @@ describe('emoji', () => {
const { field, score, name } = item;
return {
- emoji: emojiFixture[name],
+ emoji: mockEmojiData[name],
field,
- fieldValue: emojiFixture[name][field],
+ fieldValue: mockEmojiData[name][field],
score,
};
});
@@ -622,13 +659,4 @@ describe('emoji', () => {
expect(sortEmoji(scoredItems)).toEqual(expected);
});
});
-
- describe('sanitize emojis', () => {
- it('should return sanitized emoji', () => {
- expect(getEmojiInfo('xss')).toEqual({
- ...mockEmojiData.xss,
- e: '<img src="x">',
- });
- });
- });
});
diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js
index 631e3307f7f..1ab3286fe4c 100644
--- a/spec/frontend/gfm_auto_complete_spec.js
+++ b/spec/frontend/gfm_auto_complete_spec.js
@@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import labelsFixture from 'test_fixtures/autocomplete_sources/labels.json';
import GfmAutoComplete, { membersBeforeSave, highlighter } from 'ee_else_ce/gfm_auto_complete';
-import { initEmojiMock } from 'helpers/emoji';
+import { initEmojiMock, clearEmojiMock } from 'helpers/emoji';
import '~/lib/utils/jquery_at_who';
import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
@@ -803,8 +803,6 @@ describe('GfmAutoComplete', () => {
});
describe('emoji', () => {
- let mock;
-
const mockItem = {
'atwho-at': ':',
emoji: {
@@ -818,14 +816,14 @@ describe('GfmAutoComplete', () => {
};
beforeEach(async () => {
- mock = await initEmojiMock();
+ await initEmojiMock();
await new GfmAutoComplete({}).loadEmojiData({ atwho() {}, trigger() {} }, ':');
if (!GfmAutoComplete.glEmojiTag) throw new Error('emoji not loaded');
});
afterEach(() => {
- mock.restore();
+ clearEmojiMock();
});
describe('Emoji.templateFunction', () => {
diff --git a/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap b/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap
index 33e2c0db5e5..9447e7daba8 100644
--- a/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap
+++ b/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap
@@ -47,6 +47,7 @@ exports[`grafana integration component default state to match the default snapsh
label="Enable authentication"
label-for="grafana-integration-enabled"
labeldescription=""
+ optionaltext="(optional)"
>
<gl-form-checkbox-stub
id="grafana-integration-enabled"
@@ -62,6 +63,7 @@ exports[`grafana integration component default state to match the default snapsh
label="Grafana URL"
label-for="grafana-url"
labeldescription=""
+ optionaltext="(optional)"
>
<gl-form-input-stub
id="grafana-url"
@@ -74,6 +76,7 @@ exports[`grafana integration component default state to match the default snapsh
label="API token"
label-for="grafana-token"
labeldescription=""
+ optionaltext="(optional)"
>
<gl-form-input-stub
id="grafana-token"
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap
index 174a42c5dc2..feee14c9c40 100644
--- a/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap
+++ b/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap
@@ -14,6 +14,7 @@ exports[`Alert integration settings form should match the default snapshot 1`] =
<gl-form-group-stub
class="col-8 col-md-9 gl-p-0"
labeldescription=""
+ optionaltext="(optional)"
>
<gl-toggle-stub
id="active"
@@ -28,6 +29,7 @@ exports[`Alert integration settings form should match the default snapshot 1`] =
label="Webhook URL"
label-for="url"
labeldescription=""
+ optionaltext="(optional)"
>
<gl-form-input-group-stub
data-testid="webhook-url"
diff --git a/spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap b/spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap
index 1a874c3dcd6..c968c28c811 100644
--- a/spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap
+++ b/spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap
@@ -52,6 +52,7 @@ exports[`self monitor component When the self monitor project has not been creat
<gl-form-group-stub
labeldescription=""
+ optionaltext="(optional)"
>
<gl-toggle-stub
label="Self monitoring"
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
index 7577c65132b..d7261784edc 100644
--- a/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js
+++ b/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js
@@ -1,6 +1,6 @@
import { GlModal, GlFormCheckbox } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { initEmojiMock } from 'helpers/emoji';
+import { initEmojiMock, clearEmojiMock } from 'helpers/emoji';
import * as UserApi from '~/api/user_api';
import EmojiPicker from '~/emoji/components/picker.vue';
import createFlash from '~/flash';
@@ -12,7 +12,6 @@ jest.mock('~/flash');
describe('SetStatusModalWrapper', () => {
let wrapper;
- let mockEmoji;
const $toast = {
show: jest.fn(),
};
@@ -63,12 +62,12 @@ describe('SetStatusModalWrapper', () => {
afterEach(() => {
wrapper.destroy();
- mockEmoji.restore();
+ clearEmojiMock();
});
describe('with minimum props', () => {
beforeEach(async () => {
- mockEmoji = await initEmojiMock();
+ await initEmojiMock();
wrapper = createComponent();
return initModal();
});
@@ -112,7 +111,7 @@ describe('SetStatusModalWrapper', () => {
describe('improvedEmojiPicker is true', () => {
beforeEach(async () => {
- mockEmoji = await initEmojiMock();
+ await initEmojiMock();
wrapper = createComponent({}, true);
return initModal();
});
@@ -126,7 +125,7 @@ describe('SetStatusModalWrapper', () => {
describe('with no currentMessage set', () => {
beforeEach(async () => {
- mockEmoji = await initEmojiMock();
+ await initEmojiMock();
wrapper = createComponent({ currentMessage: '' });
return initModal();
});
@@ -146,7 +145,7 @@ describe('SetStatusModalWrapper', () => {
describe('with no currentEmoji set', () => {
beforeEach(async () => {
- mockEmoji = await initEmojiMock();
+ await initEmojiMock();
wrapper = createComponent({ currentEmoji: '' });
return initModal();
});
@@ -161,7 +160,7 @@ describe('SetStatusModalWrapper', () => {
describe('with no currentMessage set', () => {
beforeEach(async () => {
- mockEmoji = await initEmojiMock();
+ await initEmojiMock();
wrapper = createComponent({ currentEmoji: '', currentMessage: '' });
return initModal();
});
@@ -174,7 +173,7 @@ describe('SetStatusModalWrapper', () => {
describe('with currentClearStatusAfter set', () => {
beforeEach(async () => {
- mockEmoji = await initEmojiMock();
+ await initEmojiMock();
wrapper = createComponent({ currentClearStatusAfter: '2021-01-01 00:00:00 UTC' });
return initModal();
});
@@ -190,7 +189,7 @@ describe('SetStatusModalWrapper', () => {
describe('update status', () => {
describe('succeeds', () => {
beforeEach(async () => {
- mockEmoji = await initEmojiMock();
+ await initEmojiMock();
wrapper = createComponent();
await initModal();
@@ -246,7 +245,7 @@ describe('SetStatusModalWrapper', () => {
describe('success message', () => {
beforeEach(async () => {
- mockEmoji = await initEmojiMock();
+ await initEmojiMock();
wrapper = createComponent({ currentEmoji: '', currentMessage: '' });
jest.spyOn(UserApi, 'updateUserStatus').mockResolvedValue();
return initModal({ mockOnUpdateSuccess: false });
@@ -262,7 +261,7 @@ describe('SetStatusModalWrapper', () => {
describe('with errors', () => {
beforeEach(async () => {
- mockEmoji = await initEmojiMock();
+ await initEmojiMock();
wrapper = createComponent();
await initModal();
@@ -279,7 +278,7 @@ describe('SetStatusModalWrapper', () => {
describe('error message', () => {
beforeEach(async () => {
- mockEmoji = await initEmojiMock();
+ await initEmojiMock();
wrapper = createComponent({ currentEmoji: '', currentMessage: '' });
jest.spyOn(UserApi, 'updateUserStatus').mockRejectedValue();
return initModal({ mockOnUpdateFailure: false });
diff --git a/spec/frontend/shortcuts_spec.js b/spec/frontend/shortcuts_spec.js
index 455db325066..49148123a1c 100644
--- a/spec/frontend/shortcuts_spec.js
+++ b/spec/frontend/shortcuts_spec.js
@@ -25,6 +25,7 @@ describe('Shortcuts', () => {
jest.spyOn(document.querySelector('.js-new-note-form .js-md-preview-button'), 'focus');
jest.spyOn(document.querySelector('.edit-note .js-md-preview-button'), 'focus');
+ jest.spyOn(document.querySelector('#search'), 'focus');
new Shortcuts(); // eslint-disable-line no-new
});
@@ -111,4 +112,12 @@ describe('Shortcuts', () => {
});
});
});
+
+ describe('focusSearch', () => {
+ it('focuses the search bar', () => {
+ Shortcuts.focusSearch(createEvent('KeyboardEvent'));
+
+ expect(document.querySelector('#search').focus).toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap b/spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap
index 5df69ffb5f8..f4ebc5c3e3f 100644
--- a/spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap
+++ b/spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap
@@ -23,6 +23,7 @@ exports[`Snippet Visibility Edit component rendering matches the snapshot 1`] =
class="gl-mb-0"
id="visibility-level-setting"
labeldescription=""
+ optionaltext="(optional)"
>
<gl-form-radio-group-stub
checked="private"
diff --git a/spec/frontend/vue_shared/components/form/__snapshots__/title_spec.js.snap b/spec/frontend/vue_shared/components/form/__snapshots__/title_spec.js.snap
index ff1dad2de68..58ad1f681bc 100644
--- a/spec/frontend/vue_shared/components/form/__snapshots__/title_spec.js.snap
+++ b/spec/frontend/vue_shared/components/form/__snapshots__/title_spec.js.snap
@@ -5,6 +5,7 @@ exports[`Title edit field matches the snapshot 1`] = `
label="Title"
label-for="title-field-edit"
labeldescription=""
+ optionaltext="(optional)"
>
<gl-form-input-stub />
</gl-form-group-stub>