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>2023-07-24 15:09:32 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-07-24 15:09:32 +0300
commitf296f23500b4b3758670ae0c5ce2e1779f533e8b (patch)
tree717151cb9e81d489b4ecf880988ea10d77b7224f /spec/frontend
parentfd7c75bf603f4f2f1a4a4e63ef5cbc1a51cc0a15 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/groups/service/archived_projects_service_spec.js37
-rw-r--r--spec/frontend/import_entities/import_groups/components/import_actions_cell_spec.js48
-rw-r--r--spec/frontend/sessions/new/components/email_verification_spec.js205
-rw-r--r--spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js6
-rw-r--r--spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js27
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_links_spec.js12
-rw-r--r--spec/frontend/work_items/mock_data.js3
7 files changed, 298 insertions, 40 deletions
diff --git a/spec/frontend/groups/service/archived_projects_service_spec.js b/spec/frontend/groups/service/archived_projects_service_spec.js
index 3aec9d57ee1..8e9dfb0f971 100644
--- a/spec/frontend/groups/service/archived_projects_service_spec.js
+++ b/spec/frontend/groups/service/archived_projects_service_spec.js
@@ -18,11 +18,9 @@ describe('ArchivedProjectsService', () => {
const query = 'git';
const sort = 'created_asc';
- beforeEach(() => {
+ it('returns promise the resolves with formatted project', async () => {
Api.groupProjects.mockResolvedValueOnce({ data: projects, headers });
- });
- it('returns promise the resolves with formatted project', async () => {
await expect(service.getGroups(undefined, page, query, sort)).resolves.toEqual({
data: projects.map((project) => {
return {
@@ -47,7 +45,7 @@ describe('ArchivedProjectsService', () => {
number_users_with_delimiter: 0,
star_count: project.star_count,
updated_at: project.updated_at,
- marked_for_deletion: project.marked_for_deletion_at !== null,
+ marked_for_deletion: false,
last_activity_at: project.last_activity_at,
};
}),
@@ -63,6 +61,35 @@ describe('ArchivedProjectsService', () => {
});
describe.each`
+ markedForDeletionAt | expected
+ ${null} | ${false}
+ ${undefined} | ${false}
+ ${'2023-07-21'} | ${true}
+ `(
+ 'when `marked_for_deletion_at` is $markedForDeletionAt',
+ ({ markedForDeletionAt, expected }) => {
+ it(`sets marked_for_deletion to ${expected}`, async () => {
+ Api.groupProjects.mockResolvedValueOnce({
+ data: projects.map((project) => ({
+ ...project,
+ marked_for_deletion_at: markedForDeletionAt,
+ })),
+ headers,
+ });
+
+ await expect(service.getGroups(undefined, page, query, sort)).resolves.toMatchObject({
+ data: projects.map(() => {
+ return {
+ marked_for_deletion: expected,
+ };
+ }),
+ headers,
+ });
+ });
+ },
+ );
+
+ describe.each`
sortArgument | expectedOrderByParameter | expectedSortParameter
${'name_asc'} | ${'name'} | ${'asc'}
${'name_desc'} | ${'name'} | ${'desc'}
@@ -75,6 +102,8 @@ describe('ArchivedProjectsService', () => {
'when the sort argument is $sortArgument',
({ sortArgument, expectedSortParameter, expectedOrderByParameter }) => {
it(`calls the API with sort parameter set to ${expectedSortParameter} and order_by parameter set to ${expectedOrderByParameter}`, () => {
+ Api.groupProjects.mockResolvedValueOnce({ data: projects, headers });
+
service.getGroups(undefined, page, query, sortArgument);
expect(Api.groupProjects).toHaveBeenCalledWith(groupId, query, {
diff --git a/spec/frontend/import_entities/import_groups/components/import_actions_cell_spec.js b/spec/frontend/import_entities/import_groups/components/import_actions_cell_spec.js
index 4c13ec555c2..87bee6afd62 100644
--- a/spec/frontend/import_entities/import_groups/components/import_actions_cell_spec.js
+++ b/spec/frontend/import_entities/import_groups/components/import_actions_cell_spec.js
@@ -1,4 +1,10 @@
-import { GlDropdown, GlIcon, GlDropdownItem } from '@gitlab/ui';
+import {
+ GlDisclosureDropdown,
+ GlDisclosureDropdownItem,
+ GlButtonGroup,
+ GlButton,
+ GlIcon,
+} from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import ImportActionsCell from '~/import_entities/import_groups/components/import_actions_cell.vue';
@@ -13,6 +19,11 @@ describe('import actions cell', () => {
isInvalid: false,
...props,
},
+ stubs: {
+ GlButtonGroup,
+ GlDisclosureDropdown,
+ GlDisclosureDropdownItem,
+ },
});
};
@@ -22,9 +33,9 @@ describe('import actions cell', () => {
});
it('renders import dropdown', () => {
- const dropdown = wrapper.findComponent(GlDropdown);
- expect(dropdown.exists()).toBe(true);
- expect(dropdown.props('text')).toBe('Import with projects');
+ const button = wrapper.findComponent(GlButton);
+ expect(button.exists()).toBe(true);
+ expect(button.text()).toBe('Import with projects');
});
it('does not render icon with a hint', () => {
@@ -38,9 +49,9 @@ describe('import actions cell', () => {
});
it('renders re-import dropdown', () => {
- const dropdown = wrapper.findComponent(GlDropdown);
- expect(dropdown.exists()).toBe(true);
- expect(dropdown.props('text')).toBe('Re-import with projects');
+ const button = wrapper.findComponent(GlButton);
+ expect(button.exists()).toBe(true);
+ expect(button.text()).toBe('Re-import with projects');
});
it('renders icon with a hint', () => {
@@ -55,22 +66,22 @@ describe('import actions cell', () => {
it('does not render import dropdown when group is not available for import', () => {
createComponent({ isAvailableForImport: false });
- const dropdown = wrapper.findComponent(GlDropdown);
+ const dropdown = wrapper.findComponent(GlDisclosureDropdown);
expect(dropdown.exists()).toBe(false);
});
it('renders import dropdown as disabled when group is invalid', () => {
createComponent({ isInvalid: true, isAvailableForImport: true });
- const dropdown = wrapper.findComponent(GlDropdown);
+ const dropdown = wrapper.findComponent(GlDisclosureDropdown);
expect(dropdown.props().disabled).toBe(true);
});
it('emits import-group event when import button is clicked', () => {
createComponent({ isAvailableForImport: true });
- const dropdown = wrapper.findComponent(GlDropdown);
- dropdown.vm.$emit('click');
+ const button = wrapper.findComponent(GlButton);
+ button.vm.$emit('click');
expect(wrapper.emitted('import-group')).toHaveLength(1);
});
@@ -87,23 +98,24 @@ describe('import actions cell', () => {
});
it('render import dropdown', () => {
- const dropdown = wrapper.findComponent(GlDropdown);
- expect(dropdown.props('text')).toBe(`${expectedAction} with projects`);
- expect(dropdown.findComponent(GlDropdownItem).text()).toBe(
+ const button = wrapper.findComponent(GlButton);
+ const dropdown = wrapper.findComponent(GlDisclosureDropdown);
+ expect(button.element).toHaveText(`${expectedAction} with projects`);
+ expect(dropdown.findComponent(GlDisclosureDropdownItem).text()).toBe(
`${expectedAction} without projects`,
);
});
it('request migrate projects by default', () => {
- const dropdown = wrapper.findComponent(GlDropdown);
- dropdown.vm.$emit('click');
+ const button = wrapper.findComponent(GlButton);
+ button.vm.$emit('click');
expect(wrapper.emitted('import-group')[0]).toStrictEqual([{ migrateProjects: true }]);
});
it('request not to migrate projects via dropdown option', () => {
- const dropdown = wrapper.findComponent(GlDropdown);
- dropdown.findComponent(GlDropdownItem).vm.$emit('click');
+ const dropdown = wrapper.findComponent(GlDisclosureDropdown);
+ dropdown.findComponent(GlDisclosureDropdownItem).vm.$emit('action');
expect(wrapper.emitted('import-group')[0]).toStrictEqual([{ migrateProjects: false }]);
});
diff --git a/spec/frontend/sessions/new/components/email_verification_spec.js b/spec/frontend/sessions/new/components/email_verification_spec.js
new file mode 100644
index 00000000000..8ff139e8475
--- /dev/null
+++ b/spec/frontend/sessions/new/components/email_verification_spec.js
@@ -0,0 +1,205 @@
+import { GlForm, GlFormInput } from '@gitlab/ui';
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { s__ } from '~/locale';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
+import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status';
+import EmailVerification from '~/sessions/new/components/email_verification.vue';
+import { visitUrl } from '~/lib/utils/url_utility';
+import {
+ I18N_EMAIL_EMPTY_CODE,
+ I18N_EMAIL_INVALID_CODE,
+ I18N_GENERIC_ERROR,
+ I18N_RESEND_LINK,
+ I18N_EMAIL_RESEND_SUCCESS,
+} from '~/sessions/new/constants';
+
+jest.mock('~/alert');
+jest.mock('~/lib/utils/url_utility', () => ({
+ ...jest.requireActual('~/lib/utils/url_utility'),
+ visitUrl: jest.fn(),
+}));
+
+describe('EmailVerification', () => {
+ let wrapper;
+ let axiosMock;
+
+ const defaultPropsData = {
+ obfuscatedEmail: 'al**@g*****.com',
+ verifyPath: '/users/sign_in',
+ resendPath: '/users/resend_verification_code',
+ };
+
+ const createComponent = () => {
+ wrapper = mountExtended(EmailVerification, {
+ propsData: defaultPropsData,
+ });
+ };
+
+ const findForm = () => wrapper.findComponent(GlForm);
+ const findCodeInput = () => wrapper.findComponent(GlFormInput);
+ const findSubmitButton = () => wrapper.find('[type="submit"]');
+ const findResendLink = () => wrapper.findByText(I18N_RESEND_LINK);
+ const enterCode = (code) => findCodeInput().setValue(code);
+ const submitForm = () => findForm().trigger('submit');
+
+ beforeEach(() => {
+ axiosMock = new MockAdapter(axios);
+ createComponent();
+ });
+
+ afterEach(() => {
+ createAlert.mockClear();
+ axiosMock.restore();
+ });
+
+ describe('rendering the form', () => {
+ it('contains the obfuscated email address', () => {
+ expect(wrapper.text()).toContain(defaultPropsData.obfuscatedEmail);
+ });
+ });
+
+ describe('verifying the code', () => {
+ describe('when successfully verifying the code', () => {
+ const redirectPath = 'root';
+
+ beforeEach(async () => {
+ enterCode('123456');
+
+ axiosMock
+ .onPost(defaultPropsData.verifyPath)
+ .reply(HTTP_STATUS_OK, { status: 'success', redirect_path: redirectPath });
+
+ await submitForm();
+ await axios.waitForAll();
+ });
+
+ it('redirects to the returned redirect path', () => {
+ expect(visitUrl).toHaveBeenCalledWith(redirectPath);
+ });
+ });
+
+ describe('error messages', () => {
+ it.each`
+ scenario | code | submit | codeValid | errorShown | message
+ ${'shows no error messages before submitting the form'} | ${''} | ${false} | ${false} | ${false} | ${null}
+ ${'shows no error messages before submitting the form'} | ${'xxx'} | ${false} | ${false} | ${false} | ${null}
+ ${'shows no error messages before submitting the form'} | ${'123456'} | ${false} | ${true} | ${false} | ${null}
+ ${'shows empty code error message when submitting the form'} | ${''} | ${true} | ${false} | ${true} | ${I18N_EMAIL_EMPTY_CODE}
+ ${'shows invalid error message when submitting the form'} | ${'xxx'} | ${true} | ${false} | ${true} | ${I18N_EMAIL_INVALID_CODE}
+ ${'shows incorrect code error message returned from the server'} | ${'123456'} | ${true} | ${true} | ${true} | ${s__('IdentityVerification|The code is incorrect. Enter it again, or send a new code.')}
+ `(`$scenario with code $code`, async ({ code, submit, codeValid, errorShown, message }) => {
+ enterCode(code);
+
+ if (submit && codeValid) {
+ axiosMock
+ .onPost(defaultPropsData.verifyPath)
+ .replyOnce(HTTP_STATUS_OK, { status: 'failure', message });
+ }
+
+ if (submit) {
+ await submitForm();
+ await axios.waitForAll();
+ }
+
+ expect(findCodeInput().classes('is-invalid')).toBe(errorShown);
+ expect(findSubmitButton().props('disabled')).toBe(errorShown);
+ if (message) expect(wrapper.text()).toContain(message);
+ });
+
+ it('keeps showing error messages for invalid codes after submitting the form', async () => {
+ const serverErrorMessage = 'error message';
+
+ enterCode('123456');
+
+ axiosMock
+ .onPost(defaultPropsData.verifyPath)
+ .replyOnce(HTTP_STATUS_OK, { status: 'failure', message: serverErrorMessage });
+
+ await submitForm();
+ await axios.waitForAll();
+
+ expect(wrapper.text()).toContain(serverErrorMessage);
+
+ await enterCode('');
+ expect(wrapper.text()).toContain(I18N_EMAIL_EMPTY_CODE);
+
+ await enterCode('xxx');
+ expect(wrapper.text()).toContain(I18N_EMAIL_INVALID_CODE);
+ });
+
+ it('captures the error and shows an alert message when the request failed', async () => {
+ enterCode('123456');
+
+ axiosMock.onPost(defaultPropsData.verifyPath).replyOnce(HTTP_STATUS_OK, null);
+
+ await submitForm();
+ await axios.waitForAll();
+
+ expect(createAlert).toHaveBeenCalledWith({
+ message: I18N_GENERIC_ERROR,
+ captureError: true,
+ error: expect.any(Error),
+ });
+ });
+
+ it('captures the error and shows an alert message when the request undefined', async () => {
+ enterCode('123456');
+
+ axiosMock.onPost(defaultPropsData.verifyPath).reply(HTTP_STATUS_OK, { status: undefined });
+
+ await submitForm();
+ await axios.waitForAll();
+
+ expect(createAlert).toHaveBeenCalledWith({
+ message: I18N_GENERIC_ERROR,
+ captureError: true,
+ error: undefined,
+ });
+ });
+ });
+ });
+
+ describe('resending the code', () => {
+ const failedMessage = 'Failure sending the code';
+ const successAlertObject = {
+ message: I18N_EMAIL_RESEND_SUCCESS,
+ variant: VARIANT_SUCCESS,
+ };
+ const failedAlertObject = {
+ message: failedMessage,
+ };
+ const undefinedAlertObject = {
+ captureError: true,
+ error: undefined,
+ message: I18N_GENERIC_ERROR,
+ };
+ const genericAlertObject = {
+ message: I18N_GENERIC_ERROR,
+ captureError: true,
+ error: expect.any(Error),
+ };
+
+ it.each`
+ scenario | statusCode | response | alertObject
+ ${'the code was successfully resend'} | ${HTTP_STATUS_OK} | ${{ status: 'success' }} | ${successAlertObject}
+ ${'there was a problem resending the code'} | ${HTTP_STATUS_OK} | ${{ status: 'failure', message: failedMessage }} | ${failedAlertObject}
+ ${'when the request is undefined'} | ${HTTP_STATUS_OK} | ${{ status: undefined }} | ${undefinedAlertObject}
+ ${'when the request failed'} | ${HTTP_STATUS_NOT_FOUND} | ${null} | ${genericAlertObject}
+ `(`shows an alert message when $scenario`, async ({ statusCode, response, alertObject }) => {
+ enterCode('xxx');
+
+ await submitForm();
+
+ axiosMock.onPost(defaultPropsData.resendPath).replyOnce(statusCode, response);
+
+ findResendLink().trigger('click');
+
+ await axios.waitForAll();
+
+ expect(createAlert).toHaveBeenCalledWith(alertObject);
+ expect(findCodeInput().element.value).toBe('');
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js b/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js
index e983519d9fc..03f509a3fa3 100644
--- a/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js
+++ b/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js
@@ -1,8 +1,13 @@
import { mount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
import IssuableCreateRoot from '~/vue_shared/issuable/create/components/issuable_create_root.vue';
import IssuableForm from '~/vue_shared/issuable/create/components/issuable_form.vue';
+Vue.use(VueApollo);
+
const createComponent = ({
descriptionPreviewPath = '/gitlab-org/gitlab-shell/preview_markdown',
descriptionHelpPath = '/help/user/markdown',
@@ -16,6 +21,7 @@ const createComponent = ({
labelsFetchPath,
labelsManagePath,
},
+ apolloProvider: createMockApollo(),
slots: {
title: `
<h1 class="js-create-title">New Issuable</h1>
diff --git a/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js b/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js
index ae2fd5ebffa..338dc80b43e 100644
--- a/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js
+++ b/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js
@@ -2,8 +2,9 @@ import { GlFormInput } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import IssuableForm from '~/vue_shared/issuable/create/components/issuable_form.vue';
-import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import LabelsSelect from '~/sidebar/components/labels/labels_select_vue/labels_select_root.vue';
+import { __ } from '~/locale';
const createComponent = ({
descriptionPreviewPath = '/gitlab-org/gitlab-shell/preview_markdown',
@@ -24,7 +25,7 @@ const createComponent = ({
`,
},
stubs: {
- MarkdownField,
+ MarkdownEditor,
},
});
};
@@ -71,18 +72,20 @@ describe('IssuableForm', () => {
expect(descriptionFieldEl.exists()).toBe(true);
expect(descriptionFieldEl.find('label').text()).toBe('Description');
- expect(descriptionFieldEl.findComponent(MarkdownField).exists()).toBe(true);
- expect(descriptionFieldEl.findComponent(MarkdownField).props()).toMatchObject({
- markdownPreviewPath: wrapper.vm.descriptionPreviewPath,
+ expect(descriptionFieldEl.findComponent(MarkdownEditor).exists()).toBe(true);
+ expect(descriptionFieldEl.findComponent(MarkdownEditor).props()).toMatchObject({
+ renderMarkdownPath: wrapper.vm.descriptionPreviewPath,
markdownDocsPath: wrapper.vm.descriptionHelpPath,
- addSpacingClasses: false,
- showSuggestPopover: true,
- textareaValue: '',
+ value: '',
+ formFieldProps: {
+ ariaLabel: __('Description'),
+ class: 'rspec-issuable-form-description',
+ placeholder: __('Write a comment or drag your files here…'),
+ dataQaSelector: 'issuable_form_description_field',
+ id: 'issuable-description',
+ name: 'issuable-description',
+ },
});
- expect(descriptionFieldEl.find('textarea').exists()).toBe(true);
- expect(descriptionFieldEl.find('textarea').attributes('placeholder')).toBe(
- 'Write a comment or drag your files here…',
- );
});
it('renders labels select field', () => {
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
index dd46505bd65..e24cfe27616 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
@@ -108,8 +108,8 @@ describe('WorkItemLinks', () => {
describe('add link form', () => {
it('displays add work item form on click add dropdown then add existing button and hides form on cancel', async () => {
await createComponent();
- findToggleFormDropdown().vm.$emit('click');
- findToggleAddFormButton().vm.$emit('click');
+ findToggleFormDropdown().vm.$emit('action');
+ findToggleAddFormButton().vm.$emit('action');
await nextTick();
expect(findAddLinksForm().exists()).toBe(true);
@@ -123,8 +123,8 @@ describe('WorkItemLinks', () => {
it('displays create work item form on click add dropdown then create button and hides form on cancel', async () => {
await createComponent();
- findToggleFormDropdown().vm.$emit('click');
- findToggleCreateFormButton().vm.$emit('click');
+ findToggleFormDropdown().vm.$emit('action');
+ findToggleCreateFormButton().vm.$emit('action');
await nextTick();
expect(findAddLinksForm().exists()).toBe(true);
@@ -195,8 +195,8 @@ describe('WorkItemLinks', () => {
.fn()
.mockResolvedValue(getIssueDetailsResponse({ confidential: true })),
});
- findToggleFormDropdown().vm.$emit('click');
- findToggleAddFormButton().vm.$emit('click');
+ findToggleFormDropdown().vm.$emit('action');
+ findToggleAddFormButton().vm.$emit('action');
await nextTick();
expect(findAddLinksForm().props('parentConfidential')).toBe(true);
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index d7e5c02ffbe..0c5ce179acc 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -584,6 +584,7 @@ export const workItemResponseFactory = ({
__typename: 'WorkItemWidgetProgress',
type: 'PROGRESS',
progress: 0,
+ updatedAt: new Date(),
}
: { type: 'MOCK TYPE' },
milestoneWidgetPresent
@@ -1145,6 +1146,7 @@ export const workItemObjectiveMetadataWidgets = {
type: 'PROGRESS',
__typename: 'WorkItemWidgetProgress',
progress: 10,
+ updatedAt: new Date(),
},
};
@@ -1213,6 +1215,7 @@ export const workItemObjectiveNoMetadata = {
__typename: 'WorkItemWidgetProgress',
type: 'PROGRESS',
progress: null,
+ updatedAt: null,
},
{
__typename: 'WorkItemWidgetMilestone',