From b595cb0c1dec83de5bdee18284abe86614bed33b Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 20 Jul 2022 15:40:28 +0000 Subject: Add latest changes from gitlab-org/gitlab@15-2-stable-ee --- .../releases/components/app_edit_new_spec.js | 62 ++++++++++++--- .../frontend/releases/components/app_index_spec.js | 31 ++++++++ .../components/confirm_delete_modal_spec.js | 89 ++++++++++++++++++++++ .../components/release_block_footer_spec.js | 26 ++++--- .../releases/components/release_block_spec.js | 5 +- .../frontend/releases/components/tag_field_spec.js | 8 +- 6 files changed, 193 insertions(+), 28 deletions(-) create mode 100644 spec/frontend/releases/components/confirm_delete_modal_spec.js (limited to 'spec/frontend/releases/components') diff --git a/spec/frontend/releases/components/app_edit_new_spec.js b/spec/frontend/releases/components/app_edit_new_spec.js index 80be27c92ff..cb044b9e891 100644 --- a/spec/frontend/releases/components/app_edit_new_spec.js +++ b/spec/frontend/releases/components/app_edit_new_spec.js @@ -1,21 +1,24 @@ -import { mount } from '@vue/test-utils'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { merge } from 'lodash'; import Vuex from 'vuex'; import { nextTick } from 'vue'; -import { GlFormCheckbox } from '@gitlab/ui'; -import originalRelease from 'test_fixtures/api/releases/release.json'; +import { GlDatepicker, GlFormCheckbox } from '@gitlab/ui'; +import originalOneReleaseForEditingQueryResponse from 'test_fixtures/graphql/releases/graphql/queries/one_release_for_editing.query.graphql.json'; +import { convertOneReleaseGraphQLResponse } from '~/releases/util'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import setWindowLocation from 'helpers/set_window_location_helper'; import { TEST_HOST } from 'helpers/test_constants'; -import * as commonUtils from '~/lib/utils/common_utils'; import ReleaseEditNewApp from '~/releases/components/app_edit_new.vue'; import AssetLinksForm from '~/releases/components/asset_links_form.vue'; +import ConfirmDeleteModal from '~/releases/components/confirm_delete_modal.vue'; import { BACK_URL_PARAM } from '~/releases/constants'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; +const originalRelease = originalOneReleaseForEditingQueryResponse.data.project.release; const originalMilestones = originalRelease.milestones; const releasesPagePath = 'path/to/releases/page'; +const upcomingReleaseDocsPath = 'path/to/upcoming/release/docs'; describe('Release edit/new component', () => { let wrapper; @@ -28,22 +31,24 @@ describe('Release edit/new component', () => { const factory = async ({ featureFlags = {}, store: storeUpdates = {} } = {}) => { state = { release, + isExistingRelease: true, markdownDocsPath: 'path/to/markdown/docs', releasesPagePath, projectId: '8', groupId: '42', groupMilestonesAvailable: true, + upcomingReleaseDocsPath, }; actions = { initializeRelease: jest.fn(), saveRelease: jest.fn(), addEmptyAssetLink: jest.fn(), + deleteRelease: jest.fn(), }; getters = { isValid: () => true, - isExistingRelease: () => true, validationErrors: () => ({ assets: { links: [], @@ -68,7 +73,7 @@ describe('Release edit/new component', () => { ), ); - wrapper = mount(ReleaseEditNewApp, { + wrapper = mountExtended(ReleaseEditNewApp, { store, provide: { glFeatures: featureFlags, @@ -88,7 +93,7 @@ describe('Release edit/new component', () => { mock.onGet('/api/v4/projects/8/milestones').reply(200, originalMilestones); - release = commonUtils.convertObjectPropsToCamelCase(originalRelease, { deep: true }); + release = convertOneReleaseGraphQLResponse(originalOneReleaseForEditingQueryResponse).data; }); afterEach(() => { @@ -128,6 +133,18 @@ describe('Release edit/new component', () => { expect(wrapper.find('#release-title').element.value).toBe(release.name); }); + it('renders the released at date in the "Released at" datepicker', () => { + expect(wrapper.findComponent(GlDatepicker).props('value')).toBe(release.releasedAt); + }); + + it('links to the documentation on upcoming releases in the "Released at" description', () => { + const link = wrapper.findByRole('link', { name: 'Upcoming Release' }); + + expect(link.exists()).toBe(true); + + expect(link.attributes('href')).toBe(upcomingReleaseDocsPath); + }); + it('renders the release notes in the "Release notes" textarea', () => { expect(wrapper.find('#release-notes').element.value).toBe(release.description); }); @@ -191,9 +208,7 @@ describe('Release edit/new component', () => { store: { modules: { editNew: { - getters: { - isExistingRelease: () => false, - }, + state: { isExistingRelease: false }, }, }, }, @@ -274,4 +289,31 @@ describe('Release edit/new component', () => { }); }); }); + + describe('delete', () => { + const findConfirmDeleteModal = () => wrapper.findComponent(ConfirmDeleteModal); + + it('calls the deleteRelease action on confirmation', async () => { + await factory(); + findConfirmDeleteModal().vm.$emit('delete'); + + expect(actions.deleteRelease).toHaveBeenCalled(); + }); + + it('is hidden if this is a new release', async () => { + await factory({ + store: { + modules: { + editNew: { + state: { + isExistingRelease: false, + }, + }, + }, + }, + }); + + expect(findConfirmDeleteModal().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/releases/components/app_index_spec.js b/spec/frontend/releases/components/app_index_spec.js index 63ce4c8bb17..f64f07de90e 100644 --- a/spec/frontend/releases/components/app_index_spec.js +++ b/spec/frontend/releases/components/app_index_spec.js @@ -8,6 +8,7 @@ import waitForPromises from 'helpers/wait_for_promises'; import allReleasesQuery from '~/releases/graphql/queries/all_releases.query.graphql'; import createFlash from '~/flash'; import { historyPushState } from '~/lib/utils/common_utils'; +import { sprintf, __ } from '~/locale'; import ReleasesIndexApp from '~/releases/components/app_index.vue'; import ReleaseBlock from '~/releases/components/release_block.vue'; import ReleaseSkeletonLoader from '~/releases/components/release_skeleton_loader.vue'; @@ -15,6 +16,7 @@ import ReleasesEmptyState from '~/releases/components/releases_empty_state.vue'; import ReleasesPagination from '~/releases/components/releases_pagination.vue'; import ReleasesSort from '~/releases/components/releases_sort.vue'; import { PAGE_SIZE, CREATED_ASC, DEFAULT_SORT } from '~/releases/constants'; +import { deleteReleaseSessionKey } from '~/releases/util'; Vue.use(VueApollo); @@ -44,6 +46,7 @@ describe('app_index.vue', () => { let singleRelease; let noReleases; let queryMock; + let toast; const createComponent = ({ singleResponse = Promise.resolve(singleRelease), @@ -58,12 +61,17 @@ describe('app_index.vue', () => { ], ]); + toast = jest.fn(); + wrapper = shallowMountExtended(ReleasesIndexApp, { apolloProvider, provide: { newReleasePath, projectPath, }, + mocks: { + $toast: { show: toast }, + }, }); }; @@ -395,4 +403,27 @@ describe('app_index.vue', () => { }, ); }); + + describe('after deleting', () => { + const release = 'fake release'; + const key = deleteReleaseSessionKey(projectPath); + + beforeEach(async () => { + window.sessionStorage.setItem(key, release); + + await createComponent(); + }); + + it('shows a toast', async () => { + expect(toast).toHaveBeenCalledWith( + sprintf(__('Release %{release} has been successfully deleted.'), { + release, + }), + ); + }); + + it('clears session storage', async () => { + expect(window.sessionStorage.getItem(key)).toBe(null); + }); + }); }); diff --git a/spec/frontend/releases/components/confirm_delete_modal_spec.js b/spec/frontend/releases/components/confirm_delete_modal_spec.js new file mode 100644 index 00000000000..f7c526c1ced --- /dev/null +++ b/spec/frontend/releases/components/confirm_delete_modal_spec.js @@ -0,0 +1,89 @@ +import Vue, { nextTick } from 'vue'; +import Vuex from 'vuex'; +import { GlModal } from '@gitlab/ui'; +import originalOneReleaseForEditingQueryResponse from 'test_fixtures/graphql/releases/graphql/queries/one_release_for_editing.query.graphql.json'; +import { convertOneReleaseGraphQLResponse } from '~/releases/util'; +import ConfirmDeleteModal from '~/releases/components/confirm_delete_modal.vue'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import { __, sprintf } from '~/locale'; + +Vue.use(Vuex); + +const release = convertOneReleaseGraphQLResponse(originalOneReleaseForEditingQueryResponse).data; +const deleteReleaseDocsPath = 'path/to/delete/release/docs'; + +describe('~/releases/components/confirm_delete_modal.vue', () => { + let wrapper; + let state; + + const factory = async () => { + state = { + release, + deleteReleaseDocsPath, + }; + + const store = new Vuex.Store({ + modules: { + editNew: { + namespaced: true, + state, + }, + }, + }); + + wrapper = mountExtended(ConfirmDeleteModal, { + store, + }); + + await nextTick(); + }; + + beforeEach(() => { + factory(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('button', () => { + it('should open the modal on click', async () => { + await wrapper.findByRole('button', { name: 'Delete' }).trigger('click'); + + const title = wrapper.findByText( + sprintf(__('Delete release %{release}?'), { release: release.name }), + ); + + expect(title.exists()).toBe(true); + }); + }); + + describe('modal', () => { + beforeEach(async () => { + await wrapper.findByRole('button', { name: 'Delete' }).trigger('click'); + }); + + it('confirms the user wants to delete the release', () => { + const text = wrapper.findByText(__('Are you sure you want to delete this release?')); + + expect(text.exists()).toBe(true); + }); + + it('links to the tag', () => { + const tagPath = wrapper.findByRole('link', { name: release.tagName }); + expect(tagPath.attributes('href')).toBe(release.tagPath); + }); + + it('links to the docs on deleting releases', () => { + const docsPath = wrapper.findByRole('link', { name: 'Deleting a release' }); + + expect(docsPath.attributes('href')).toBe(deleteReleaseDocsPath); + }); + + it('emits a delete event on action primary', () => { + wrapper.findComponent(GlModal).vm.$emit('primary'); + + expect(wrapper.emitted('delete')).toEqual([[]]); + }); + }); +}); diff --git a/spec/frontend/releases/components/release_block_footer_spec.js b/spec/frontend/releases/components/release_block_footer_spec.js index b095e9e1d78..848e802df4b 100644 --- a/spec/frontend/releases/components/release_block_footer_spec.js +++ b/spec/frontend/releases/components/release_block_footer_spec.js @@ -2,14 +2,16 @@ import { GlLink, GlIcon } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import { cloneDeep } from 'lodash'; import { nextTick } from 'vue'; -import originalRelease from 'test_fixtures/api/releases/release.json'; +import originalOneReleaseQueryResponse from 'test_fixtures/graphql/releases/graphql/queries/one_release.query.graphql.json'; +import { convertOneReleaseGraphQLResponse } from '~/releases/util'; import { trimText } from 'helpers/text_helper'; -import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import ReleaseBlockFooter from '~/releases/components/release_block_footer.vue'; // TODO: Encapsulate date helpers https://gitlab.com/gitlab-org/gitlab/-/issues/320883 const MONTHS_IN_MS = 1000 * 60 * 60 * 24 * 31; -const mockFutureDate = new Date(new Date().getTime() + MONTHS_IN_MS).toISOString(); +const mockFutureDate = new Date(new Date().getTime() + MONTHS_IN_MS); + +const originalRelease = convertOneReleaseGraphQLResponse(originalOneReleaseQueryResponse).data; describe('Release block footer', () => { let wrapper; @@ -18,7 +20,7 @@ describe('Release block footer', () => { const factory = async (props = {}) => { wrapper = mount(ReleaseBlockFooter, { propsData: { - ...convertObjectPropsToCamelCase(release, { deep: true }), + ...originalRelease, ...props, }, }); @@ -55,8 +57,8 @@ describe('Release block footer', () => { const commitLink = commitInfoSectionLink(); expect(commitLink.exists()).toBe(true); - expect(commitLink.text()).toBe(release.commit.short_id); - expect(commitLink.attributes('href')).toBe(release.commit_path); + expect(commitLink.text()).toBe(release.commit.shortId); + expect(commitLink.attributes('href')).toBe(release.commitPath); }); it('renders the tag icon', () => { @@ -70,8 +72,8 @@ describe('Release block footer', () => { const commitLink = tagInfoSection().find(GlLink); expect(commitLink.exists()).toBe(true); - expect(commitLink.text()).toBe(release.tag_name); - expect(commitLink.attributes('href')).toBe(release.tag_path); + expect(commitLink.text()).toBe(release.tagName); + expect(commitLink.attributes('href')).toBe(release.tagPath); }); it('renders the author and creation time info', () => { @@ -114,14 +116,14 @@ describe('Release block footer', () => { const avatarImg = authorDateInfoSection().find('img'); expect(avatarImg.exists()).toBe(true); - expect(avatarImg.attributes('src')).toBe(release.author.avatar_url); + expect(avatarImg.attributes('src')).toBe(release.author.avatarUrl); }); it("renders a link to the author's profile", () => { const authorLink = authorDateInfoSection().find(GlLink); expect(authorLink.exists()).toBe(true); - expect(authorLink.attributes('href')).toBe(release.author.web_url); + expect(authorLink.attributes('href')).toBe(release.author.webUrl); }); }); @@ -138,7 +140,7 @@ describe('Release block footer', () => { it('renders the commit SHA as plain text (instead of a link)', () => { expect(commitInfoSectionLink().exists()).toBe(false); - expect(commitInfoSection().text()).toBe(release.commit.short_id); + expect(commitInfoSection().text()).toBe(release.commit.shortId); }); }); @@ -155,7 +157,7 @@ describe('Release block footer', () => { it('renders the tag name as plain text (instead of a link)', () => { expect(tagInfoSectionLink().exists()).toBe(false); - expect(tagInfoSection().text()).toBe(release.tag_name); + expect(tagInfoSection().text()).toBe(release.tagName); }); }); diff --git a/spec/frontend/releases/components/release_block_spec.js b/spec/frontend/releases/components/release_block_spec.js index c4910ae9b2f..17e2af687a6 100644 --- a/spec/frontend/releases/components/release_block_spec.js +++ b/spec/frontend/releases/components/release_block_spec.js @@ -1,7 +1,8 @@ import { mount } from '@vue/test-utils'; import $ from 'jquery'; import { nextTick } from 'vue'; -import originalRelease from 'test_fixtures/api/releases/release.json'; +import originalOneReleaseQueryResponse from 'test_fixtures/graphql/releases/graphql/queries/one_release.query.graphql.json'; +import { convertOneReleaseGraphQLResponse } from '~/releases/util'; import * as commonUtils from '~/lib/utils/common_utils'; import * as urlUtility from '~/lib/utils/url_utility'; import EvidenceBlock from '~/releases/components/evidence_block.vue'; @@ -34,7 +35,7 @@ describe('Release block', () => { beforeEach(() => { jest.spyOn($.fn, 'renderGFM'); - release = commonUtils.convertObjectPropsToCamelCase(originalRelease, { deep: true }); + release = convertOneReleaseGraphQLResponse(originalOneReleaseQueryResponse).data; }); afterEach(() => { diff --git a/spec/frontend/releases/components/tag_field_spec.js b/spec/frontend/releases/components/tag_field_spec.js index db08f874959..e7b9aa4abbb 100644 --- a/spec/frontend/releases/components/tag_field_spec.js +++ b/spec/frontend/releases/components/tag_field_spec.js @@ -9,14 +9,14 @@ describe('releases/components/tag_field', () => { let store; let wrapper; - const createComponent = ({ tagName }) => { + const createComponent = ({ isExistingRelease }) => { store = createStore({ modules: { editNew: createEditNewModule({}), }, }); - store.state.editNew.tagName = tagName; + store.state.editNew.isExistingRelease = isExistingRelease; wrapper = shallowMount(TagField, { store }); }; @@ -31,7 +31,7 @@ describe('releases/components/tag_field', () => { describe('when an existing release is being edited', () => { beforeEach(() => { - createComponent({ tagName: 'v1.0' }); + createComponent({ isExistingRelease: true }); }); it('renders the TagFieldExisting component', () => { @@ -45,7 +45,7 @@ describe('releases/components/tag_field', () => { describe('when a new release is being created', () => { beforeEach(() => { - createComponent({ tagName: null }); + createComponent({ isExistingRelease: false }); }); it('renders the TagFieldNew component', () => { -- cgit v1.2.3