diff options
Diffstat (limited to 'spec/frontend/projects')
8 files changed, 782 insertions, 4 deletions
diff --git a/spec/frontend/projects/commits/store/actions_spec.js b/spec/frontend/projects/commits/store/actions_spec.js index c9945e1cc27..886224252ad 100644 --- a/spec/frontend/projects/commits/store/actions_spec.js +++ b/spec/frontend/projects/commits/store/actions_spec.js @@ -45,7 +45,7 @@ describe('Project commits actions', () => { describe('fetchAuthors', () => { it('dispatches request/receive', () => { - const path = '/autocomplete/users.json'; + const path = '/-/autocomplete/users.json'; state.projectId = '8'; const data = [{ id: 1 }]; @@ -60,7 +60,7 @@ describe('Project commits actions', () => { }); it('dispatches request/receive on error', () => { - const path = '/autocomplete/users.json'; + const path = '/-/autocomplete/users.json'; mock.onGet(path).replyOnce(500); testAction(actions.fetchAuthors, null, state, [], [{ type: 'receiveAuthorsError' }]); diff --git a/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap b/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap new file mode 100644 index 00000000000..4d5b6c56a34 --- /dev/null +++ b/spec/frontend/projects/components/__snapshots__/remove_modal_spec.js.snap @@ -0,0 +1,126 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Project remove modal initialized matches the snapshot 1`] = ` +<form + action="some/path" + method="post" +> + <input + name="_method" + type="hidden" + value="delete" + /> + + <input + name="authenticity_token" + type="hidden" + /> + + <b-button-stub + class="[object Object]" + event="click" + role="button" + routertag="a" + size="md" + tabindex="0" + tag="button" + type="button" + variant="danger" + > + <!----> + + <!----> + + <span + class="gl-button-text" + > + Remove project + </span> + </b-button-stub> + + <b-modal-stub + canceltitle="Cancel" + cancelvariant="secondary" + footerclass="bg-gray-light gl-p-5" + headerclosecontent="×" + headercloselabel="Close" + id="remove-project-modal" + ignoreenforcefocusselector="" + lazy="true" + modalclass="gl-modal," + oktitle="OK" + okvariant="danger" + size="sm" + title="" + titletag="h4" + > + + <div> + <p + class="gl-text-red-500 gl-font-weight-bold" + > + This can lead to data loss. + </p> + + <p + class="gl-mb-0" + > + This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention. + </p> + + <p> + <gl-sprintf-stub + message="Please type %{phrase_code} to proceed or close this modal to cancel." + /> + </p> + + <gl-form-input-stub + id="confirm_name_input" + name="confirm_name_input" + type="text" + /> + </div> + + <template /> + + <template> + Confirmation required + </template> + + <template /> + + <template /> + + <template /> + + <template> + <div + class="gl-w-full gl-display-flex gl-just-content-start gl-m-0" + > + <b-button-stub + class="[object Object]" + disabled="true" + event="click" + routertag="a" + size="md" + tag="button" + type="button" + variant="danger" + > + <!----> + + <!----> + + <span + class="gl-button-text" + > + + Confirm + + </span> + </b-button-stub> + </div> + </template> + </b-modal-stub> +</form> +`; diff --git a/spec/frontend/projects/components/remove_modal_spec.js b/spec/frontend/projects/components/remove_modal_spec.js new file mode 100644 index 00000000000..339aee65b99 --- /dev/null +++ b/spec/frontend/projects/components/remove_modal_spec.js @@ -0,0 +1,62 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlButton, GlModal } from '@gitlab/ui'; +import ProjectRemoveModal from '~/projects/components/remove_modal.vue'; + +describe('Project remove modal', () => { + let wrapper; + + const findFormElement = () => wrapper.find('form').element; + const findConfirmButton = () => wrapper.find(GlModal).find(GlButton); + + const defaultProps = { + formPath: 'some/path', + confirmPhrase: 'foo', + warningMessage: 'This can lead to data loss.', + }; + + const createComponent = (data = {}) => { + wrapper = shallowMount(ProjectRemoveModal, { + propsData: defaultProps, + data: () => data, + stubs: { + GlButton, + GlModal, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('initialized', () => { + beforeEach(() => { + createComponent(); + }); + + it('matches the snapshot', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + }); + + describe('user input matches the confirmPhrase', () => { + beforeEach(() => { + createComponent({ userInput: defaultProps.confirmPhrase }); + }); + + it('the confirm button is not dislabled', () => { + expect(findConfirmButton().attributes('disabled')).toBe(undefined); + }); + + describe('and when the confirmation button is clicked', () => { + beforeEach(() => { + findConfirmButton().vm.$emit('click'); + }); + + it('submits the form element', () => { + expect(findFormElement().submit).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/spec/frontend/projects/pipelines/charts/components/__snapshots__/pipelines_area_chart_spec.js.snap b/spec/frontend/projects/pipelines/charts/components/__snapshots__/pipelines_area_chart_spec.js.snap index f280ecaa0bc..d68e009f46e 100644 --- a/spec/frontend/projects/pipelines/charts/components/__snapshots__/pipelines_area_chart_spec.js.snap +++ b/spec/frontend/projects/pipelines/charts/components/__snapshots__/pipelines_area_chart_spec.js.snap @@ -2,7 +2,7 @@ exports[`PipelinesAreaChart matches the snapshot 1`] = ` <div - class="prepend-top-default" + class="gl-mt-3" > <p> Some title diff --git a/spec/frontend/projects/project_new_spec.js b/spec/frontend/projects/project_new_spec.js index 7c6ff90aff6..7aafbd33fc8 100644 --- a/spec/frontend/projects/project_new_spec.js +++ b/spec/frontend/projects/project_new_spec.js @@ -1,5 +1,6 @@ import $ from 'jquery'; import projectNew from '~/projects/project_new'; +import { TEST_HOST } from 'jest/helpers/test_constants'; describe('New Project', () => { let $projectImportUrl; @@ -33,7 +34,7 @@ describe('New Project', () => { }); describe('deriveProjectPathFromUrl', () => { - const dummyImportUrl = `${gl.TEST_HOST}/dummy/import/url.git`; + const dummyImportUrl = `${TEST_HOST}/dummy/import/url.git`; beforeEach(() => { projectNew.bindEvents(); diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js new file mode 100644 index 00000000000..4c873bdfd60 --- /dev/null +++ b/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js @@ -0,0 +1,226 @@ +import { shallowMount, mount } from '@vue/test-utils'; +import AxiosMockAdapter from 'axios-mock-adapter'; +import waitForPromises from 'helpers/wait_for_promises'; +import ServiceDeskRoot from '~/projects/settings_service_desk/components/service_desk_root.vue'; +import axios from '~/lib/utils/axios_utils'; +import httpStatusCodes from '~/lib/utils/http_status'; + +describe('ServiceDeskRoot', () => { + const endpoint = '/gitlab-org/gitlab-test/service_desk'; + const initialIncomingEmail = 'servicedeskaddress@example.com'; + let axiosMock; + let wrapper; + let spy; + + beforeEach(() => { + axiosMock = new AxiosMockAdapter(axios); + }); + + afterEach(() => { + axiosMock.restore(); + wrapper.destroy(); + if (spy) { + spy.mockRestore(); + } + }); + + it('fetches incoming email when there is no incoming email provided', () => { + axiosMock.onGet(endpoint).replyOnce(httpStatusCodes.OK); + + wrapper = shallowMount(ServiceDeskRoot, { + propsData: { + initialIsEnabled: true, + initialIncomingEmail: '', + endpoint, + }, + }); + + return wrapper.vm + .$nextTick() + .then(waitForPromises) + .then(() => { + expect(axiosMock.history.get).toHaveLength(1); + }); + }); + + it('does not fetch incoming email when there is an incoming email provided', () => { + axiosMock.onGet(endpoint).replyOnce(httpStatusCodes.OK); + + wrapper = shallowMount(ServiceDeskRoot, { + propsData: { + initialIsEnabled: true, + initialIncomingEmail, + endpoint, + }, + }); + + return wrapper.vm + .$nextTick() + .then(waitForPromises) + .then(() => { + expect(axiosMock.history.get).toHaveLength(0); + }); + }); + + it('shows an error message when incoming email is not fetched correctly', () => { + axiosMock.onGet(endpoint).networkError(); + + wrapper = shallowMount(ServiceDeskRoot, { + propsData: { + initialIsEnabled: true, + initialIncomingEmail: '', + endpoint, + }, + }); + + return wrapper.vm + .$nextTick() + .then(waitForPromises) + .then(() => { + expect(wrapper.html()).toContain( + 'An error occurred while fetching the Service Desk address.', + ); + }); + }); + + it('sends a request to toggle service desk off when the toggle is clicked from the on state', () => { + axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK); + + spy = jest.spyOn(axios, 'put'); + + wrapper = mount(ServiceDeskRoot, { + propsData: { + initialIsEnabled: true, + initialIncomingEmail, + endpoint, + }, + }); + + wrapper.find('button.gl-toggle').trigger('click'); + + return wrapper.vm + .$nextTick() + .then(waitForPromises) + .then(() => { + expect(spy).toHaveBeenCalledWith(endpoint, { service_desk_enabled: false }); + }); + }); + + it('sends a request to toggle service desk on when the toggle is clicked from the off state', () => { + axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK); + + spy = jest.spyOn(axios, 'put'); + + wrapper = mount(ServiceDeskRoot, { + propsData: { + initialIsEnabled: false, + initialIncomingEmail: '', + endpoint, + }, + }); + + wrapper.find('button.gl-toggle').trigger('click'); + + return wrapper.vm.$nextTick(() => { + expect(spy).toHaveBeenCalledWith(endpoint, { service_desk_enabled: true }); + }); + }); + + it('shows an error message when there is an issue toggling service desk on', () => { + axiosMock.onPut(endpoint).networkError(); + + wrapper = mount(ServiceDeskRoot, { + propsData: { + initialIsEnabled: false, + initialIncomingEmail: '', + endpoint, + }, + }); + + wrapper.find('button.gl-toggle').trigger('click'); + + return wrapper.vm + .$nextTick() + .then(waitForPromises) + .then(() => { + expect(wrapper.html()).toContain('An error occurred while enabling Service Desk.'); + }); + }); + + it('sends a request to update template when the "Save template" button is clicked', () => { + axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK); + + spy = jest.spyOn(axios, 'put'); + + wrapper = mount(ServiceDeskRoot, { + propsData: { + initialIsEnabled: true, + endpoint, + initialIncomingEmail, + selectedTemplate: 'Bug', + outgoingName: 'GitLab Support Bot', + templates: ['Bug', 'Documentation'], + projectKey: 'key', + }, + }); + + wrapper.find('button.btn-success').trigger('click'); + + return wrapper.vm.$nextTick(() => { + expect(spy).toHaveBeenCalledWith(endpoint, { + issue_template_key: 'Bug', + outgoing_name: 'GitLab Support Bot', + project_key: 'key', + service_desk_enabled: true, + }); + }); + }); + + it('saves the template when the "Save template" button is clicked', () => { + axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK); + + wrapper = mount(ServiceDeskRoot, { + propsData: { + initialIsEnabled: true, + endpoint, + initialIncomingEmail, + selectedTemplate: 'Bug', + templates: ['Bug', 'Documentation'], + }, + }); + + wrapper.find('button.btn-success').trigger('click'); + + return wrapper.vm + .$nextTick() + .then(waitForPromises) + .then(() => { + expect(wrapper.html()).toContain('Template was successfully saved.'); + }); + }); + + it('shows an error message when there is an issue saving the template', () => { + axiosMock.onPut(endpoint).networkError(); + + wrapper = mount(ServiceDeskRoot, { + propsData: { + initialIsEnabled: true, + endpoint, + initialIncomingEmail, + selectedTemplate: 'Bug', + templates: ['Bug', 'Documentation'], + }, + }); + + wrapper.find('button.btn-success').trigger('click'); + + return wrapper.vm + .$nextTick() + .then(waitForPromises) + .then(() => { + expect(wrapper.html()).toContain( + 'An error occurred while saving the template. Please check if the template exists.', + ); + }); + }); +}); diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js new file mode 100644 index 00000000000..7fe310aa400 --- /dev/null +++ b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js @@ -0,0 +1,234 @@ +import { shallowMount, mount } from '@vue/test-utils'; +import { GlLoadingIcon } from '@gitlab/ui'; +import eventHub from '~/projects/settings_service_desk/event_hub'; +import ServiceDeskSetting from '~/projects/settings_service_desk/components/service_desk_setting.vue'; + +describe('ServiceDeskSetting', () => { + let wrapper; + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + const findTemplateDropdown = () => wrapper.find('#service-desk-template-select'); + + describe('when isEnabled=true', () => { + describe('only isEnabled', () => { + describe('as project admin', () => { + beforeEach(() => { + wrapper = shallowMount(ServiceDeskSetting, { + propsData: { + isEnabled: true, + }, + }); + }); + + it('should see activation checkbox', () => { + expect(wrapper.contains('#service-desk-checkbox')).toBe(true); + }); + + it('should see main panel with the email info', () => { + expect(wrapper.contains('#incoming-email-describer')).toBe(true); + }); + + it('should see loading spinner and not the incoming email', () => { + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.contains('.incoming-email')).toBe(false); + }); + }); + }); + + describe('service desk toggle', () => { + it('emits an event to turn on Service Desk when clicked', () => { + const eventSpy = jest.fn(); + eventHub.$on('serviceDeskEnabledCheckboxToggled', eventSpy); + + wrapper = mount(ServiceDeskSetting, { + propsData: { + isEnabled: false, + }, + }); + + wrapper.find('#service-desk-checkbox').trigger('click'); + + expect(eventSpy).toHaveBeenCalledWith(true); + + eventHub.$off('serviceDeskEnabledCheckboxToggled', eventSpy); + eventSpy.mockRestore(); + }); + }); + + describe('with incomingEmail', () => { + const incomingEmail = 'foo@bar.com'; + + beforeEach(() => { + wrapper = mount(ServiceDeskSetting, { + propsData: { + isEnabled: true, + incomingEmail, + }, + }); + }); + + it('should see email and not the loading spinner', () => { + expect(wrapper.find('.incoming-email').element.value).toEqual(incomingEmail); + expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + }); + + it('renders a copy to clipboard button', () => { + expect(wrapper.contains('.qa-clipboard-button')).toBe(true); + expect(wrapper.find('.qa-clipboard-button').element.dataset.clipboardText).toBe( + incomingEmail, + ); + }); + }); + + describe('templates dropdown', () => { + it('renders a dropdown to choose a template', () => { + wrapper = shallowMount(ServiceDeskSetting, { + propsData: { + isEnabled: true, + }, + }); + + expect(wrapper.contains('#service-desk-template-select')).toBe(true); + }); + + it('renders a dropdown with a default value of ""', () => { + wrapper = mount(ServiceDeskSetting, { + propsData: { + isEnabled: true, + }, + }); + + expect(findTemplateDropdown().element.value).toEqual(''); + }); + + it('renders a dropdown with a value of "Bug" when it is the initial value', () => { + const templates = ['Bug', 'Documentation', 'Security release']; + + wrapper = mount(ServiceDeskSetting, { + propsData: { + isEnabled: true, + initialSelectedTemplate: 'Bug', + templates, + }, + }); + + expect(findTemplateDropdown().element.value).toEqual('Bug'); + }); + + it('renders a dropdown with no options when the project has no templates', () => { + wrapper = mount(ServiceDeskSetting, { + propsData: { + isEnabled: true, + templates: [], + }, + }); + + // The dropdown by default has one empty option + expect(findTemplateDropdown().element.children).toHaveLength(1); + }); + + it('renders a dropdown with options when the project has templates', () => { + const templates = ['Bug', 'Documentation', 'Security release']; + wrapper = mount(ServiceDeskSetting, { + propsData: { + isEnabled: true, + templates, + }, + }); + + // An empty-named template is prepended so the user can select no template + const expectedTemplates = [''].concat(templates); + + const dropdown = findTemplateDropdown(); + const dropdownList = Array.from(dropdown.element.children).map(option => option.innerText); + + expect(dropdown.element.children).toHaveLength(expectedTemplates.length); + expect(dropdownList.includes('Bug')).toEqual(true); + expect(dropdownList.includes('Documentation')).toEqual(true); + expect(dropdownList.includes('Security release')).toEqual(true); + }); + }); + }); + + describe('save button', () => { + it('renders a save button to save a template', () => { + wrapper = mount(ServiceDeskSetting, { + propsData: { + isEnabled: true, + }, + }); + + expect(wrapper.find('button.btn-success').text()).toContain('Save template'); + }); + + it('emits a save event with the chosen template when the save button is clicked', () => { + const eventSpy = jest.fn(); + eventHub.$on('serviceDeskTemplateSave', eventSpy); + + wrapper = mount(ServiceDeskSetting, { + propsData: { + isEnabled: true, + initialSelectedTemplate: 'Bug', + initialOutgoingName: 'GitLab Support Bot', + initialProjectKey: 'key', + }, + }); + + wrapper.find('button.btn-success').trigger('click'); + + expect(eventSpy).toHaveBeenCalledWith({ + selectedTemplate: 'Bug', + outgoingName: 'GitLab Support Bot', + projectKey: 'key', + }); + + eventHub.$off('serviceDeskTemplateSave', eventSpy); + eventSpy.mockRestore(); + }); + }); + + describe('when isEnabled=false', () => { + beforeEach(() => { + wrapper = shallowMount(ServiceDeskSetting, { + propsData: { + isEnabled: false, + }, + }); + }); + + it('does not render email panel', () => { + expect(wrapper.contains('#incoming-email-describer')).toBe(false); + }); + + it('does not render template dropdown', () => { + expect(wrapper.contains('#service-desk-template-select')).toBe(false); + }); + + it('does not render template save button', () => { + expect(wrapper.contains('button.btn-success')).toBe(false); + }); + + it('emits an event to turn on Service Desk when the toggle is clicked', () => { + const eventSpy = jest.fn(); + eventHub.$on('serviceDeskEnabledCheckboxToggled', eventSpy); + + wrapper = mount(ServiceDeskSetting, { + propsData: { + isEnabled: true, + }, + }); + + wrapper.find('#service-desk-checkbox').trigger('click'); + + expect(eventSpy).toHaveBeenCalledWith(false); + + eventHub.$off('serviceDeskEnabledCheckboxToggled', eventSpy); + eventSpy.mockRestore(); + }); + }); +}); diff --git a/spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js b/spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js new file mode 100644 index 00000000000..f9e4d55245a --- /dev/null +++ b/spec/frontend/projects/settings_service_desk/services/service_desk_service_spec.js @@ -0,0 +1,129 @@ +import AxiosMockAdapter from 'axios-mock-adapter'; +import ServiceDeskService from '~/projects/settings_service_desk/services/service_desk_service'; +import axios from '~/lib/utils/axios_utils'; +import httpStatusCodes from '~/lib/utils/http_status'; + +describe('ServiceDeskService', () => { + const endpoint = `/gitlab-org/gitlab-test/service_desk`; + const dummyResponse = { message: 'Dummy response' }; + const errorMessage = 'Network Error'; + let axiosMock; + let service; + + beforeEach(() => { + axiosMock = new AxiosMockAdapter(axios); + service = new ServiceDeskService(endpoint); + }); + + afterEach(() => { + axiosMock.restore(); + }); + + describe('fetchIncomingEmail', () => { + it('makes a request to fetch incoming email', () => { + axiosMock.onGet(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse); + + return service.fetchIncomingEmail().then(response => { + expect(response.data).toEqual(dummyResponse); + }); + }); + + it('fails on error response', () => { + axiosMock.onGet(endpoint).networkError(); + + return service.fetchIncomingEmail().catch(error => { + expect(error.message).toBe(errorMessage); + }); + }); + }); + + describe('toggleServiceDesk', () => { + it('makes a request to set service desk', () => { + axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse); + + return service.toggleServiceDesk(true).then(response => { + expect(response.data).toEqual(dummyResponse); + }); + }); + + it('fails on error response', () => { + axiosMock.onPut(endpoint).networkError(); + + return service.toggleServiceDesk(true).catch(error => { + expect(error.message).toBe(errorMessage); + }); + }); + + it('makes a request with the expected body', () => { + axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse); + + const spy = jest.spyOn(axios, 'put'); + + service.toggleServiceDesk(true); + + expect(spy).toHaveBeenCalledWith(endpoint, { + service_desk_enabled: true, + }); + + spy.mockRestore(); + }); + }); + + describe('updateTemplate', () => { + it('makes a request to update template', () => { + axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse); + + return service + .updateTemplate( + { + selectedTemplate: 'Bug', + outgoingName: 'GitLab Support Bot', + }, + true, + ) + .then(response => { + expect(response.data).toEqual(dummyResponse); + }); + }); + + it('fails on error response', () => { + axiosMock.onPut(endpoint).networkError(); + + return service + .updateTemplate( + { + selectedTemplate: 'Bug', + outgoingName: 'GitLab Support Bot', + }, + true, + ) + .catch(error => { + expect(error.message).toBe(errorMessage); + }); + }); + + it('makes a request with the expected body', () => { + axiosMock.onPut(endpoint).replyOnce(httpStatusCodes.OK, dummyResponse); + + const spy = jest.spyOn(axios, 'put'); + + service.updateTemplate( + { + selectedTemplate: 'Bug', + outgoingName: 'GitLab Support Bot', + projectKey: 'key', + }, + true, + ); + + expect(spy).toHaveBeenCalledWith(endpoint, { + issue_template_key: 'Bug', + outgoing_name: 'GitLab Support Bot', + project_key: 'key', + service_desk_enabled: true, + }); + + spy.mockRestore(); + }); + }); +}); |