diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-15 15:08:30 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-15 15:08:30 +0000 |
commit | ce97c898865e06644ae9c04d5c3666775b9998cb (patch) | |
tree | 03e9e241b7d713a9fd137be4be841e38297e7acd /spec/frontend | |
parent | b50c9d31e395afcab172d16760c5700c8cd5bcae (diff) | |
download | gitlab-ce-ce97c898865e06644ae9c04d5c3666775b9998cb.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
17 files changed, 289 insertions, 412 deletions
diff --git a/spec/frontend/__helpers__/gon_helper.js b/spec/frontend/__helpers__/gon_helper.js new file mode 100644 index 00000000000..51d5ece5fc1 --- /dev/null +++ b/spec/frontend/__helpers__/gon_helper.js @@ -0,0 +1,5 @@ +export const createGon = (IS_EE) => { + return { + ee: IS_EE, + }; +}; diff --git a/spec/frontend/__helpers__/shared_test_setup.js b/spec/frontend/__helpers__/shared_test_setup.js index 7fc81cf6548..0217835b2a3 100644 --- a/spec/frontend/__helpers__/shared_test_setup.js +++ b/spec/frontend/__helpers__/shared_test_setup.js @@ -6,6 +6,7 @@ import { enableAutoDestroy } from '@vue/test-utils'; import 'jquery'; import Translate from '~/vue_shared/translate'; import setWindowLocation from './set_window_location_helper'; +import { createGon } from './gon_helper'; import { setGlobalDateToFakeDate } from './fake_date'; import { TEST_HOST } from './test_constants'; import * as customMatchers from './matchers'; @@ -71,8 +72,13 @@ beforeEach(() => { // eslint-disable-next-line jest/no-standalone-expect expect.hasAssertions(); - // Reset the mocked window.location. This ensures tests don't interfere with - // each other, and removes the need to tidy up if it was changed for a given - // test. + // Reset globals: This ensures tests don't interfere with + // each other, and removes the need to tidy up if it was + // changed for a given test. + + // Reset the mocked window.location setWindowLocation(TEST_HOST); + + // Reset window.gon object + window.gon = createGon(window.IS_EE); }); diff --git a/spec/frontend/ci/runner/components/registration/registration_instructions_spec.js b/spec/frontend/ci/runner/components/registration/registration_instructions_spec.js index 72064a3895f..9b264e17c41 100644 --- a/spec/frontend/ci/runner/components/registration/registration_instructions_spec.js +++ b/spec/frontend/ci/runner/components/registration/registration_instructions_spec.js @@ -16,10 +16,6 @@ import { } from '~/ci/runner/constants'; import { runnerForRegistration } from '../../mock_data'; -const DUMMY_GON = { - gitlab_url: TEST_HOST, -}; - const AUTH_TOKEN = 'AUTH_TOKEN'; const mockRunner = { @@ -29,7 +25,6 @@ const mockRunner = { describe('RegistrationInstructions', () => { let wrapper; - let originalGon; const findHeading = () => wrapper.find('h1'); const findStepAt = (i) => extendedWrapper(wrapper.findAll('section').at(i)); @@ -48,13 +43,8 @@ describe('RegistrationInstructions', () => { }); }; - beforeAll(() => { - originalGon = window.gon; - window.gon = { ...DUMMY_GON }; - }); - - afterAll(() => { - window.gon = originalGon; + beforeEach(() => { + window.gon.gitlab_url = TEST_HOST; }); describe('renders heading', () => { diff --git a/spec/frontend/ci/runner/components/registration/utils_spec.js b/spec/frontend/ci/runner/components/registration/utils_spec.js index c2bf28b4a25..acf5993b15b 100644 --- a/spec/frontend/ci/runner/components/registration/utils_spec.js +++ b/spec/frontend/ci/runner/components/registration/utils_spec.js @@ -16,20 +16,10 @@ import { const REGISTRATION_TOKEN = 'REGISTRATION_TOKEN'; const DESCRIPTION = 'RUNNER'; -const DUMMY_GON = { - gitlab_url: TEST_HOST, -}; describe('registration utils', () => { - let originalGon; - - beforeAll(() => { - originalGon = window.gon; - window.gon = { ...DUMMY_GON }; - }); - - afterAll(() => { - window.gon = originalGon; + beforeEach(() => { + window.gon.gitlab_url = TEST_HOST; }); describe.each([LINUX_PLATFORM, MACOS_PLATFORM, WINDOWS_PLATFORM])( diff --git a/spec/frontend/editor/source_editor_ci_schema_ext_spec.js b/spec/frontend/editor/source_editor_ci_schema_ext_spec.js index 21f8979f1a9..e515285601b 100644 --- a/spec/frontend/editor/source_editor_ci_schema_ext_spec.js +++ b/spec/frontend/editor/source_editor_ci_schema_ext_spec.js @@ -17,7 +17,6 @@ describe('~/editor/editor_ci_config_ext', () => { let editor; let instance; let editorEl; - let originalGitlabUrl; const createMockEditor = ({ blobPath = defaultBlobPath } = {}) => { setHTMLFixture('<div id="editor"></div>'); @@ -31,16 +30,8 @@ describe('~/editor/editor_ci_config_ext', () => { instance.use({ definition: CiSchemaExtension }); }; - beforeAll(() => { - originalGitlabUrl = gon.gitlab_url; - gon.gitlab_url = TEST_HOST; - }); - - afterAll(() => { - gon.gitlab_url = originalGitlabUrl; - }); - beforeEach(() => { + gon.gitlab_url = TEST_HOST; createMockEditor(); }); diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js index 82e3b50aeb8..7b160c48501 100644 --- a/spec/frontend/environment.js +++ b/spec/frontend/environment.js @@ -8,6 +8,7 @@ const { setGlobalDateToRealDate, } = require('./__helpers__/fake_date/fake_date'); const { TEST_HOST } = require('./__helpers__/test_constants'); +const { createGon } = require('./__helpers__/gon_helper'); const ROOT_PATH = path.resolve(__dirname, '../..'); @@ -40,11 +41,12 @@ class CustomEnvironment extends TestEnvironment { }); const { IS_EE } = projectConfig.testEnvironmentOptions; - this.global.gon = { - ee: IS_EE, - }; + this.global.IS_EE = IS_EE; + // Set up global `gon` object + this.global.gon = createGon(IS_EE); + // Set up global `gl` object this.global.gl = {}; diff --git a/spec/frontend/header_search/components/app_spec.js b/spec/frontend/header_search/components/app_spec.js index 8adc108d38a..35104b84a88 100644 --- a/spec/frontend/header_search/components/app_spec.js +++ b/spec/frontend/header_search/components/app_spec.js @@ -350,8 +350,8 @@ describe('HeaderSearchApp', () => { describe('events', () => { beforeEach(() => { - createComponent(); window.gon.current_username = MOCK_USERNAME; + createComponent(); }); describe('Header Search Input', () => { @@ -459,8 +459,8 @@ describe('HeaderSearchApp', () => { ${2} | ${'test1'} `('currentFocusedOption', ({ MOCK_INDEX, search }) => { beforeEach(() => { - createComponent({ search }); window.gon.current_username = MOCK_USERNAME; + createComponent({ search }); findHeaderSearchInput().vm.$emit('click'); }); @@ -500,8 +500,8 @@ describe('HeaderSearchApp', () => { const MOCK_INDEX = 1; beforeEach(() => { - createComponent(); window.gon.current_username = MOCK_USERNAME; + createComponent(); findHeaderSearchInput().vm.$emit('click'); }); diff --git a/spec/frontend/issues/show/components/app_spec.js b/spec/frontend/issues/show/components/app_spec.js index abc44f19855..1006f54eeaf 100644 --- a/spec/frontend/issues/show/components/app_spec.js +++ b/spec/frontend/issues/show/components/app_spec.js @@ -1,7 +1,6 @@ import { GlIcon, GlIntersectionObserver } from '@gitlab/ui'; import MockAdapter from 'axios-mock-adapter'; -import { nextTick } from 'vue'; -import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; +import { setHTMLFixture } from 'helpers/fixtures'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; @@ -21,29 +20,27 @@ import FormComponent from '~/issues/show/components/form.vue'; import TitleComponent from '~/issues/show/components/title.vue'; import IncidentTabs from '~/issues/show/components/incidents/incident_tabs.vue'; import PinnedLinks from '~/issues/show/components/pinned_links.vue'; -import { POLLING_DELAY } from '~/issues/show/constants'; import eventHub from '~/issues/show/event_hub'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; +import { HTTP_STATUS_OK, HTTP_STATUS_UNAUTHORIZED } from '~/lib/utils/http_status'; import { visitUrl } from '~/lib/utils/url_utility'; import { appProps, initialRequest, publishedIncidentUrl, + putRequest, secondRequest, zoomMeetingUrl, } from '../mock_data/mock_data'; jest.mock('~/alert'); -jest.mock('~/issues/show/event_hub'); jest.mock('~/lib/utils/url_utility'); jest.mock('~/behaviors/markdown/render_gfm'); const REALTIME_REQUEST_STACK = [initialRequest, secondRequest]; describe('Issuable output', () => { - let mock; - let realtimeRequestCount = 0; + let axiosMock; let wrapper; const findStickyHeader = () => wrapper.findByTestId('issue-sticky-header'); @@ -57,7 +54,7 @@ describe('Issuable output', () => { const findForm = () => wrapper.findComponent(FormComponent); const findPinnedLinks = () => wrapper.findComponent(PinnedLinks); - const mountComponent = (props = {}, options = {}, data = {}) => { + const createComponent = ({ props = {}, options = {}, data = {} } = {}) => { wrapper = shallowMountExtended(IssuableApp, { directives: { GlTooltip: createMockDirective('gl-tooltip'), @@ -78,6 +75,28 @@ describe('Issuable output', () => { }, ...options, }); + + jest.advanceTimersToNextTimer(2); + return waitForPromises(); + }; + + const emitHubEvent = (event) => { + eventHub.$emit(event); + return waitForPromises(); + }; + + const openForm = () => { + return emitHubEvent('open.form'); + }; + + const updateIssuable = () => { + return emitHubEvent('update.issuable'); + }; + + const advanceToNextPoll = () => { + // We get new data through the HTTP request. + jest.advanceTimersToNextTimer(); + return waitForPromises(); }; beforeEach(() => { @@ -97,78 +116,100 @@ describe('Issuable output', () => { </div> `); - mock = new MockAdapter(axios); - mock - .onGet('/gitlab-org/gitlab-shell/-/issues/9/realtime_changes/realtime_changes') - .reply(() => { - const res = Promise.resolve([HTTP_STATUS_OK, REALTIME_REQUEST_STACK[realtimeRequestCount]]); - realtimeRequestCount += 1; - return res; - }); + jest.spyOn(eventHub, '$emit'); - mountComponent(); + axiosMock = new MockAdapter(axios); + const endpoint = '/gitlab-org/gitlab-shell/-/issues/9/realtime_changes/realtime_changes'; - jest.advanceTimersByTime(2); + axiosMock.onGet(endpoint).replyOnce(HTTP_STATUS_OK, REALTIME_REQUEST_STACK[0], { + 'POLL-INTERVAL': '1', + }); + axiosMock.onGet(endpoint).reply(HTTP_STATUS_OK, REALTIME_REQUEST_STACK[1], { + 'POLL-INTERVAL': '-1', + }); + axiosMock.onPut().reply(HTTP_STATUS_OK, putRequest); }); - afterEach(() => { - mock.restore(); - realtimeRequestCount = 0; - wrapper.vm.poll.stop(); - resetHTMLFixture(); - }); + describe('update', () => { + beforeEach(async () => { + await createComponent(); + }); - it('should render a title/description/edited and update title/description/edited on update', () => { - return axios - .waitForAll() - .then(() => { - expect(findTitle().props('titleText')).toContain('this is a title'); - expect(findDescription().props('descriptionText')).toContain('this is a description'); - - expect(findEdited().exists()).toBe(true); - expect(findEdited().props('updatedByPath')).toMatch(/\/some_user$/); - expect(findEdited().props('updatedAt')).toBe(initialRequest.updated_at); - expect(wrapper.vm.state.lock_version).toBe(initialRequest.lock_version); - }) - .then(() => { - wrapper.vm.poll.makeRequest(); - return axios.waitForAll(); - }) - .then(() => { - expect(findTitle().props('titleText')).toContain('2'); - expect(findDescription().props('descriptionText')).toContain('42'); - - expect(findEdited().exists()).toBe(true); - expect(findEdited().props('updatedByName')).toBe('Other User'); - expect(findEdited().props('updatedByPath')).toMatch(/\/other_user$/); - expect(findEdited().props('updatedAt')).toBe(secondRequest.updated_at); - }); - }); + it('should render a title/description/edited and update title/description/edited on update', async () => { + expect(findTitle().props('titleText')).toContain(initialRequest.title_text); + expect(findDescription().props('descriptionText')).toContain('this is a description'); - it('shows actions if permissions are correct', async () => { - wrapper.vm.showForm = true; + expect(findEdited().exists()).toBe(true); + expect(findEdited().props('updatedByPath')).toMatch(/\/some_user$/); + expect(findEdited().props('updatedAt')).toBe(initialRequest.updated_at); + expect(findDescription().props().lockVersion).toBe(initialRequest.lock_version); - await nextTick(); - expect(findForm().exists()).toBe(true); - }); + await advanceToNextPoll(); - it('does not show actions if permissions are incorrect', async () => { - wrapper.vm.showForm = true; - wrapper.setProps({ canUpdate: false }); + expect(findTitle().props('titleText')).toContain('2'); + expect(findDescription().props('descriptionText')).toContain('42'); - await nextTick(); - expect(findForm().exists()).toBe(false); + expect(findEdited().exists()).toBe(true); + expect(findEdited().props('updatedByName')).toBe('Other User'); + expect(findEdited().props('updatedByPath')).toMatch(/\/other_user$/); + expect(findEdited().props('updatedAt')).toBe(secondRequest.updated_at); + }); }); - it('does not update formState if form is already open', async () => { - wrapper.vm.updateAndShowForm(); + describe('with permissions', () => { + beforeEach(async () => { + await createComponent(); + }); + + it('shows actions on `open.form` event', async () => { + expect(findForm().exists()).toBe(false); + + await openForm(); + + expect(findForm().exists()).toBe(true); + }); + + it('update formState if form is not open', async () => { + const titleValue = initialRequest.title_text; + + expect(findTitle().exists()).toBe(true); + expect(findTitle().props('titleText')).toBe(titleValue); + + await advanceToNextPoll(); + + // The title component has the new data, so the state was updated + expect(findTitle().exists()).toBe(true); + expect(findTitle().props('titleText')).toBe(secondRequest.title_text); + }); + + it('does not update formState if form is already open', async () => { + const titleValue = initialRequest.title_text; + + expect(findTitle().exists()).toBe(true); + expect(findTitle().props('titleText')).toBe(titleValue); + + await openForm(); + + // Opening the form, the data has not changed + expect(findForm().props().formState.title).toBe(titleValue); + + await advanceToNextPoll(); + + // We expect the prop value not to have changed after another API call + expect(findForm().props().formState.title).toBe(titleValue); + }); + }); - wrapper.vm.state.titleText = 'testing 123'; + describe('without permissions', () => { + beforeEach(async () => { + await createComponent({ props: { canUpdate: false } }); + }); - wrapper.vm.updateAndShowForm(); + it('does not show actions if permissions are incorrect', async () => { + await openForm(); - await nextTick(); - expect(wrapper.vm.store.formState.title).not.toBe('testing 123'); + expect(findForm().exists()).toBe(false); + }); }); describe('Pinned links propagated', () => { @@ -176,268 +217,130 @@ describe('Issuable output', () => { prop | value ${'zoomMeetingUrl'} | ${zoomMeetingUrl} ${'publishedIncidentUrl'} | ${publishedIncidentUrl} - `('sets the $prop correctly on underlying pinned links', ({ prop, value }) => { + `('sets the $prop correctly on underlying pinned links', async ({ prop, value }) => { + await createComponent(); + expect(findPinnedLinks().props(prop)).toBe(value); }); }); - describe('updateIssuable', () => { + describe('updating an issue', () => { + beforeEach(async () => { + await createComponent(); + }); + it('fetches new data after update', async () => { - const updateStoreSpy = jest.spyOn(wrapper.vm, 'updateStoreState'); - const getDataSpy = jest.spyOn(wrapper.vm.service, 'getData'); - jest.spyOn(wrapper.vm.service, 'updateIssuable').mockResolvedValue({ - data: { web_url: window.location.pathname }, - }); + await advanceToNextPoll(); + + await updateIssuable(); - await wrapper.vm.updateIssuable(); - expect(updateStoreSpy).toHaveBeenCalled(); - expect(getDataSpy).toHaveBeenCalled(); + expect(axiosMock.history.put).toHaveLength(1); + // The call was made with the new data + expect(axiosMock.history.put[0].data.title).toEqual(findTitle().props().title); }); - it('correctly updates issuable data', async () => { - const spy = jest.spyOn(wrapper.vm.service, 'updateIssuable').mockResolvedValue({ - data: { web_url: window.location.pathname }, - }); + it('closes the form after fetching data', async () => { + await updateIssuable(); - await wrapper.vm.updateIssuable(); - expect(spy).toHaveBeenCalledWith(wrapper.vm.formState); expect(eventHub.$emit).toHaveBeenCalledWith('close.form'); }); it('does not redirect if issue has not moved', async () => { - jest.spyOn(wrapper.vm.service, 'updateIssuable').mockResolvedValue({ - data: { - web_url: window.location.pathname, - confidential: wrapper.vm.isConfidential, - }, + axiosMock.onPut().reply(HTTP_STATUS_OK, { + ...putRequest, + confidential: appProps.isConfidential, }); - await wrapper.vm.updateIssuable(); + await updateIssuable(); + expect(visitUrl).not.toHaveBeenCalled(); }); it('does not redirect if issue has not moved and user has switched tabs', async () => { - jest.spyOn(wrapper.vm.service, 'updateIssuable').mockResolvedValue({ - data: { - web_url: '', - confidential: wrapper.vm.isConfidential, - }, + axiosMock.onPut().reply(HTTP_STATUS_OK, { + ...putRequest, + web_url: '', + confidential: appProps.isConfidential, }); - await wrapper.vm.updateIssuable(); + await updateIssuable(); + expect(visitUrl).not.toHaveBeenCalled(); }); it('redirects if returned web_url has changed', async () => { - jest.spyOn(wrapper.vm.service, 'updateIssuable').mockResolvedValue({ - data: { - web_url: '/testing-issue-move', - confidential: wrapper.vm.isConfidential, - }, - }); - - wrapper.vm.updateIssuable(); + const webUrl = '/testing-issue-move'; - await wrapper.vm.updateIssuable(); - expect(visitUrl).toHaveBeenCalledWith('/testing-issue-move'); - }); - - describe('shows dialog when issue has unsaved changed', () => { - it('confirms on title change', async () => { - wrapper.vm.showForm = true; - wrapper.vm.state.titleText = 'title has changed'; - const e = { returnValue: null }; - wrapper.vm.handleBeforeUnloadEvent(e); - - await nextTick(); - expect(e.returnValue).not.toBeNull(); + axiosMock.onPut().reply(HTTP_STATUS_OK, { + ...putRequest, + web_url: webUrl, + confidential: appProps.isConfidential, }); - it('confirms on description change', async () => { - wrapper.vm.showForm = true; - wrapper.vm.state.descriptionText = 'description has changed'; - const e = { returnValue: null }; - wrapper.vm.handleBeforeUnloadEvent(e); - - await nextTick(); - expect(e.returnValue).not.toBeNull(); - }); + await updateIssuable(); - it('does nothing when nothing has changed', async () => { - const e = { returnValue: null }; - wrapper.vm.handleBeforeUnloadEvent(e); - - await nextTick(); - expect(e.returnValue).toBeNull(); - }); + expect(visitUrl).toHaveBeenCalledWith(webUrl); }); describe('error when updating', () => { - it('closes form on error', async () => { - jest.spyOn(wrapper.vm.service, 'updateIssuable').mockRejectedValue(); + it('closes form', async () => { + axiosMock.onPut().reply(HTTP_STATUS_UNAUTHORIZED); - await wrapper.vm.updateIssuable(); - expect(eventHub.$emit).not.toHaveBeenCalledWith('close.form'); - expect(createAlert).toHaveBeenCalledWith({ message: `Error updating issue` }); - }); + await updateIssuable(); - it('returns the correct error message for issuableType', async () => { - jest.spyOn(wrapper.vm.service, 'updateIssuable').mockRejectedValue(); - wrapper.setProps({ issuableType: 'merge request' }); - - await nextTick(); - await wrapper.vm.updateIssuable(); expect(eventHub.$emit).not.toHaveBeenCalledWith('close.form'); - expect(createAlert).toHaveBeenCalledWith({ message: `Error updating merge request` }); - }); - - it('shows error message from backend if exists', async () => { - const msg = 'Custom error message from backend'; - jest - .spyOn(wrapper.vm.service, 'updateIssuable') - .mockRejectedValue({ response: { data: { errors: [msg] } } }); - - await wrapper.vm.updateIssuable(); expect(createAlert).toHaveBeenCalledWith({ - message: `${wrapper.vm.defaultErrorMessage}. ${msg}`, + message: `Error updating issue. Request failed with status code 401`, }); }); - }); - }); - - describe('updateAndShowForm', () => { - it('shows locked warning if form is open & data is different', async () => { - await nextTick(); - wrapper.vm.updateAndShowForm(); - - wrapper.vm.poll.makeRequest(); - - await new Promise((resolve) => { - wrapper.vm.$watch('formState.lockedWarningVisible', (value) => { - if (value) { - resolve(); - } - }); - }); - - expect(wrapper.vm.formState.lockedWarningVisible).toBe(true); - expect(wrapper.vm.formState.lock_version).toBe(1); - }); - }); - - describe('requestTemplatesAndShowForm', () => { - let formSpy; - - beforeEach(() => { - formSpy = jest.spyOn(wrapper.vm, 'updateAndShowForm'); - }); - - it('shows the form if template names as hash request is successful', () => { - const mockData = { - test: [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }], - }; - mock - .onGet('/issuable-templates-path') - .reply(() => Promise.resolve([HTTP_STATUS_OK, mockData])); - - return wrapper.vm.requestTemplatesAndShowForm().then(() => { - expect(formSpy).toHaveBeenCalledWith(mockData); - }); - }); - it('shows the form if template names as array request is successful', () => { - const mockData = [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }]; - mock - .onGet('/issuable-templates-path') - .reply(() => Promise.resolve([HTTP_STATUS_OK, mockData])); + it('returns the correct error message for issuableType', async () => { + axiosMock.onPut().reply(HTTP_STATUS_UNAUTHORIZED); - return wrapper.vm.requestTemplatesAndShowForm().then(() => { - expect(formSpy).toHaveBeenCalledWith(mockData); - }); - }); + await updateIssuable(); - it('shows the form if template names request failed', () => { - mock - .onGet('/issuable-templates-path') - .reply(() => Promise.reject(new Error('something went wrong'))); + wrapper.setProps({ issuableType: 'merge request' }); - return wrapper.vm.requestTemplatesAndShowForm().then(() => { - expect(createAlert).toHaveBeenCalledWith({ message: 'Error updating issue' }); + await updateIssuable(); - expect(formSpy).toHaveBeenCalledWith(); + expect(eventHub.$emit).not.toHaveBeenCalledWith('close.form'); + expect(createAlert).toHaveBeenCalledWith({ + message: `Error updating merge request. Request failed with status code 401`, + }); }); - }); - }); - describe('updateStoreState', () => { - it('should make a request and update the state of the store', () => { - const data = { foo: 1 }; - const getDataSpy = jest.spyOn(wrapper.vm.service, 'getData').mockResolvedValue({ data }); - const updateStateSpy = jest - .spyOn(wrapper.vm.store, 'updateState') - .mockImplementation(jest.fn); - - return wrapper.vm.updateStoreState().then(() => { - expect(getDataSpy).toHaveBeenCalled(); - expect(updateStateSpy).toHaveBeenCalledWith(data); - }); - }); + it('shows error message from backend if exists', async () => { + const msg = 'Custom error message from backend'; + axiosMock.onPut().reply(HTTP_STATUS_UNAUTHORIZED, { errors: [msg] }); - it('should show error message if store update fails', () => { - jest.spyOn(wrapper.vm.service, 'getData').mockRejectedValue(); - wrapper.setProps({ issuableType: 'merge request' }); + await updateIssuable(); - return wrapper.vm.updateStoreState().then(() => { expect(createAlert).toHaveBeenCalledWith({ - message: `Error updating ${wrapper.vm.issuableType}`, + message: `Error updating issue. ${msg}`, }); }); }); }); - describe('issueChanged', () => { - beforeEach(() => { - wrapper.vm.store.formState.title = ''; - wrapper.vm.store.formState.description = ''; - wrapper.setProps({ - initialDescriptionText: '', - initialTitleText: '', - }); - }); - - it('returns true when title is changed', () => { - wrapper.vm.store.formState.title = 'RandomText'; - - expect(wrapper.vm.issueChanged).toBe(true); + describe('Locked warning', () => { + beforeEach(async () => { + await createComponent(); }); - it('returns false when title is empty null', () => { - wrapper.vm.store.formState.title = null; - - expect(wrapper.vm.issueChanged).toBe(false); - }); - - it('returns true when description is changed', () => { - wrapper.vm.store.formState.description = 'RandomText'; - - expect(wrapper.vm.issueChanged).toBe(true); - }); - - it('returns false when description is empty null', () => { - wrapper.vm.store.formState.description = null; - - expect(wrapper.vm.issueChanged).toBe(false); - }); - - it('returns false when `initialDescriptionText` is null and `formState.description` is empty string', () => { - wrapper.vm.store.formState.description = ''; - wrapper.setProps({ initialDescriptionText: null }); + it('shows locked warning if form is open & data is different', async () => { + await openForm(); + await advanceToNextPoll(); - expect(wrapper.vm.issueChanged).toBe(false); + expect(findForm().props().formState.lockedWarningVisible).toBe(true); + expect(findForm().props().formState.lock_version).toBe(1); }); }); describe('sticky header', () => { + beforeEach(async () => { + await createComponent(); + }); + describe('when title is in view', () => { it('is not shown', () => { expect(findStickyHeader().exists()).toBe(false); @@ -445,21 +348,18 @@ describe('Issuable output', () => { }); describe('when title is not in view', () => { - beforeEach(() => { - wrapper.vm.state.titleText = 'Sticky header title'; + beforeEach(async () => { wrapper.findComponent(GlIntersectionObserver).vm.$emit('disappear'); }); it('shows with title', () => { - expect(findStickyHeader().text()).toContain('Sticky header title'); + expect(findStickyHeader().text()).toContain(initialRequest.title_text); }); it('shows with title for an epic', async () => { - wrapper.setProps({ issuableType: 'epic' }); + await wrapper.setProps({ issuableType: 'epic' }); - await nextTick(); - - expect(findStickyHeader().text()).toContain('Sticky header title'); + expect(findStickyHeader().text()).toContain(' this is a title'); }); it.each` @@ -471,9 +371,7 @@ describe('Issuable output', () => { `( 'shows with state icon "$statusIcon" for $issuableType when status is $issuableStatus', async ({ issuableType, issuableStatus, statusIcon }) => { - wrapper.setProps({ issuableType, issuableStatus }); - - await nextTick(); + await wrapper.setProps({ issuableType, issuableStatus }); expect(findStickyHeader().findComponent(GlIcon).props('name')).toBe(statusIcon); }, @@ -485,9 +383,7 @@ describe('Issuable output', () => { ${'shows with Closed when status is closed'} | ${STATUS_CLOSED} ${'shows with Open when status is reopened'} | ${STATUS_REOPENED} `('$title', async ({ state }) => { - wrapper.setProps({ issuableStatus: state }); - - await nextTick(); + await wrapper.setProps({ issuableStatus: state }); expect(findStickyHeader().text()).toContain(IssuableStatusText[state]); }); @@ -497,9 +393,7 @@ describe('Issuable output', () => { ${'does not show confidential badge when issue is not confidential'} | ${false} ${'shows confidential badge when issue is confidential'} | ${true} `('$title', async ({ isConfidential }) => { - wrapper.setProps({ isConfidential }); - - await nextTick(); + await wrapper.setProps({ isConfidential }); const confidentialEl = findConfidentialBadge(); expect(confidentialEl.exists()).toBe(isConfidential); @@ -516,9 +410,7 @@ describe('Issuable output', () => { ${'does not show locked badge when issue is not locked'} | ${false} ${'shows locked badge when issue is locked'} | ${true} `('$title', async ({ isLocked }) => { - wrapper.setProps({ isLocked }); - - await nextTick(); + await wrapper.setProps({ isLocked }); expect(findLockedBadge().exists()).toBe(isLocked); }); @@ -528,9 +420,7 @@ describe('Issuable output', () => { ${'does not show hidden badge when issue is not hidden'} | ${false} ${'shows hidden badge when issue is hidden'} | ${true} `('$title', async ({ isHidden }) => { - wrapper.setProps({ isHidden }); - - await nextTick(); + await wrapper.setProps({ isHidden }); const hiddenBadge = findHiddenBadge(); @@ -547,6 +437,10 @@ describe('Issuable output', () => { }); describe('Composable description component', () => { + beforeEach(async () => { + await createComponent(); + }); + const findIncidentTabs = () => wrapper.findComponent(IncidentTabs); const borderClass = 'gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid gl-mb-6'; @@ -565,13 +459,13 @@ describe('Issuable output', () => { }); describe('when using incident tabs description wrapper', () => { - beforeEach(() => { - mountComponent( - { + beforeEach(async () => { + await createComponent({ + props: { descriptionComponent: IncidentTabs, showTitleBorder: false, }, - { + options: { mocks: { $apollo: { queries: { @@ -582,7 +476,7 @@ describe('Issuable output', () => { }, }, }, - ); + }); }); it('does not the description component', () => { @@ -600,48 +494,77 @@ describe('Issuable output', () => { }); describe('taskListUpdateStarted', () => { - it('stops polling', () => { - jest.spyOn(wrapper.vm.poll, 'stop'); + beforeEach(async () => { + await createComponent(); + }); + + it('stops polling', async () => { + expect(findTitle().props().titleText).toBe(initialRequest.title_text); - wrapper.vm.taskListUpdateStarted(); + findDescription().vm.$emit('taskListUpdateStarted'); - expect(wrapper.vm.poll.stop).toHaveBeenCalled(); + await advanceToNextPoll(); + + expect(findTitle().props().titleText).toBe(initialRequest.title_text); }); }); describe('taskListUpdateSucceeded', () => { - it('enables polling', () => { - jest.spyOn(wrapper.vm.poll, 'enable'); - jest.spyOn(wrapper.vm.poll, 'makeDelayedRequest'); + beforeEach(async () => { + await createComponent(); + findDescription().vm.$emit('taskListUpdateStarted'); + }); - wrapper.vm.taskListUpdateSucceeded(); + it('enables polling', async () => { + // Ensure that polling is not working before + expect(findTitle().props().titleText).toBe(initialRequest.title_text); + await advanceToNextPoll(); - expect(wrapper.vm.poll.enable).toHaveBeenCalled(); - expect(wrapper.vm.poll.makeDelayedRequest).toHaveBeenCalledWith(POLLING_DELAY); + expect(findTitle().props().titleText).toBe(initialRequest.title_text); + + // Enable Polling an move forward + findDescription().vm.$emit('taskListUpdateSucceeded'); + await advanceToNextPoll(); + + // Title has changed: polling works! + expect(findTitle().props().titleText).toBe(secondRequest.title_text); }); }); describe('taskListUpdateFailed', () => { - it('enables polling and calls updateStoreState', () => { - jest.spyOn(wrapper.vm.poll, 'enable'); - jest.spyOn(wrapper.vm.poll, 'makeDelayedRequest'); - jest.spyOn(wrapper.vm, 'updateStoreState'); + beforeEach(async () => { + await createComponent(); + findDescription().vm.$emit('taskListUpdateStarted'); + }); + + it('enables polling and calls updateStoreState', async () => { + // Ensure that polling is not working before + expect(findTitle().props().titleText).toBe(initialRequest.title_text); + await advanceToNextPoll(); - wrapper.vm.taskListUpdateFailed(); + expect(findTitle().props().titleText).toBe(initialRequest.title_text); - expect(wrapper.vm.poll.enable).toHaveBeenCalled(); - expect(wrapper.vm.poll.makeDelayedRequest).toHaveBeenCalledWith(POLLING_DELAY); - expect(wrapper.vm.updateStoreState).toHaveBeenCalled(); + // Enable Polling an move forward + findDescription().vm.$emit('taskListUpdateFailed'); + await advanceToNextPoll(); + + // Title has changed: polling works! + expect(findTitle().props().titleText).toBe(secondRequest.title_text); }); }); describe('saveDescription event', () => { + beforeEach(async () => { + await createComponent(); + }); + it('makes request to update issue', async () => { const description = 'I have been updated!'; findDescription().vm.$emit('saveDescription', description); + await waitForPromises(); - expect(mock.history.put[0].data).toContain(description); + expect(axiosMock.history.put[0].data).toContain(description); }); }); }); diff --git a/spec/frontend/issues/show/mock_data/mock_data.js b/spec/frontend/issues/show/mock_data/mock_data.js index d09bbf7c7f6..86d09665947 100644 --- a/spec/frontend/issues/show/mock_data/mock_data.js +++ b/spec/frontend/issues/show/mock_data/mock_data.js @@ -24,6 +24,19 @@ export const secondRequest = { lock_version: 2, }; +export const putRequest = { + web_url: window.location.pathname, + title: '<p>PUT</p>', + title_text: 'PUT', + description: '<p>PUT_DESC</p>', + description_text: 'PUT_DESC', + task_status: '0 of 0 completed', + updated_at: '2016-05-15T12:31:04.428Z', + updated_by_name: 'Other User', + updated_by_path: '/other_user', + lock_version: 2, +}; + export const descriptionProps = { canUpdate: true, descriptionHtml: 'test', diff --git a/spec/frontend/lib/dompurify_spec.js b/spec/frontend/lib/dompurify_spec.js index f767a673553..fdc8789c1a8 100644 --- a/spec/frontend/lib/dompurify_spec.js +++ b/spec/frontend/lib/dompurify_spec.js @@ -49,8 +49,6 @@ const forbiddenDataAttrs = defaultConfig.FORBID_ATTR; const acceptedDataAttrs = ['data-random', 'data-custom']; describe('~/lib/dompurify', () => { - let originalGon; - it('uses local configuration when given', () => { // As dompurify uses a "Persistent Configuration", it might // ignore config, this check verifies we respect @@ -104,15 +102,10 @@ describe('~/lib/dompurify', () => { ${'root'} | ${rootGon} ${'absolute'} | ${absoluteGon} `('when gon contains $type icon urls', ({ type, gon }) => { - beforeAll(() => { - originalGon = window.gon; + beforeEach(() => { window.gon = gon; }); - afterAll(() => { - window.gon = originalGon; - }); - it('allows no href attrs', () => { const htmlHref = `<svg><use></use></svg>`; expect(sanitize(htmlHref)).toBe(htmlHref); @@ -137,14 +130,9 @@ describe('~/lib/dompurify', () => { describe('when gon does not contain icon urls', () => { beforeAll(() => { - originalGon = window.gon; window.gon = {}; }); - afterAll(() => { - window.gon = originalGon; - }); - it.each([...safeUrls.root, ...safeUrls.absolute, ...unsafeUrls])('sanitizes URL %s', (url) => { const htmlHref = `<svg><use href="${url}"></use></svg>`; const htmlXlink = `<svg><use xlink:href="${url}"></use></svg>`; diff --git a/spec/frontend/observability/observability_app_spec.js b/spec/frontend/observability/observability_app_spec.js index 1f8386b97db..5f41e36595f 100644 --- a/spec/frontend/observability/observability_app_spec.js +++ b/spec/frontend/observability/observability_app_spec.js @@ -51,7 +51,7 @@ describe('Observability root app', () => { describe('iframe src', () => { const TEST_USERNAME = 'test-user'; - beforeAll(() => { + beforeEach(() => { gon.current_username = TEST_USERNAME; }); diff --git a/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js b/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js index 79db288c19b..477511cde64 100644 --- a/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js +++ b/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js @@ -68,15 +68,10 @@ describe('BulkImportsHistoryApp', () => { const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync); - const originalApiVersion = gon.api_version; - beforeAll(() => { + beforeEach(() => { gon.api_version = 'v4'; }); - afterAll(() => { - gon.api_version = originalApiVersion; - }); - beforeEach(() => { mock = new MockAdapter(axios); mock.onGet(API_URL).reply(HTTP_STATUS_OK, DUMMY_RESPONSE, DEFAULT_HEADERS); diff --git a/spec/frontend/pages/import/history/components/import_error_details_spec.js b/spec/frontend/pages/import/history/components/import_error_details_spec.js index e51ace44fe6..239826c1458 100644 --- a/spec/frontend/pages/import/history/components/import_error_details_spec.js +++ b/spec/frontend/pages/import/history/components/import_error_details_spec.js @@ -21,16 +21,8 @@ describe('ImportErrorDetails', () => { }); } - const originalApiVersion = gon.api_version; - beforeAll(() => { - gon.api_version = 'v4'; - }); - - afterAll(() => { - gon.api_version = originalApiVersion; - }); - beforeEach(() => { + gon.api_version = 'v4'; mock = new MockAdapter(axios); }); diff --git a/spec/frontend/pages/import/history/components/import_history_app_spec.js b/spec/frontend/pages/import/history/components/import_history_app_spec.js index 07a031e5949..43cbac25fe8 100644 --- a/spec/frontend/pages/import/history/components/import_history_app_spec.js +++ b/spec/frontend/pages/import/history/components/import_history_app_spec.js @@ -59,17 +59,9 @@ describe('ImportHistoryApp', () => { }); } - const originalApiVersion = gon.api_version; - beforeAll(() => { + beforeEach(() => { gon.api_version = 'v4'; gon.features = { fullPathProjectSearch: true }; - }); - - afterAll(() => { - gon.api_version = originalApiVersion; - }); - - beforeEach(() => { mock = new MockAdapter(axios); }); diff --git a/spec/frontend/pages/projects/forks/new/components/project_namespace_spec.js b/spec/frontend/pages/projects/forks/new/components/project_namespace_spec.js index 509ce3c3443..af578b69a81 100644 --- a/spec/frontend/pages/projects/forks/new/components/project_namespace_spec.js +++ b/spec/frontend/pages/projects/forks/new/components/project_namespace_spec.js @@ -11,7 +11,6 @@ jest.mock('~/alert'); describe('ProjectNamespace component', () => { let wrapper; - let originalGon; const data = { project: { @@ -85,14 +84,8 @@ describe('ProjectNamespace component', () => { findListBox().vm.$emit('shown'); }; - beforeAll(() => { - originalGon = window.gon; - window.gon = { gitlab_url: gitlabUrl }; - }); - - afterAll(() => { - window.gon = originalGon; - wrapper.destroy(); + beforeEach(() => { + gon.gitlab_url = gitlabUrl; }); describe('Initial state', () => { diff --git a/spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js b/spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js index 4246cc15ee3..b9c8655d5d8 100644 --- a/spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js +++ b/spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js @@ -22,10 +22,6 @@ describe('Sidebar date Widget', () => { let fakeApollo; const date = '2021-04-15'; - window.gon = { - first_day_of_week: 1, - }; - const findEditableItem = () => wrapper.findComponent(SidebarEditableItem); const findPopoverIcon = () => wrapper.find('[data-testid="inherit-date-popover"]'); const findDatePicker = () => wrapper.findComponent(GlDatepicker); @@ -61,6 +57,10 @@ describe('Sidebar date Widget', () => { }); }; + beforeEach(() => { + window.gon.first_day_of_week = 1; + }); + afterEach(() => { fakeApollo = null; }); diff --git a/spec/frontend/vue_shared/components/entity_select/project_select_spec.js b/spec/frontend/vue_shared/components/entity_select/project_select_spec.js index 57dce032d30..32ce2155494 100644 --- a/spec/frontend/vue_shared/components/entity_select/project_select_spec.js +++ b/spec/frontend/vue_shared/components/entity_select/project_select_spec.js @@ -63,11 +63,8 @@ describe('ProjectSelect', () => { }; const openListbox = () => findListbox().vm.$emit('shown'); - beforeAll(() => { - gon.api_version = apiVersion; - }); - beforeEach(() => { + gon.api_version = apiVersion; mock = new MockAdapter(axios); }); |