diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-18 15:08:41 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-18 15:08:41 +0300 |
commit | 5c5d24f032b67d98452f391192386e330a0f880c (patch) | |
tree | 9e58d94388f63cb825e426fea2c4929740604768 /spec/frontend/releases | |
parent | 5ccb67600c63549774a28811d177dd673e6dd4d0 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/releases')
6 files changed, 303 insertions, 1 deletions
diff --git a/spec/frontend/releases/__snapshots__/util_spec.js.snap b/spec/frontend/releases/__snapshots__/util_spec.js.snap index 0bf0ef1ded4..90a33152877 100644 --- a/spec/frontend/releases/__snapshots__/util_spec.js.snap +++ b/spec/frontend/releases/__snapshots__/util_spec.js.snap @@ -269,6 +269,7 @@ Object { "name": "The first release", "releasedAt": 2018-12-10T00:00:00.000Z, "tagName": "v1.1", + "tagPath": "/releases-namespace/releases-project/-/tags/v1.1", }, } `; diff --git a/spec/frontend/releases/components/app_edit_new_spec.js b/spec/frontend/releases/components/app_edit_new_spec.js index bccb0849800..cb044b9e891 100644 --- a/spec/frontend/releases/components/app_edit_new_spec.js +++ b/spec/frontend/releases/components/app_edit_new_spec.js @@ -11,6 +11,7 @@ import setWindowLocation from 'helpers/set_window_location_helper'; import { TEST_HOST } from 'helpers/test_constants'; 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'; @@ -43,6 +44,7 @@ describe('Release edit/new component', () => { initializeRelease: jest.fn(), saveRelease: jest.fn(), addEmptyAssetLink: jest.fn(), + deleteRelease: jest.fn(), }; getters = { @@ -287,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/stores/modules/detail/actions_spec.js b/spec/frontend/releases/stores/modules/detail/actions_spec.js index c7ee214013f..ce3b690213c 100644 --- a/spec/frontend/releases/stores/modules/detail/actions_spec.js +++ b/spec/frontend/releases/stores/modules/detail/actions_spec.js @@ -9,10 +9,15 @@ import { ASSET_LINK_TYPE } from '~/releases/constants'; import createReleaseAssetLinkMutation from '~/releases/graphql/mutations/create_release_link.mutation.graphql'; import deleteReleaseAssetLinkMutation from '~/releases/graphql/mutations/delete_release_link.mutation.graphql'; import updateReleaseMutation from '~/releases/graphql/mutations/update_release.mutation.graphql'; +import deleteReleaseMutation from '~/releases/graphql/mutations/delete_release.mutation.graphql'; import * as actions from '~/releases/stores/modules/edit_new/actions'; import * as types from '~/releases/stores/modules/edit_new/mutation_types'; import createState from '~/releases/stores/modules/edit_new/state'; -import { gqClient, convertOneReleaseGraphQLResponse } from '~/releases/util'; +import { + gqClient, + convertOneReleaseGraphQLResponse, + deleteReleaseSessionKey, +} from '~/releases/util'; jest.mock('~/api/tags_api'); @@ -586,6 +591,133 @@ describe('Release edit/new actions', () => { }); }); + describe('deleteRelease', () => { + let getters; + let dispatch; + let commit; + let release; + + beforeEach(() => { + getters = { + releaseDeleteMutationVariables: { + input: { + projectPath: 'test-org/test', + tagName: 'v1.0', + }, + }, + }; + + release = convertOneReleaseGraphQLResponse(releaseResponse).data; + + setupState({ + release, + originalRelease: release, + ...getters, + }); + + dispatch = jest.fn(); + commit = jest.fn(); + + gqClient.mutate.mockResolvedValue({ + data: { + releaseDelete: { + errors: [], + }, + releaseAssetLinkDelete: { + errors: [], + }, + }, + }); + }); + + describe('when the delete is successful', () => { + beforeEach(() => { + window.sessionStorage.clear(); + }); + + it('dispatches receiveSaveReleaseSuccess', async () => { + await actions.deleteRelease({ commit, dispatch, state, getters }); + expect(dispatch.mock.calls).toEqual([ + ['receiveSaveReleaseSuccess', state.releasesPagePath], + ]); + }); + + it('deletes the release', async () => { + await actions.deleteRelease({ commit, dispatch, state, getters }); + expect(gqClient.mutate.mock.calls[0]).toEqual([ + { + mutation: deleteReleaseMutation, + variables: getters.releaseDeleteMutationVariables, + }, + ]); + }); + + it('stores the name for toasting', async () => { + await actions.deleteRelease({ commit, dispatch, state, getters }); + expect(window.sessionStorage.getItem(deleteReleaseSessionKey(state.projectPath))).toBe( + state.release.name, + ); + }); + }); + + describe('when the delete request fails', () => { + beforeEach(() => { + gqClient.mutate.mockRejectedValue(error); + }); + + it('dispatches requestDeleteRelease and receiveSaveReleaseError with an error object', async () => { + await actions.deleteRelease({ commit, dispatch, state, getters }); + + expect(commit.mock.calls).toContainEqual([types.RECEIVE_SAVE_RELEASE_ERROR, error]); + }); + + it('shows a flash message', async () => { + await actions.deleteRelease({ commit, dispatch, state, getters }); + + expect(createFlash).toHaveBeenCalledTimes(1); + expect(createFlash).toHaveBeenCalledWith({ + message: 'Something went wrong while deleting the release.', + }); + }); + }); + + describe('when the delete returns errors', () => { + beforeEach(() => { + gqClient.mutate.mockResolvedValue({ + data: { + releaseUpdate: { + errors: ['Something went wrong!'], + }, + releaseAssetLinkDelete: { + errors: [], + }, + releaseAssetLinkCreate: { + errors: [], + }, + }, + }); + }); + + it('dispatches requestDeleteRelease and receiveSaveReleaseError with an error object', async () => { + await actions.deleteRelease({ commit, dispatch, state, getters }); + + expect(commit.mock.calls).toContainEqual([ + types.RECEIVE_SAVE_RELEASE_ERROR, + expect.any(Error), + ]); + }); + + it('shows a flash message', async () => { + await actions.deleteRelease({ commit, dispatch, state, getters }); + + expect(createFlash).toHaveBeenCalledTimes(1); + expect(createFlash).toHaveBeenCalledWith({ + message: 'Something went wrong while deleting the release.', + }); + }); + }); + }); + describe('fetchTagNotes', () => { const tagName = 'v8.0.0'; diff --git a/spec/frontend/releases/stores/modules/detail/getters_spec.js b/spec/frontend/releases/stores/modules/detail/getters_spec.js index 2fb38e6ec1a..4ac6eaebaa2 100644 --- a/spec/frontend/releases/stores/modules/detail/getters_spec.js +++ b/spec/frontend/releases/stores/modules/detail/getters_spec.js @@ -369,6 +369,26 @@ describe('Release edit/new getters', () => { }); }); + describe('releaseDeleteMutationVariables', () => { + it('returns all the data needed for the releaseDelete GraphQL mutation', () => { + const state = { + projectPath: 'test-org/test', + release: { tagName: 'v1.0' }, + }; + + const expectedVariables = { + input: { + projectPath: 'test-org/test', + tagName: 'v1.0', + }, + }; + + const actualVariables = getters.releaseDeleteMutationVariables(state); + + expect(actualVariables).toEqual(expectedVariables); + }); + }); + describe('formattedReleaseNotes', () => { it.each` description | includeTagNotes | tagNotes | included |