diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-17 16:05:49 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-17 16:05:49 +0000 |
commit | 43a25d93ebdabea52f99b05e15b06250cd8f07d7 (patch) | |
tree | dceebdc68925362117480a5d672bcff122fb625b /spec/frontend/design_management/components | |
parent | 20c84b99005abd1c82101dfeff264ac50d2df211 (diff) | |
download | gitlab-ce-0f94cf6ca9d272d8e0fda4a7a597866cf3dc1fc0.tar.gz |
Add latest changes from gitlab-org/gitlab@16-0-stable-eev16.0.0-rc4216-0-stable
Diffstat (limited to 'spec/frontend/design_management/components')
21 files changed, 447 insertions, 378 deletions
diff --git a/spec/frontend/design_management/components/delete_button_spec.js b/spec/frontend/design_management/components/delete_button_spec.js index 426a61f5a47..cacda9a475e 100644 --- a/spec/frontend/design_management/components/delete_button_spec.js +++ b/spec/frontend/design_management/components/delete_button_spec.js @@ -21,10 +21,6 @@ describe('Batch delete button component', () => { }); } - afterEach(() => { - wrapper.destroy(); - }); - it('renders non-disabled button by default', () => { createComponent(); @@ -34,7 +30,7 @@ describe('Batch delete button component', () => { it('renders disabled button when design is deleting', () => { createComponent({ isDeleting: true }); - expect(findButton().attributes('disabled')).toBe('true'); + expect(findButton().attributes('disabled')).toBeDefined(); }); it('emits `delete-selected-designs` event on modal ok click', async () => { diff --git a/spec/frontend/design_management/components/design_notes/__snapshots__/design_note_spec.js.snap b/spec/frontend/design_management/components/design_notes/__snapshots__/design_note_spec.js.snap index 402e55347af..3b407d11041 100644 --- a/spec/frontend/design_management/components/design_notes/__snapshots__/design_note_spec.js.snap +++ b/spec/frontend/design_management/components/design_notes/__snapshots__/design_note_spec.js.snap @@ -58,6 +58,7 @@ exports[`Design note component should match the snapshot 1`] = ` > <time-ago-tooltip-stub cssclass="" + datetimeformat="DATE_WITH_TIME_FORMAT" time="2019-07-26T15:02:20Z" tooltipplacement="bottom" /> @@ -70,6 +71,8 @@ exports[`Design note component should match the snapshot 1`] = ` > <!----> + + <!----> </div> </div> diff --git a/spec/frontend/design_management/components/design_notes/design_discussion_spec.js b/spec/frontend/design_management/components/design_notes/design_discussion_spec.js index 2091e1e08dd..a6ab147884f 100644 --- a/spec/frontend/design_management/components/design_notes/design_discussion_spec.js +++ b/spec/frontend/design_management/components/design_notes/design_discussion_spec.js @@ -1,18 +1,22 @@ import { GlLoadingIcon } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; -import { ApolloMutation } from 'vue-apollo'; import { nextTick } from 'vue'; +import waitForPromises from 'helpers/wait_for_promises'; +import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; import DesignDiscussion from '~/design_management/components/design_notes/design_discussion.vue'; import DesignNote from '~/design_management/components/design_notes/design_note.vue'; import DesignNoteSignedOut from '~/design_management/components/design_notes/design_note_signed_out.vue'; import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue'; import ToggleRepliesWidget from '~/design_management/components/design_notes/toggle_replies_widget.vue'; -import createNoteMutation from '~/design_management/graphql/mutations/create_note.mutation.graphql'; import toggleResolveDiscussionMutation from '~/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql'; import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue'; +import destroyNoteMutation from '~/design_management/graphql/mutations/destroy_note.mutation.graphql'; +import { DELETE_NOTE_ERROR_MSG } from '~/design_management/constants'; import mockDiscussion from '../../mock_data/discussion'; import notes from '../../mock_data/notes'; +jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'); + const defaultMockDiscussion = { id: '0', resolved: false, @@ -23,7 +27,6 @@ const defaultMockDiscussion = { const DEFAULT_TODO_COUNT = 2; describe('Design discussions component', () => { - const originalGon = window.gon; let wrapper; const findDesignNotes = () => wrapper.findAllComponents(DesignNote); @@ -34,18 +37,7 @@ describe('Design discussions component', () => { const findResolvedMessage = () => wrapper.find('[data-testid="resolved-message"]'); const findResolveLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findResolveCheckbox = () => wrapper.find('[data-testid="resolve-checkbox"]'); - const findApolloMutation = () => wrapper.findComponent(ApolloMutation); - const mutationVariables = { - mutation: createNoteMutation, - variables: { - input: { - noteableId: 'noteable-id', - body: 'test', - discussionId: '0', - }, - }, - }; const registerPath = '/users/sign_up?redirect_to_referer=yes'; const signInPath = '/users/sign_in?redirect_to_referer=yes'; const mutate = jest.fn().mockResolvedValue({ data: { createNote: { errors: [] } } }); @@ -59,7 +51,7 @@ describe('Design discussions component', () => { provider: { clients: { defaultClient: { readQuery } } }, }; - function createComponent(props = {}, data = {}) { + function createComponent({ props = {}, data = {}, apolloConfig = {} } = {}) { wrapper = mount(DesignDiscussion, { propsData: { resolvedDiscussionsExpanded: true, @@ -82,7 +74,10 @@ describe('Design discussions component', () => { issueIid: '1', }, mocks: { - $apollo, + $apollo: { + ...$apollo, + ...apolloConfig, + }, $route: { hash: '#note_1', params: { @@ -101,16 +96,17 @@ describe('Design discussions component', () => { }); afterEach(() => { - wrapper.destroy(); - window.gon = originalGon; + confirmAction.mockReset(); }); describe('when discussion is not resolvable', () => { beforeEach(() => { createComponent({ - discussion: { - ...defaultMockDiscussion, - resolvable: false, + props: { + discussion: { + ...defaultMockDiscussion, + resolvable: false, + }, }, }); }); @@ -171,11 +167,13 @@ describe('Design discussions component', () => { innerText: DEFAULT_TODO_COUNT, }); createComponent({ - discussion: { - ...defaultMockDiscussion, - resolved: true, - resolvedBy: notes[0].author, - resolvedAt: '2020-05-08T07:10:45Z', + props: { + discussion: { + ...defaultMockDiscussion, + resolved: true, + resolvedBy: notes[0].author, + resolvedAt: '2020-05-08T07:10:45Z', + }, }, }); }); @@ -206,10 +204,10 @@ describe('Design discussions component', () => { }); it('emit todo:toggle when discussion is resolved', async () => { - createComponent( - { discussionWithOpenForm: defaultMockDiscussion.id }, - { discussionComment: 'test', isFormRendered: true }, - ); + createComponent({ + props: { discussionWithOpenForm: defaultMockDiscussion.id }, + data: { isFormRendered: true }, + }); findResolveButton().trigger('click'); findReplyForm().vm.$emit('submitForm'); @@ -261,32 +259,28 @@ describe('Design discussions component', () => { expect(findReplyForm().exists()).toBe(true); }); - it('calls mutation on submitting form and closes the form', async () => { - createComponent( - { discussionWithOpenForm: defaultMockDiscussion.id }, - { discussionComment: 'test', isFormRendered: true }, - ); + it('closes the form when note submit mutation is completed', async () => { + createComponent({ + props: { discussionWithOpenForm: defaultMockDiscussion.id }, + data: { isFormRendered: true }, + }); - findReplyForm().vm.$emit('submit-form'); - expect(mutate).toHaveBeenCalledWith(mutationVariables); + findReplyForm().vm.$emit('note-submit-complete', { data: { createNote: {} } }); - await mutate(); await nextTick(); expect(findReplyForm().exists()).toBe(false); }); it('clears the discussion comment on closing comment form', async () => { - createComponent( - { discussionWithOpenForm: defaultMockDiscussion.id }, - { discussionComment: 'test', isFormRendered: true }, - ); + createComponent({ + props: { discussionWithOpenForm: defaultMockDiscussion.id }, + data: { isFormRendered: true }, + }); await nextTick(); findReplyForm().vm.$emit('cancel-form'); - expect(wrapper.vm.discussionComment).toBe(''); - await nextTick(); expect(findReplyForm().exists()).toBe(false); }); @@ -295,15 +289,15 @@ describe('Design discussions component', () => { it.each([notes[0], notes[0].discussion.notes.nodes[1]])( 'applies correct class to all notes in the active discussion', (note) => { - createComponent( - { discussion: mockDiscussion }, - { + createComponent({ + props: { discussion: mockDiscussion }, + data: { activeDiscussion: { id: note.id, source: 'pin', }, }, - ); + }); expect( wrapper @@ -329,10 +323,10 @@ describe('Design discussions component', () => { }); it('calls toggleResolveDiscussion mutation after adding a note if checkbox was checked', () => { - createComponent( - { discussionWithOpenForm: defaultMockDiscussion.id }, - { discussionComment: 'test', isFormRendered: true }, - ); + createComponent({ + props: { discussionWithOpenForm: defaultMockDiscussion.id }, + data: { isFormRendered: true }, + }); findResolveButton().trigger('click'); findReplyForm().vm.$emit('submitForm'); @@ -359,15 +353,15 @@ describe('Design discussions component', () => { beforeEach(() => { window.gon = { current_user_id: null }; - createComponent( - { + createComponent({ + props: { discussion: { ...defaultMockDiscussion, }, discussionWithOpenForm: defaultMockDiscussion.id, }, - { discussionComment: 'test', isFormRendered: true }, - ); + data: { isFormRendered: true }, + }); }); it('does not render resolve discussion button', () => { @@ -378,10 +372,6 @@ describe('Design discussions component', () => { expect(findReplyPlaceholder().exists()).toBe(false); }); - it('does not render apollo-mutation component', () => { - expect(findApolloMutation().exists()).toBe(false); - }); - it('renders design-note-signed-out component', () => { expect(findDesignNoteSignedOut().exists()).toBe(true); expect(findDesignNoteSignedOut().props()).toMatchObject({ @@ -390,4 +380,64 @@ describe('Design discussions component', () => { }); }); }); + + it('should open confirmation modal when the note emits `delete-note` event', () => { + createComponent(); + + findDesignNotes().at(0).vm.$emit('delete-note', { id: '1' }); + expect(confirmAction).toHaveBeenCalled(); + }); + + describe('when confirmation modal is opened', () => { + const noteId = 'note-test-id'; + + it('sends the mutation with correct variables', async () => { + confirmAction.mockResolvedValueOnce(true); + const destroyNoteMutationSuccess = jest.fn().mockResolvedValue({ + data: { destroyNote: { note: null, __typename: 'DestroyNote', errors: [] } }, + }); + createComponent({ apolloConfig: { mutate: destroyNoteMutationSuccess } }); + + findDesignNotes().at(0).vm.$emit('delete-note', { id: noteId }); + + expect(confirmAction).toHaveBeenCalled(); + + await waitForPromises(); + + expect(destroyNoteMutationSuccess).toHaveBeenCalledWith({ + update: expect.any(Function), + mutation: destroyNoteMutation, + variables: { + input: { + id: noteId, + }, + }, + optimisticResponse: { + destroyNote: { + note: null, + errors: [], + __typename: 'DestroyNotePayload', + }, + }, + }); + }); + + it('emits `delete-note-error` event if GraphQL mutation fails', async () => { + confirmAction.mockResolvedValueOnce(true); + const destroyNoteMutationError = jest.fn().mockRejectedValue(new Error('GraphQL error')); + createComponent({ apolloConfig: { mutate: destroyNoteMutationError } }); + + findDesignNotes().at(0).vm.$emit('delete-note', { id: noteId }); + + await waitForPromises(); + + expect(destroyNoteMutationError).toHaveBeenCalled(); + + await waitForPromises(); + + expect(wrapper.emitted()).toEqual({ + 'delete-note-error': [[DELETE_NOTE_ERROR_MSG]], + }); + }); + }); }); diff --git a/spec/frontend/design_management/components/design_notes/design_note_signed_out_spec.js b/spec/frontend/design_management/components/design_notes/design_note_signed_out_spec.js index e71bb5ab520..95b08b89809 100644 --- a/spec/frontend/design_management/components/design_notes/design_note_signed_out_spec.js +++ b/spec/frontend/design_management/components/design_notes/design_note_signed_out_spec.js @@ -18,10 +18,6 @@ function createComponent(isAddDiscussion = false) { describe('DesignNoteSignedOut', () => { let wrapper; - afterEach(() => { - wrapper.destroy(); - }); - it('renders message containing register and sign-in links while user wants to reply to a discussion', () => { wrapper = createComponent(); diff --git a/spec/frontend/design_management/components/design_notes/design_note_spec.js b/spec/frontend/design_management/components/design_notes/design_note_spec.js index df511586c10..6f5b282fa3b 100644 --- a/spec/frontend/design_management/components/design_notes/design_note_spec.js +++ b/spec/frontend/design_management/components/design_notes/design_note_spec.js @@ -1,6 +1,6 @@ import { ApolloMutation } from 'vue-apollo'; import { nextTick } from 'vue'; -import { GlAvatar, GlAvatarLink } from '@gitlab/ui'; +import { GlAvatar, GlAvatarLink, GlDropdown } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import DesignNote from '~/design_management/components/design_notes/design_note.vue'; import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue'; @@ -38,6 +38,8 @@ describe('Design note component', () => { const findReplyForm = () => wrapper.findComponent(DesignReplyForm); const findEditButton = () => wrapper.findByTestId('note-edit'); const findNoteContent = () => wrapper.findByTestId('note-text'); + const findDropdown = () => wrapper.findComponent(GlDropdown); + const findDeleteNoteButton = () => wrapper.find('[data-testid="delete-note-button"]'); function createComponent(props = {}, data = { isEditing: false }) { wrapper = shallowMountExtended(DesignNote, { @@ -63,10 +65,6 @@ describe('Design note component', () => { }); } - afterEach(() => { - wrapper.destroy(); - }); - it('should match the snapshot', () => { createComponent({ note, @@ -112,6 +110,14 @@ describe('Design note component', () => { expect(findEditButton().exists()).toBe(false); }); + it('should not display a dropdown if user does not have a permission to delete note', () => { + createComponent({ + note, + }); + + expect(findDropdown().exists()).toBe(false); + }); + describe('when user has a permission to edit note', () => { it('should open an edit form on edit button click', async () => { createComponent({ @@ -158,15 +164,47 @@ describe('Design note component', () => { expect(findNoteContent().exists()).toBe(true); }); - it('calls a mutation on submit-form event and hides a form', async () => { - findReplyForm().vm.$emit('submit-form'); - expect(mutate).toHaveBeenCalled(); + it('hides a form after update mutation is completed', async () => { + findReplyForm().vm.$emit('note-submit-complete', { data: { updateNote: { errors: [] } } }); - await mutate(); await nextTick(); expect(findReplyForm().exists()).toBe(false); expect(findNoteContent().exists()).toBe(true); }); }); }); + + describe('when user has a permission to delete note', () => { + it('should display a dropdown', () => { + createComponent({ + note: { + ...note, + userPermissions: { + adminNote: true, + }, + }, + }); + + expect(findDropdown().exists()).toBe(true); + }); + }); + + it('should emit `delete-note` event with proper payload when delete note button is clicked', () => { + const payload = { + ...note, + userPermissions: { + adminNote: true, + }, + }; + + createComponent({ + note: { + ...payload, + }, + }); + + findDeleteNoteButton().vm.$emit('click'); + + expect(wrapper.emitted()).toEqual({ 'delete-note': [[{ ...payload }]] }); + }); }); diff --git a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js index f4d4f9cf896..f08efc0c685 100644 --- a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js +++ b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js @@ -1,46 +1,95 @@ +import { GlAlert } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; -import { nextTick } from 'vue'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; import Autosave from '~/autosave'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; +import createNoteMutation from '~/design_management/graphql/mutations/create_note.mutation.graphql'; import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue'; +import { + ADD_DISCUSSION_COMMENT_ERROR, + ADD_IMAGE_DIFF_NOTE_ERROR, + UPDATE_IMAGE_DIFF_NOTE_ERROR, + UPDATE_NOTE_ERROR, +} from '~/design_management/utils/error_messages'; +import { + mockNoteSubmitSuccessMutationResponse, + mockNoteSubmitFailureMutationResponse, +} from '../../mock_data/apollo_mock'; + +Vue.use(VueApollo); jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'); jest.mock('~/autosave'); describe('Design reply form component', () => { let wrapper; - let originalGon; + let mockApollo; const findTextarea = () => wrapper.find('textarea'); const findSubmitButton = () => wrapper.findComponent({ ref: 'submitButton' }); const findCancelButton = () => wrapper.findComponent({ ref: 'cancelButton' }); - - function createComponent(props = {}, mountOptions = {}) { + const findAlert = () => wrapper.findComponent(GlAlert); + + const mockNoteableId = 'gid://gitlab/DesignManagement::Design/6'; + const mockComment = 'New comment'; + const mockDiscussionId = 'gid://gitlab/Discussion/6466a72f35b163f3c3e52d7976a09387f2c573e8'; + const createNoteMutationData = { + input: { + noteableId: mockNoteableId, + discussionId: mockDiscussionId, + body: mockComment, + }, + }; + + const ctrlKey = { + ctrlKey: true, + }; + const metaKey = { + metaKey: true, + }; + const mockMutationHandler = jest.fn().mockResolvedValue(mockNoteSubmitSuccessMutationResponse); + + function createComponent({ + props = {}, + mountOptions = {}, + data = {}, + mutationHandler = mockMutationHandler, + } = {}) { + mockApollo = createMockApollo([[createNoteMutation, mutationHandler]]); wrapper = mount(DesignReplyForm, { propsData: { + designNoteMutation: createNoteMutation, + noteableId: mockNoteableId, + markdownDocsPath: 'path/to/markdown/docs', + markdownPreviewPath: 'path/to/markdown/preview', value: '', - isSaving: false, - noteableId: 'gid://gitlab/DesignManagement::Design/6', ...props, }, ...mountOptions, + apolloProvider: mockApollo, + data() { + return { + ...data, + }; + }, }); } beforeEach(() => { - originalGon = window.gon; window.gon.current_user_id = 1; }); afterEach(() => { - wrapper.destroy(); - window.gon = originalGon; + mockApollo = null; confirmAction.mockReset(); }); it('textarea has focus after component mount', () => { // We need to attach to document, so that `document.activeElement` is properly set in jsdom - createComponent({}, { attachTo: document.body }); + createComponent({ mountOptions: { attachTo: document.body } }); expect(findTextarea().element).toEqual(document.activeElement); }); @@ -64,7 +113,7 @@ describe('Design reply form component', () => { }); it('renders button text as "Save comment" when creating a comment', () => { - createComponent({ isNewComment: false }); + createComponent({ props: { isNewComment: false } }); expect(findSubmitButton().html()).toMatchSnapshot(); }); @@ -75,9 +124,8 @@ describe('Design reply form component', () => { ${'gid://gitlab/DiffDiscussion/123'} | ${123} `( 'initializes autosave support on discussion with proper key', - async ({ discussionId, shortDiscussionId }) => { - createComponent({ discussionId }); - await nextTick(); + ({ discussionId, shortDiscussionId }) => { + createComponent({ props: { discussionId } }); expect(Autosave).toHaveBeenCalledWith(expect.any(Element), [ 'Discussion', @@ -89,31 +137,21 @@ describe('Design reply form component', () => { describe('when form has no text', () => { beforeEach(() => { - createComponent({ - value: '', - }); + createComponent(); }); it('submit button is disabled', () => { expect(findSubmitButton().attributes().disabled).toBe('disabled'); }); - it('does not emit submitForm event on textarea ctrl+enter keydown', async () => { - findTextarea().trigger('keydown.enter', { - ctrlKey: true, - }); - - await nextTick(); - expect(wrapper.emitted('submit-form')).toBeUndefined(); - }); - - it('does not emit submitForm event on textarea meta+enter keydown', async () => { - findTextarea().trigger('keydown.enter', { - metaKey: true, - }); + it.each` + key | keyData + ${'ctrl'} | ${ctrlKey} + ${'meta'} | ${metaKey} + `('does not perform mutation on textarea $key+enter keydown', ({ keyData }) => { + findTextarea().trigger('keydown.enter', keyData); - await nextTick(); - expect(wrapper.emitted('submit-form')).toBeUndefined(); + expect(mockMutationHandler).not.toHaveBeenCalled(); }); it('emits cancelForm event on pressing escape button on textarea', () => { @@ -129,118 +167,150 @@ describe('Design reply form component', () => { }); }); - describe('when form has text', () => { - beforeEach(() => { - createComponent({ - value: 'test', - }); - }); - + describe('when the form has text', () => { it('submit button is enabled', () => { + createComponent({ props: { value: mockComment } }); expect(findSubmitButton().attributes().disabled).toBeUndefined(); }); - it('emits submitForm event on Comment button click', async () => { - const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset'); + it('calls a mutation on submit button click event', async () => { + const mockMutationVariables = { + noteableId: mockNoteableId, + discussionId: mockDiscussionId, + }; - findSubmitButton().vm.$emit('click'); + createComponent({ + props: { + mutationVariables: mockMutationVariables, + value: mockComment, + }, + }); - await nextTick(); - expect(wrapper.emitted('submit-form')).toHaveLength(1); - expect(autosaveResetSpy).toHaveBeenCalled(); - }); + findSubmitButton().vm.$emit('click'); - it('emits submitForm event on textarea ctrl+enter keydown', async () => { - const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset'); + expect(mockMutationHandler).toHaveBeenCalledWith(createNoteMutationData); - findTextarea().trigger('keydown.enter', { - ctrlKey: true, - }); + await waitForPromises(); - await nextTick(); - expect(wrapper.emitted('submit-form')).toHaveLength(1); - expect(autosaveResetSpy).toHaveBeenCalled(); + expect(wrapper.emitted('note-submit-complete')).toEqual([ + [mockNoteSubmitSuccessMutationResponse], + ]); }); - it('emits submitForm event on textarea meta+enter keydown', async () => { - const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset'); + it.each` + key | keyData + ${'ctrl'} | ${ctrlKey} + ${'meta'} | ${metaKey} + `('does perform mutation on textarea $key+enter keydown', async ({ keyData }) => { + const mockMutationVariables = { + noteableId: mockNoteableId, + discussionId: mockDiscussionId, + }; - findTextarea().trigger('keydown.enter', { - metaKey: true, + createComponent({ + props: { + mutationVariables: mockMutationVariables, + value: mockComment, + }, }); - await nextTick(); - expect(wrapper.emitted('submit-form')).toHaveLength(1); - expect(autosaveResetSpy).toHaveBeenCalled(); - }); + findTextarea().trigger('keydown.enter', keyData); - it('emits input event on changing textarea content', async () => { - findTextarea().setValue('test2'); + expect(mockMutationHandler).toHaveBeenCalledWith(createNoteMutationData); - await nextTick(); - expect(wrapper.emitted('input')).toEqual([['test2']]); + await waitForPromises(); + expect(wrapper.emitted('note-submit-complete')).toEqual([ + [mockNoteSubmitSuccessMutationResponse], + ]); }); - it('emits cancelForm event on Escape key if text was not changed', () => { - findTextarea().trigger('keyup.esc'); + it('shows error message when mutation fails', async () => { + const failedMutation = jest.fn().mockRejectedValue(mockNoteSubmitFailureMutationResponse); + createComponent({ + props: { + designNoteMutation: createNoteMutation, + value: mockComment, + }, + mutationHandler: failedMutation, + data: { + errorMessage: 'error', + }, + }); - expect(wrapper.emitted('cancel-form')).toHaveLength(1); + findSubmitButton().vm.$emit('click'); + + await waitForPromises(); + expect(findAlert().exists()).toBe(true); }); - it('opens confirmation modal on Escape key when text has changed', async () => { - wrapper.setProps({ value: 'test2' }); + it.each` + isDiscussion | isNewComment | errorMessage + ${true} | ${true} | ${ADD_IMAGE_DIFF_NOTE_ERROR} + ${true} | ${false} | ${UPDATE_IMAGE_DIFF_NOTE_ERROR} + ${false} | ${true} | ${ADD_DISCUSSION_COMMENT_ERROR} + ${false} | ${false} | ${UPDATE_NOTE_ERROR} + `( + 'return proper error message on error in case of isDiscussion is $isDiscussion and isNewComment is $isNewComment', + ({ isDiscussion, isNewComment, errorMessage }) => { + createComponent({ props: { isDiscussion, isNewComment } }); + + expect(wrapper.vm.getErrorMessage()).toBe(errorMessage); + }, + ); - await nextTick(); - findTextarea().trigger('keyup.esc'); - expect(confirmAction).toHaveBeenCalled(); - }); + it('emits cancelForm event on Escape key if text was not changed', () => { + createComponent(); - it('emits cancelForm event on Cancel button click if text was not changed', () => { - findCancelButton().trigger('click'); + findTextarea().trigger('keyup.esc'); expect(wrapper.emitted('cancel-form')).toHaveLength(1); }); - it('opens confirmation modal on Cancel button click when text has changed', async () => { - wrapper.setProps({ value: 'test2' }); + it('opens confirmation modal on Escape key when text has changed', () => { + createComponent(); + + findTextarea().setValue(mockComment); + + findTextarea().trigger('keyup.esc'); - await nextTick(); - findCancelButton().trigger('click'); expect(confirmAction).toHaveBeenCalled(); }); it('emits cancelForm event when confirmed', async () => { confirmAction.mockResolvedValueOnce(true); - const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset'); - wrapper.setProps({ value: 'test3' }); - await nextTick(); + createComponent({ props: { value: mockComment } }); + findTextarea().setValue('Comment changed'); findTextarea().trigger('keyup.esc'); - await nextTick(); expect(confirmAction).toHaveBeenCalled(); - await nextTick(); + await waitForPromises(); expect(wrapper.emitted('cancel-form')).toHaveLength(1); - expect(autosaveResetSpy).toHaveBeenCalled(); }); - it("doesn't emit cancelForm event when not confirmed", async () => { + it('does not emit cancelForm event when not confirmed', async () => { confirmAction.mockResolvedValueOnce(false); - const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset'); - wrapper.setProps({ value: 'test3' }); - await nextTick(); + createComponent({ props: { value: mockComment } }); + findTextarea().setValue('Comment changed'); findTextarea().trigger('keyup.esc'); - await nextTick(); expect(confirmAction).toHaveBeenCalled(); - await nextTick(); + await waitForPromises(); expect(wrapper.emitted('cancel-form')).toBeUndefined(); - expect(autosaveResetSpy).not.toHaveBeenCalled(); + }); + }); + + describe('when component is destroyed', () => { + it('calls autosave.reset', async () => { + const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset'); + createComponent(); + await wrapper.destroy(); + expect(autosaveResetSpy).toHaveBeenCalled(); }); }); }); diff --git a/spec/frontend/design_management/components/design_notes/toggle_replies_widget_spec.js b/spec/frontend/design_management/components/design_notes/toggle_replies_widget_spec.js index 41129e2b58d..eaa5a620fa6 100644 --- a/spec/frontend/design_management/components/design_notes/toggle_replies_widget_spec.js +++ b/spec/frontend/design_management/components/design_notes/toggle_replies_widget_spec.js @@ -23,10 +23,6 @@ describe('Toggle replies widget component', () => { }); } - afterEach(() => { - wrapper.destroy(); - }); - describe('when replies are collapsed', () => { beforeEach(() => { createComponent(); diff --git a/spec/frontend/design_management/components/design_overlay_spec.js b/spec/frontend/design_management/components/design_overlay_spec.js index 2807fe7727f..3eb47fdb97e 100644 --- a/spec/frontend/design_management/components/design_overlay_spec.js +++ b/spec/frontend/design_management/components/design_overlay_spec.js @@ -1,6 +1,6 @@ -import { shallowMount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import DesignOverlay from '~/design_management/components/design_overlay.vue'; @@ -16,22 +16,20 @@ describe('Design overlay component', () => { const mockDimensions = { width: 100, height: 100 }; - const findOverlay = () => wrapper.find('[data-testid="design-overlay"]'); - const findAllNotes = () => wrapper.findAll('[data-testid="note-pin"]'); - const findCommentBadge = () => wrapper.find('[data-testid="comment-badge"]'); + const findOverlay = () => wrapper.findByTestId('design-overlay'); + const findAllNotes = () => wrapper.findAllByTestId('note-pin'); + const findCommentBadge = () => wrapper.findByTestId('comment-badge'); const findBadgeAtIndex = (noteIndex) => findAllNotes().at(noteIndex); const findFirstBadge = () => findBadgeAtIndex(0); const findSecondBadge = () => findBadgeAtIndex(1); - const clickAndDragBadge = async (elem, fromPoint, toPoint) => { + const clickAndDragBadge = (elem, fromPoint, toPoint) => { elem.vm.$emit( 'mousedown', new MouseEvent('click', { clientX: fromPoint.x, clientY: fromPoint.y }), ); findOverlay().trigger('mousemove', { clientX: toPoint.x, clientY: toPoint.y }); - await nextTick(); elem.vm.$emit('mouseup', new MouseEvent('click', { clientX: toPoint.x, clientY: toPoint.y })); - await nextTick(); }; function createComponent(props = {}, data = {}) { @@ -47,7 +45,7 @@ describe('Design overlay component', () => { }, }); - wrapper = shallowMount(DesignOverlay, { + wrapper = shallowMountExtended(DesignOverlay, { apolloProvider, propsData: { dimensions: mockDimensions, @@ -80,7 +78,7 @@ describe('Design overlay component', () => { expect(wrapper.attributes().style).toBe('width: 100px; height: 100px; top: 0px; left: 0px;'); }); - it('should emit `openCommentForm` when clicking on overlay', async () => { + it('should emit `openCommentForm` when clicking on overlay', () => { createComponent(); const newCoordinates = { x: 10, @@ -90,7 +88,7 @@ describe('Design overlay component', () => { wrapper .find('[data-qa-selector="design_image_button"]') .trigger('mouseup', { offsetX: newCoordinates.x, offsetY: newCoordinates.y }); - await nextTick(); + expect(wrapper.emitted('openCommentForm')).toEqual([ [{ x: newCoordinates.x, y: newCoordinates.y }], ]); @@ -175,25 +173,15 @@ describe('Design overlay component', () => { }); }); - it('should recalculate badges positions on window resize', async () => { + it('should calculate badges positions based on dimensions', () => { createComponent({ notes, dimensions: { - width: 400, - height: 400, - }, - }); - - expect(findFirstBadge().props('position')).toEqual({ left: '40px', top: '60px' }); - - wrapper.setProps({ - dimensions: { width: 200, height: 200, }, }); - await nextTick(); expect(findFirstBadge().props('position')).toEqual({ left: '20px', top: '30px' }); }); @@ -216,7 +204,6 @@ describe('Design overlay component', () => { new MouseEvent('click', { clientX: position.x, clientY: position.y }), ); - await nextTick(); findFirstBadge().vm.$emit( 'mouseup', new MouseEvent('click', { clientX: position.x, clientY: position.y }), @@ -290,7 +277,7 @@ describe('Design overlay component', () => { }); describe('when moving the comment badge', () => { - it('should update badge style when note-moving action ends', async () => { + it('should update badge style when note-moving action ends', () => { const { position } = notes[0]; createComponent({ currentCommentForm: { @@ -298,19 +285,15 @@ describe('Design overlay component', () => { }, }); - const commentBadge = findCommentBadge(); + expect(findCommentBadge().props('position')).toEqual({ left: '10px', top: '15px' }); + const toPoint = { x: 20, y: 20 }; - await clickAndDragBadge(commentBadge, { x: position.x, y: position.y }, toPoint); - commentBadge.vm.$emit('mouseup', new MouseEvent('click')); - // simulates the currentCommentForm being updated in index.vue component, and - // propagated back down to this prop - wrapper.setProps({ + createComponent({ currentCommentForm: { height: position.height, width: position.width, ...toPoint }, }); - await nextTick(); - expect(commentBadge.props('position')).toEqual({ left: '20px', top: '20px' }); + expect(findCommentBadge().props('position')).toEqual({ left: '20px', top: '20px' }); }); it('should emit `openCommentForm` event when mouseleave fired on overlay element', async () => { @@ -330,8 +313,7 @@ describe('Design overlay component', () => { newCoordinates, ); - wrapper.trigger('mouseleave'); - await nextTick(); + findOverlay().vm.$emit('mouseleave'); expect(wrapper.emitted('openCommentForm')).toEqual([[newCoordinates]]); }); diff --git a/spec/frontend/design_management/components/design_presentation_spec.js b/spec/frontend/design_management/components/design_presentation_spec.js index 4a339899473..fdcea6d88c0 100644 --- a/spec/frontend/design_management/components/design_presentation_spec.js +++ b/spec/frontend/design_management/components/design_presentation_spec.js @@ -15,7 +15,6 @@ const mockOverlayData = { }; describe('Design management design presentation component', () => { - const originalGon = window.gon; let wrapper; function createComponent( @@ -114,11 +113,6 @@ describe('Design management design presentation component', () => { window.gon = { current_user_id: 1 }; }); - afterEach(() => { - wrapper.destroy(); - window.gon = originalGon; - }); - it('renders image and overlay when image provided', async () => { createComponent( { diff --git a/spec/frontend/design_management/components/design_scaler_spec.js b/spec/frontend/design_management/components/design_scaler_spec.js index e1a66cea329..b29448b4471 100644 --- a/spec/frontend/design_management/components/design_scaler_spec.js +++ b/spec/frontend/design_management/components/design_scaler_spec.js @@ -25,11 +25,6 @@ describe('Design management design scaler component', () => { createComponent(); }); - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('when `scale` value is greater than 1', () => { beforeEach(async () => { setScale(1.6); @@ -41,7 +36,7 @@ describe('Design management design scaler component', () => { expect(wrapper.emitted('scale')[1]).toEqual([1]); }); - it('emits @scale event when "decrement" button clicked', async () => { + it('emits @scale event when "decrement" button clicked', () => { getDecreaseScaleButton().vm.$emit('click'); expect(wrapper.emitted('scale')[1]).toEqual([1.4]); }); diff --git a/spec/frontend/design_management/components/design_sidebar_spec.js b/spec/frontend/design_management/components/design_sidebar_spec.js index af995f75ddc..90424175417 100644 --- a/spec/frontend/design_management/components/design_sidebar_spec.js +++ b/spec/frontend/design_management/components/design_sidebar_spec.js @@ -29,7 +29,6 @@ const $route = { const mutate = jest.fn().mockResolvedValue(); describe('Design management design sidebar component', () => { - const originalGon = window.gon; let wrapper; const findDiscussions = () => wrapper.findAllComponents(DesignDiscussion); @@ -67,11 +66,6 @@ describe('Design management design sidebar component', () => { window.gon = { current_user_id: 1 }; }); - afterEach(() => { - wrapper.destroy(); - window.gon = originalGon; - }); - it('renders participants', () => { createComponent(); @@ -143,8 +137,8 @@ describe('Design management design sidebar component', () => { expect(findResolvedCommentsToggle().props('visible')).toBe(true); }); - it('sends a mutation to set an active discussion when clicking on a discussion', () => { - findFirstDiscussion().trigger('click'); + it('emits correct event to send a mutation to set an active discussion when clicking on a discussion', () => { + findFirstDiscussion().vm.$emit('update-active-discussion'); expect(mutate).toHaveBeenCalledWith(updateActiveDiscussionMutationVariables); }); diff --git a/spec/frontend/design_management/components/design_todo_button_spec.js b/spec/frontend/design_management/components/design_todo_button_spec.js index ac26873b692..698535d8937 100644 --- a/spec/frontend/design_management/components/design_todo_button_spec.js +++ b/spec/frontend/design_management/components/design_todo_button_spec.js @@ -51,8 +51,6 @@ describe('Design management design todo button', () => { }); afterEach(() => { - wrapper.destroy(); - wrapper = null; jest.clearAllMocks(); }); @@ -83,7 +81,7 @@ describe('Design management design todo button', () => { await nextTick(); }); - it('calls `$apollo.mutate` with the `todoMarkDone` mutation and variables containing `id`', async () => { + it('calls `$apollo.mutate` with the `todoMarkDone` mutation and variables containing `id`', () => { const todoMarkDoneMutationVariables = { mutation: todoMarkDoneMutation, update: expect.anything(), @@ -129,7 +127,7 @@ describe('Design management design todo button', () => { await nextTick(); }); - it('calls `$apollo.mutate` with the `createDesignTodoMutation` mutation and variables containing `issuable_id`, `issue_id`, & `projectPath`', async () => { + it('calls `$apollo.mutate` with the `createDesignTodoMutation` mutation and variables containing `issuable_id`, `issue_id`, & `projectPath`', () => { const createDesignTodoMutationVariables = { mutation: createDesignTodoMutation, update: expect.anything(), diff --git a/spec/frontend/design_management/components/image_spec.js b/spec/frontend/design_management/components/image_spec.js index 95d2ad504de..53abcc559d8 100644 --- a/spec/frontend/design_management/components/image_spec.js +++ b/spec/frontend/design_management/components/image_spec.js @@ -20,10 +20,6 @@ describe('Design management large image component', () => { stubPerformanceWebAPI(); }); - afterEach(() => { - wrapper.destroy(); - }); - it('renders loading state', () => { createComponent({ isLoading: true, diff --git a/spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap b/spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap index 3517c0f7a44..9451f35ac5b 100644 --- a/spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap +++ b/spec/frontend/design_management/components/list/__snapshots__/item_spec.js.snap @@ -38,13 +38,13 @@ exports[`Design management list item component with notes renders item with mult </div> <div - class="card-footer gl-display-flex gl-w-full" + class="card-footer gl-display-flex gl-w-full gl-bg-white gl-py-3 gl-px-4" > <div class="gl-display-flex gl-flex-direction-column str-truncated-100" > <span - class="gl-font-weight-bold str-truncated-100" + class="gl-font-weight-semibold str-truncated-100" data-qa-selector="design_file_name" data-testid="design-img-filename-1" title="test" @@ -59,6 +59,7 @@ exports[`Design management list item component with notes renders item with mult Updated <timeago-stub cssclass="" + datetimeformat="DATE_WITH_TIME_FORMAT" time="01-01-2019" tooltipplacement="bottom" /> @@ -117,13 +118,13 @@ exports[`Design management list item component with notes renders item with sing </div> <div - class="card-footer gl-display-flex gl-w-full" + class="card-footer gl-display-flex gl-w-full gl-bg-white gl-py-3 gl-px-4" > <div class="gl-display-flex gl-flex-direction-column str-truncated-100" > <span - class="gl-font-weight-bold str-truncated-100" + class="gl-font-weight-semibold str-truncated-100" data-qa-selector="design_file_name" data-testid="design-img-filename-1" title="test" @@ -138,6 +139,7 @@ exports[`Design management list item component with notes renders item with sing Updated <timeago-stub cssclass="" + datetimeformat="DATE_WITH_TIME_FORMAT" time="01-01-2019" tooltipplacement="bottom" /> diff --git a/spec/frontend/design_management/components/list/item_spec.js b/spec/frontend/design_management/components/list/item_spec.js index e907e2e4ac5..4a0ad5a045b 100644 --- a/spec/frontend/design_management/components/list/item_spec.js +++ b/spec/frontend/design_management/components/list/item_spec.js @@ -54,10 +54,6 @@ describe('Design management list item component', () => { ); } - afterEach(() => { - wrapper.destroy(); - }); - describe('when item is not in view', () => { it('image is not rendered', () => { createComponent(); diff --git a/spec/frontend/design_management/components/toolbar/__snapshots__/design_navigation_spec.js.snap b/spec/frontend/design_management/components/toolbar/__snapshots__/design_navigation_spec.js.snap deleted file mode 100644 index b5a69b28a88..00000000000 --- a/spec/frontend/design_management/components/toolbar/__snapshots__/design_navigation_spec.js.snap +++ /dev/null @@ -1,39 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Design management pagination component hides components when designs are empty 1`] = `<!---->`; - -exports[`Design management pagination component renders navigation buttons 1`] = ` -<div - class="gl-display-flex gl-align-items-center" -> - - 0 of 2 - - <gl-button-group-stub - class="gl-mx-5" - > - <gl-button-stub - aria-label="Go to previous design" - buttontextclasses="" - category="primary" - class="js-previous-design" - disabled="true" - icon="chevron-lg-left" - size="medium" - title="Go to previous design" - variant="default" - /> - - <gl-button-stub - aria-label="Go to next design" - buttontextclasses="" - category="primary" - class="js-next-design" - icon="chevron-lg-right" - size="medium" - title="Go to next design" - variant="default" - /> - </gl-button-group-stub> -</div> -`; diff --git a/spec/frontend/design_management/components/toolbar/design_navigation_spec.js b/spec/frontend/design_management/components/toolbar/design_navigation_spec.js index 38a7fadee79..cee05bafeb6 100644 --- a/spec/frontend/design_management/components/toolbar/design_navigation_spec.js +++ b/spec/frontend/design_management/components/toolbar/design_navigation_spec.js @@ -1,9 +1,17 @@ -/* global Mousetrap */ -import 'mousetrap'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { GlButtonGroup } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; import DesignNavigation from '~/design_management/components/toolbar/design_navigation.vue'; import { DESIGN_ROUTE_NAME } from '~/design_management/router/constants'; +import { Mousetrap } from '~/lib/mousetrap'; +import getDesignListQuery from 'shared_queries/design_management/get_design_list.query.graphql'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { + getDesignListQueryResponse, + designListQueryResponseNodes, +} from '../../mock_data/apollo_mock'; const push = jest.fn(); const $router = { @@ -18,11 +26,23 @@ const $route = { describe('Design management pagination component', () => { let wrapper; - function createComponent() { + const buildMockHandler = (nodes = designListQueryResponseNodes) => { + return jest.fn().mockResolvedValue(getDesignListQueryResponse({ designs: nodes })); + }; + + const createMockApolloProvider = (handler) => { + Vue.use(VueApollo); + + return createMockApollo([[getDesignListQuery, handler]]); + }; + + function createComponent({ propsData = {}, handler = buildMockHandler() } = {}) { wrapper = shallowMount(DesignNavigation, { propsData: { id: '2', + ...propsData, }, + apolloProvider: createMockApolloProvider(handler), mocks: { $router, $route, @@ -30,52 +50,43 @@ describe('Design management pagination component', () => { }); } - beforeEach(() => { - createComponent(); - }); + const findGlButtonGroup = () => wrapper.findComponent(GlButtonGroup); - afterEach(() => { - wrapper.destroy(); - }); + it('hides components when designs are empty', async () => { + createComponent({ handler: buildMockHandler([]) }); + await waitForPromises(); - it('hides components when designs are empty', () => { - expect(wrapper.element).toMatchSnapshot(); + expect(findGlButtonGroup().exists()).toBe(false); }); it('renders navigation buttons', async () => { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - designCollection: { designs: [{ id: '1' }, { id: '2' }] }, - }); + createComponent({ handler: buildMockHandler() }); + await waitForPromises(); - await nextTick(); - expect(wrapper.element).toMatchSnapshot(); + expect(findGlButtonGroup().exists()).toBe(true); }); describe('keyboard buttons navigation', () => { - beforeEach(() => { - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - designCollection: { designs: [{ filename: '1' }, { filename: '2' }, { filename: '3' }] }, - }); - }); + it('routes to previous design on Left button', async () => { + createComponent({ propsData: { id: designListQueryResponseNodes[1].filename } }); + await waitForPromises(); - it('routes to previous design on Left button', () => { Mousetrap.trigger('left'); expect(push).toHaveBeenCalledWith({ name: DESIGN_ROUTE_NAME, - params: { id: '1' }, + params: { id: designListQueryResponseNodes[0].filename }, query: {}, }); }); - it('routes to next design on Right button', () => { + it('routes to next design on Right button', async () => { + createComponent({ propsData: { id: designListQueryResponseNodes[1].filename } }); + await waitForPromises(); + Mousetrap.trigger('right'); expect(push).toHaveBeenCalledWith({ name: DESIGN_ROUTE_NAME, - params: { id: '3' }, + params: { id: designListQueryResponseNodes[2].filename }, query: {}, }); }); diff --git a/spec/frontend/design_management/components/toolbar/index_spec.js b/spec/frontend/design_management/components/toolbar/index_spec.js index 1776405ece9..764ad73805f 100644 --- a/spec/frontend/design_management/components/toolbar/index_spec.js +++ b/spec/frontend/design_management/components/toolbar/index_spec.js @@ -1,12 +1,18 @@ import { GlButton } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import Vue, { nextTick } from 'vue'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; import VueRouter from 'vue-router'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import permissionsQuery from 'shared_queries/design_management/design_permissions.query.graphql'; import DeleteButton from '~/design_management/components/delete_button.vue'; import Toolbar from '~/design_management/components/toolbar/index.vue'; import { DESIGNS_ROUTE_NAME } from '~/design_management/router/constants'; +import { getPermissionsQueryResponse } from '../../mock_data/apollo_mock'; Vue.use(VueRouter); +Vue.use(VueApollo); const router = new VueRouter(); const RouterLinkStub = { @@ -27,7 +33,12 @@ describe('Design management toolbar component', () => { const updatedAt = new Date(); updatedAt.setHours(updatedAt.getHours() - 1); + const mockApollo = createMockApollo([ + [permissionsQuery, jest.fn().mockResolvedValue(getPermissionsQueryResponse(createDesign))], + ]); + wrapper = shallowMount(Toolbar, { + apolloProvider: mockApollo, router, propsData: { id: '1', @@ -46,31 +57,20 @@ describe('Design management toolbar component', () => { 'router-link': RouterLinkStub, }, }); - - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - permissions: { - createDesign, - }, - }); } - afterEach(() => { - wrapper.destroy(); - }); - it('renders design and updated data', async () => { createComponent(); - await nextTick(); + await waitForPromises(); + expect(wrapper.element).toMatchSnapshot(); }); it('links back to designs list', async () => { createComponent(); - await nextTick(); + await waitForPromises(); const link = wrapper.find('a'); expect(link.props('to')).toEqual({ @@ -84,35 +84,41 @@ describe('Design management toolbar component', () => { it('renders delete button on latest designs version with logged in user', async () => { createComponent(); - await nextTick(); + await waitForPromises(); + expect(wrapper.findComponent(DeleteButton).exists()).toBe(true); }); it('does not render delete button on non-latest version', async () => { createComponent(false, true, { isLatestVersion: false }); - await nextTick(); + await waitForPromises(); + expect(wrapper.findComponent(DeleteButton).exists()).toBe(false); }); it('does not render delete button when user is not logged in', async () => { createComponent(false, false); - await nextTick(); + await waitForPromises(); + expect(wrapper.findComponent(DeleteButton).exists()).toBe(false); }); it('emits `delete` event on deleteButton `delete-selected-designs` event', async () => { createComponent(); - await nextTick(); + await waitForPromises(); + wrapper.findComponent(DeleteButton).vm.$emit('delete-selected-designs'); expect(wrapper.emitted().delete).toHaveLength(1); }); - it('renders download button with correct link', () => { + it('renders download button with correct link', async () => { createComponent(); + await waitForPromises(); + expect(wrapper.findComponent(GlButton).attributes('href')).toBe( '/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d', ); diff --git a/spec/frontend/design_management/components/upload/button_spec.js b/spec/frontend/design_management/components/upload/button_spec.js index 59821218ab8..ceae7920e0d 100644 --- a/spec/frontend/design_management/components/upload/button_spec.js +++ b/spec/frontend/design_management/components/upload/button_spec.js @@ -14,10 +14,6 @@ describe('Design management upload button component', () => { }); } - afterEach(() => { - wrapper.destroy(); - }); - it('renders upload design button', () => { createComponent(); diff --git a/spec/frontend/design_management/components/upload/design_version_dropdown_spec.js b/spec/frontend/design_management/components/upload/design_version_dropdown_spec.js index 6ad10e707ab..3ee68f80538 100644 --- a/spec/frontend/design_management/components/upload/design_version_dropdown_spec.js +++ b/spec/frontend/design_management/components/upload/design_version_dropdown_spec.js @@ -1,9 +1,14 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; import { GlAvatar, GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; +import getDesignListQuery from 'shared_queries/design_management/get_design_list.query.graphql'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import DesignVersionDropdown from '~/design_management/components/upload/design_version_dropdown.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; -import mockAllVersions from './mock_data/all_versions'; +import mockAllVersions from '../../mock_data/all_versions'; +import { getDesignListQueryResponse } from '../../mock_data/apollo_mock'; const LATEST_VERSION_ID = 1; const PREVIOUS_VERSION_ID = 2; @@ -20,11 +25,20 @@ const MOCK_ROUTE = { query: {}, }; +Vue.use(VueApollo); + describe('Design management design version dropdown component', () => { let wrapper; function createComponent({ maxVersions = -1, $route = MOCK_ROUTE } = {}) { + const designVersions = + maxVersions > -1 ? mockAllVersions.slice(0, maxVersions) : mockAllVersions; + const designListHandler = jest + .fn() + .mockResolvedValue(getDesignListQueryResponse({ versions: designVersions })); + wrapper = shallowMount(DesignVersionDropdown, { + apolloProvider: createMockApollo([[getDesignListQuery, designListHandler]]), propsData: { projectPath: '', issueIid: '', @@ -34,18 +48,8 @@ describe('Design management design version dropdown component', () => { }, stubs: { GlAvatar: true, GlCollapsibleListbox }, }); - - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - allVersions: maxVersions > -1 ? mockAllVersions.slice(0, maxVersions) : mockAllVersions, - }); } - afterEach(() => { - wrapper.destroy(); - }); - const findListbox = () => wrapper.findComponent(GlCollapsibleListbox); const findAllListboxItems = () => wrapper.findAllComponents(GlListboxItem); const findVersionLink = (index) => wrapper.findAllComponents(GlListboxItem).at(index); @@ -56,7 +60,7 @@ describe('Design management design version dropdown component', () => { beforeEach(async () => { createComponent(); - await nextTick(); + await waitForPromises(); listItem = findAllListboxItems().at(0); }); @@ -78,7 +82,8 @@ describe('Design management design version dropdown component', () => { it('has "latest" on most recent version item', async () => { createComponent(); - await nextTick(); + await waitForPromises(); + expect(findVersionLink(0).text()).toContain('latest'); }); }); @@ -87,7 +92,7 @@ describe('Design management design version dropdown component', () => { it('displays latest version text by default', async () => { createComponent(); - await nextTick(); + await waitForPromises(); expect(findListbox().props('toggleText')).toBe('Showing latest version'); }); @@ -95,35 +100,39 @@ describe('Design management design version dropdown component', () => { it('displays latest version text when only 1 version is present', async () => { createComponent({ maxVersions: 1 }); - await nextTick(); + await waitForPromises(); + expect(findListbox().props('toggleText')).toBe('Showing latest version'); }); it('displays version text when the current version is not the latest', async () => { createComponent({ $route: designRouteFactory(PREVIOUS_VERSION_ID) }); - await nextTick(); + await waitForPromises(); + expect(findListbox().props('toggleText')).toBe(`Showing version #1`); }); it('displays latest version text when the current version is the latest', async () => { createComponent({ $route: designRouteFactory(LATEST_VERSION_ID) }); - await nextTick(); + await waitForPromises(); + expect(findListbox().props('toggleText')).toBe('Showing latest version'); }); it('should have the same length as apollo query', async () => { createComponent(); - await nextTick(); + await waitForPromises(); + expect(findAllListboxItems()).toHaveLength(wrapper.vm.allVersions.length); }); it('should render TimeAgo', async () => { createComponent(); - await nextTick(); + await waitForPromises(); expect(wrapper.findAllComponents(TimeAgo)).toHaveLength(wrapper.vm.allVersions.length); }); diff --git a/spec/frontend/design_management/components/upload/mock_data/all_versions.js b/spec/frontend/design_management/components/upload/mock_data/all_versions.js deleted file mode 100644 index 24c59ce1a75..00000000000 --- a/spec/frontend/design_management/components/upload/mock_data/all_versions.js +++ /dev/null @@ -1,20 +0,0 @@ -export default [ - { - id: 'gid://gitlab/DesignManagement::Version/1', - sha: 'b389071a06c153509e11da1f582005b316667001', - createdAt: '2021-08-09T06:05:00Z', - author: { - id: 'gid://gitlab/User/1', - name: 'Adminstrator', - }, - }, - { - id: 'gid://gitlab/DesignManagement::Version/2', - sha: 'b389071a06c153509e11da1f582005b316667021', - createdAt: '2021-08-09T06:05:00Z', - author: { - id: 'gid://gitlab/User/1', - name: 'Adminstrator', - }, - }, -]; |