From d80f3cd75e700b6e62910865bfd36734644ffa89 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 4 Mar 2020 09:08:20 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- spec/frontend/releases/components/app_edit_spec.js | 136 +++++++++++++-------- spec/frontend/releases/components/app_show_spec.js | 61 +++++++++ .../components/release_block_header_spec.js | 37 ++++++ .../releases/components/release_block_spec.js | 56 ++++----- 4 files changed, 206 insertions(+), 84 deletions(-) create mode 100644 spec/frontend/releases/components/app_show_spec.js (limited to 'spec/frontend/releases/components') diff --git a/spec/frontend/releases/components/app_edit_spec.js b/spec/frontend/releases/components/app_edit_spec.js index b2dbb8cc435..ac4b2b9124f 100644 --- a/spec/frontend/releases/components/app_edit_spec.js +++ b/spec/frontend/releases/components/app_edit_spec.js @@ -1,30 +1,27 @@ import Vuex from 'vuex'; import { mount } from '@vue/test-utils'; import ReleaseEditApp from '~/releases/components/app_edit.vue'; -import { release } from '../mock_data'; -import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { release as originalRelease } from '../mock_data'; +import * as commonUtils from '~/lib/utils/common_utils'; +import { BACK_URL_PARAM } from '~/releases/constants'; describe('Release edit component', () => { let wrapper; - let releaseClone; + let release; let actions; let state; - beforeEach(() => { - gon.api_version = 'v4'; - - releaseClone = convertObjectPropsToCamelCase(release, { deep: true }); - + const factory = () => { state = { - release: releaseClone, + release, markdownDocsPath: 'path/to/markdown/docs', updateReleaseApiDocsPath: 'path/to/update/release/api/docs', + releasesPagePath: 'path/to/releases/page', }; actions = { fetchRelease: jest.fn(), updateRelease: jest.fn(), - navigateToReleasesPage: jest.fn(), }; const store = new Vuex.Store({ @@ -40,58 +37,99 @@ describe('Release edit component', () => { wrapper = mount(ReleaseEditApp, { store, }); + }; - return wrapper.vm.$nextTick(); - }); + beforeEach(() => { + gon.api_version = 'v4'; - it('calls fetchRelease when the component is created', () => { - expect(actions.fetchRelease).toHaveBeenCalledTimes(1); + release = commonUtils.convertObjectPropsToCamelCase(originalRelease, { deep: true }); }); - it('renders the description text at the top of the page', () => { - expect(wrapper.find('.js-subtitle-text').text()).toBe( - 'Releases are based on Git tags. We recommend naming tags that fit within semantic versioning, for example v1.0, v2.0-pre.', - ); + afterEach(() => { + wrapper.destroy(); + wrapper = null; }); - it('renders the correct tag name in the "Tag name" field', () => { - expect(wrapper.find('#git-ref').element.value).toBe(releaseClone.tagName); - }); + describe(`basic functionality tests: all tests unrelated to the "${BACK_URL_PARAM}" parameter`, () => { + beforeEach(() => { + factory(); + }); - it('renders the correct help text under the "Tag name" field', () => { - const helperText = wrapper.find('#tag-name-help'); - const helperTextLink = helperText.find('a'); - const helperTextLinkAttrs = helperTextLink.attributes(); - - expect(helperText.text()).toBe( - 'Changing a Release tag is only supported via Releases API. More information', - ); - expect(helperTextLink.text()).toBe('More information'); - expect(helperTextLinkAttrs.href).toBe(state.updateReleaseApiDocsPath); - expect(helperTextLinkAttrs.rel).toContain('noopener'); - expect(helperTextLinkAttrs.rel).toContain('noreferrer'); - expect(helperTextLinkAttrs.target).toBe('_blank'); - }); + it('calls fetchRelease when the component is created', () => { + expect(actions.fetchRelease).toHaveBeenCalledTimes(1); + }); - it('renders the correct release title in the "Release title" field', () => { - expect(wrapper.find('#release-title').element.value).toBe(releaseClone.name); - }); + it('renders the description text at the top of the page', () => { + expect(wrapper.find('.js-subtitle-text').text()).toBe( + 'Releases are based on Git tags. We recommend naming tags that fit within semantic versioning, for example v1.0, v2.0-pre.', + ); + }); - it('renders the release notes in the "Release notes" textarea', () => { - expect(wrapper.find('#release-notes').element.value).toBe(releaseClone.description); - }); + it('renders the correct tag name in the "Tag name" field', () => { + expect(wrapper.find('#git-ref').element.value).toBe(release.tagName); + }); + + it('renders the correct help text under the "Tag name" field', () => { + const helperText = wrapper.find('#tag-name-help'); + const helperTextLink = helperText.find('a'); + const helperTextLinkAttrs = helperTextLink.attributes(); + + expect(helperText.text()).toBe( + 'Changing a Release tag is only supported via Releases API. More information', + ); + expect(helperTextLink.text()).toBe('More information'); + expect(helperTextLinkAttrs).toEqual( + expect.objectContaining({ + href: state.updateReleaseApiDocsPath, + rel: 'noopener noreferrer', + target: '_blank', + }), + ); + }); + + it('renders the correct release title in the "Release title" field', () => { + expect(wrapper.find('#release-title').element.value).toBe(release.name); + }); + + it('renders the release notes in the "Release notes" textarea', () => { + expect(wrapper.find('#release-notes').element.value).toBe(release.description); + }); + + it('renders the "Save changes" button as type="submit"', () => { + expect(wrapper.find('.js-submit-button').attributes('type')).toBe('submit'); + }); - it('renders the "Save changes" button as type="submit"', () => { - expect(wrapper.find('.js-submit-button').attributes('type')).toBe('submit'); + it('calls updateRelease when the form is submitted', () => { + wrapper.find('form').trigger('submit'); + expect(actions.updateRelease).toHaveBeenCalledTimes(1); + }); }); - it('calls updateRelease when the form is submitted', () => { - wrapper.find('form').trigger('submit'); - expect(actions.updateRelease).toHaveBeenCalledTimes(1); + describe(`when the URL does not contain a "${BACK_URL_PARAM}" parameter`, () => { + beforeEach(() => { + factory(); + }); + + it(`renders a "Cancel" button with an href pointing to "${BACK_URL_PARAM}"`, () => { + const cancelButton = wrapper.find('.js-cancel-button'); + expect(cancelButton.attributes().href).toBe(state.releasesPagePath); + }); }); - it('calls navigateToReleasesPage when the "Cancel" button is clicked', () => { - wrapper.find('.js-cancel-button').vm.$emit('click'); - expect(actions.navigateToReleasesPage).toHaveBeenCalledTimes(1); + describe(`when the URL contains a "${BACK_URL_PARAM}" parameter`, () => { + const backUrl = 'https://example.gitlab.com/back/url'; + + beforeEach(() => { + commonUtils.getParameterByName = jest + .fn() + .mockImplementation(paramToGet => ({ [BACK_URL_PARAM]: backUrl }[paramToGet])); + + factory(); + }); + + it('renders a "Cancel" button with an href pointing to the main Releases page', () => { + const cancelButton = wrapper.find('.js-cancel-button'); + expect(cancelButton.attributes().href).toBe(backUrl); + }); }); }); diff --git a/spec/frontend/releases/components/app_show_spec.js b/spec/frontend/releases/components/app_show_spec.js new file mode 100644 index 00000000000..3dc9964c25c --- /dev/null +++ b/spec/frontend/releases/components/app_show_spec.js @@ -0,0 +1,61 @@ +import Vuex from 'vuex'; +import { shallowMount } from '@vue/test-utils'; +import ReleaseShowApp from '~/releases/components/app_show.vue'; +import { release as originalRelease } from '../mock_data'; +import { GlSkeletonLoading } from '@gitlab/ui'; +import ReleaseBlock from '~/releases/components/release_block.vue'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; + +describe('Release show component', () => { + let wrapper; + let release; + let actions; + + beforeEach(() => { + release = convertObjectPropsToCamelCase(originalRelease); + }); + + const factory = state => { + actions = { + fetchRelease: jest.fn(), + }; + + const store = new Vuex.Store({ + modules: { + detail: { + namespaced: true, + actions, + state, + }, + }, + }); + + wrapper = shallowMount(ReleaseShowApp, { store }); + }; + + const findLoadingSkeleton = () => wrapper.find(GlSkeletonLoading); + const findReleaseBlock = () => wrapper.find(ReleaseBlock); + + it('calls fetchRelease when the component is created', () => { + factory({ release }); + expect(actions.fetchRelease).toHaveBeenCalledTimes(1); + }); + + it('shows a loading skeleton and hides the release block while the API call is in progress', () => { + factory({ isFetchingRelease: true }); + expect(findLoadingSkeleton().exists()).toBe(true); + expect(findReleaseBlock().exists()).toBe(false); + }); + + it('hides the loading skeleton and shows the release block when the API call finishes successfully', () => { + factory({ isFetchingRelease: false }); + expect(findLoadingSkeleton().exists()).toBe(false); + expect(findReleaseBlock().exists()).toBe(true); + }); + + it('hides both the loading skeleton and the release block when the API call fails', () => { + factory({ fetchError: new Error('Uh oh') }); + expect(findLoadingSkeleton().exists()).toBe(false); + expect(findReleaseBlock().exists()).toBe(false); + }); +}); diff --git a/spec/frontend/releases/components/release_block_header_spec.js b/spec/frontend/releases/components/release_block_header_spec.js index 44f6f63fa79..9c6cbc86d3c 100644 --- a/spec/frontend/releases/components/release_block_header_spec.js +++ b/spec/frontend/releases/components/release_block_header_spec.js @@ -4,6 +4,7 @@ import { GlLink } from '@gitlab/ui'; import ReleaseBlockHeader from '~/releases/components/release_block_header.vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { release as originalRelease } from '../mock_data'; +import { BACK_URL_PARAM } from '~/releases/constants'; describe('Release block header', () => { let wrapper; @@ -27,6 +28,7 @@ describe('Release block header', () => { const findHeader = () => wrapper.find('h2'); const findHeaderLink = () => findHeader().find(GlLink); + const findEditButton = () => wrapper.find('.js-edit-button'); describe('when _links.self is provided', () => { beforeEach(() => { @@ -51,4 +53,39 @@ describe('Release block header', () => { expect(findHeaderLink().exists()).toBe(false); }); }); + + describe('when _links.edit_url is provided', () => { + const currentUrl = 'https://example.gitlab.com/path'; + + beforeEach(() => { + Object.defineProperty(window, 'location', { + writable: true, + value: { + href: currentUrl, + }, + }); + + factory(); + }); + + it('renders an edit button', () => { + expect(findEditButton().exists()).toBe(true); + }); + + it('renders the edit button with the correct href', () => { + const expectedQueryParam = `${BACK_URL_PARAM}=${encodeURIComponent(currentUrl)}`; + const expectedUrl = `${release._links.editUrl}?${expectedQueryParam}`; + expect(findEditButton().attributes().href).toBe(expectedUrl); + }); + }); + + describe('when _links.edit is missing', () => { + beforeEach(() => { + factory({ _links: { editUrl: null } }); + }); + + it('does not render an edit button', () => { + expect(findEditButton().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/releases/components/release_block_spec.js b/spec/frontend/releases/components/release_block_spec.js index ff88e3193bc..227998b0271 100644 --- a/spec/frontend/releases/components/release_block_spec.js +++ b/spec/frontend/releases/components/release_block_spec.js @@ -7,20 +7,9 @@ import ReleaseBlockFooter from '~/releases/components/release_block_footer.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; import { release as originalRelease } from '../mock_data'; import Icon from '~/vue_shared/components/icon.vue'; -import { scrollToElement } from '~/lib/utils/common_utils'; - -const { convertObjectPropsToCamelCase } = jest.requireActual('~/lib/utils/common_utils'); - -let mockLocationHash; -jest.mock('~/lib/utils/url_utility', () => ({ - __esModule: true, - getLocationHash: jest.fn().mockImplementation(() => mockLocationHash), -})); - -jest.mock('~/lib/utils/common_utils', () => ({ - __esModule: true, - scrollToElement: jest.fn(), -})); +import * as commonUtils from '~/lib/utils/common_utils'; +import { BACK_URL_PARAM } from '~/releases/constants'; +import * as urlUtility from '~/lib/utils/url_utility'; describe('Release block', () => { let wrapper; @@ -47,7 +36,7 @@ describe('Release block', () => { beforeEach(() => { jest.spyOn($.fn, 'renderGFM'); - release = convertObjectPropsToCamelCase(originalRelease, { deep: true }); + release = commonUtils.convertObjectPropsToCamelCase(originalRelease, { deep: true }); }); afterEach(() => { @@ -61,9 +50,11 @@ describe('Release block', () => { expect(wrapper.attributes().id).toBe('v0.3'); }); - it('renders an edit button that links to the "Edit release" page', () => { + it(`renders an edit button that links to the "Edit release" page with a "${BACK_URL_PARAM}" parameter`, () => { expect(editButton().exists()).toBe(true); - expect(editButton().attributes('href')).toBe(release._links.editUrl); + expect(editButton().attributes('href')).toBe( + `${release._links.editUrl}?${BACK_URL_PARAM}=${encodeURIComponent(window.location.href)}`, + ); }); it('renders release name', () => { @@ -150,14 +141,6 @@ describe('Release block', () => { }); }); - it("does not render an edit button if release._links.editUrl isn't a string", () => { - delete release._links; - - return factory(release).then(() => { - expect(editButton().exists()).toBe(false); - }); - }); - it('does not render the milestone list if no milestones are associated to the release', () => { delete release.milestones; @@ -203,37 +186,40 @@ describe('Release block', () => { }); describe('anchor scrolling', () => { + let locationHash; + beforeEach(() => { - scrollToElement.mockClear(); + commonUtils.scrollToElement = jest.fn(); + urlUtility.getLocationHash = jest.fn().mockImplementation(() => locationHash); }); const hasTargetBlueBackground = () => wrapper.classes('bg-line-target-blue'); it('does not attempt to scroll the page if no anchor tag is included in the URL', () => { - mockLocationHash = ''; + locationHash = ''; return factory(release).then(() => { - expect(scrollToElement).not.toHaveBeenCalled(); + expect(commonUtils.scrollToElement).not.toHaveBeenCalled(); }); }); it("does not attempt to scroll the page if the anchor tag doesn't match the release's tag name", () => { - mockLocationHash = 'v0.4'; + locationHash = 'v0.4'; return factory(release).then(() => { - expect(scrollToElement).not.toHaveBeenCalled(); + expect(commonUtils.scrollToElement).not.toHaveBeenCalled(); }); }); it("attempts to scroll itself into view if the anchor tag matches the release's tag name", () => { - mockLocationHash = release.tagName; + locationHash = release.tagName; return factory(release).then(() => { - expect(scrollToElement).toHaveBeenCalledTimes(1); + expect(commonUtils.scrollToElement).toHaveBeenCalledTimes(1); - expect(scrollToElement).toHaveBeenCalledWith(wrapper.element); + expect(commonUtils.scrollToElement).toHaveBeenCalledWith(wrapper.element); }); }); it('renders with a light blue background if it is the target of the anchor', () => { - mockLocationHash = release.tagName; + locationHash = release.tagName; return factory(release).then(() => { expect(hasTargetBlueBackground()).toBe(true); @@ -241,7 +227,7 @@ describe('Release block', () => { }); it('does not render with a light blue background if it is not the target of the anchor', () => { - mockLocationHash = ''; + locationHash = ''; return factory(release).then(() => { expect(hasTargetBlueBackground()).toBe(false); -- cgit v1.2.3