diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-20 02:18:09 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-20 02:18:09 +0300 |
commit | 6ed4ec3e0b1340f96b7c043ef51d1b33bbe85fde (patch) | |
tree | dc4d20fe6064752c0bd323187252c77e0a89144b /spec/frontend/notes | |
parent | 9868dae7fc0655bd7ce4a6887d4e6d487690eeed (diff) |
Add latest changes from gitlab-org/gitlab@15-4-stable-eev15.4.0-rc42
Diffstat (limited to 'spec/frontend/notes')
17 files changed, 294 insertions, 186 deletions
diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js index 463787c148b..55e4ef42e37 100644 --- a/spec/frontend/notes/components/comment_form_spec.js +++ b/spec/frontend/notes/components/comment_form_spec.js @@ -586,10 +586,10 @@ describe('issue_comment_form component', () => { ${true} ${false} `('when checkbox value is `$shouldCheckboxBeChecked`', ({ shouldCheckboxBeChecked }) => { - it(`sets \`confidential\` to \`${shouldCheckboxBeChecked}\``, async () => { + it(`sets \`internal\` to \`${shouldCheckboxBeChecked}\``, async () => { mountComponent({ mountFunction: mount, - initialData: { note: 'confidential note' }, + initialData: { note: 'internal note' }, noteableData: { ...notableDataMockCanUpdateIssuable }, }); @@ -606,7 +606,7 @@ describe('issue_comment_form component', () => { findCommentButton().trigger('click'); const [providedData] = wrapper.vm.saveNote.mock.calls[0]; - expect(providedData.data.note.confidential).toBe(shouldCheckboxBeChecked); + expect(providedData.data.note.internal).toBe(shouldCheckboxBeChecked); }); }); @@ -679,7 +679,7 @@ describe('issue_comment_form component', () => { ); }); - it('clicking `add comment now`, should call note endpoint, set `isDraft` false ', () => { + it('clicking `add comment now`, should call note endpoint, set `isDraft` false', () => { mountComponent({ mountFunction: mount, initialData: { note: 'a comment' } }); jest.spyOn(store, 'dispatch').mockResolvedValue(); diff --git a/spec/frontend/notes/components/discussion_counter_spec.js b/spec/frontend/notes/components/discussion_counter_spec.js index a7e2f1efa09..f4ec7f835bb 100644 --- a/spec/frontend/notes/components/discussion_counter_spec.js +++ b/spec/frontend/notes/components/discussion_counter_spec.js @@ -1,5 +1,5 @@ -import { GlButton } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; +import { GlDropdownItem } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import DiscussionCounter from '~/notes/components/discussion_counter.vue'; @@ -45,7 +45,7 @@ describe('DiscussionCounter component', () => { describe('has no discussions', () => { it('does not render', () => { - wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); + wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); expect(wrapper.findComponent({ ref: 'discussionCounter' }).exists()).toBe(false); }); @@ -55,7 +55,7 @@ describe('DiscussionCounter component', () => { it('does not render', () => { store.commit(types.ADD_OR_UPDATE_DISCUSSIONS, [{ ...discussionMock, resolvable: false }]); store.dispatch('updateResolvableDiscussionsCounts'); - wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); + wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); expect(wrapper.findComponent({ ref: 'discussionCounter' }).exists()).toBe(false); }); @@ -75,7 +75,7 @@ describe('DiscussionCounter component', () => { it('renders', () => { updateStore(); - wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); + wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); expect(wrapper.findComponent({ ref: 'discussionCounter' }).exists()).toBe(true); }); @@ -89,7 +89,7 @@ describe('DiscussionCounter component', () => { ({ blocksMerge, color }) => { updateStore(); store.state.unresolvedDiscussionsCount = 1; - wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge } }); + wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge } }); expect(wrapper.find('[data-testid="discussions-counter-text"]').classes()).toContain(color); }, @@ -97,60 +97,58 @@ describe('DiscussionCounter component', () => { it.each` title | resolved | groupLength - ${'not allResolved'} | ${false} | ${4} + ${'not allResolved'} | ${false} | ${2} ${'allResolved'} | ${true} | ${1} - `('renders correctly if $title', ({ resolved, groupLength }) => { + `('renders correctly if $title', async ({ resolved, groupLength }) => { updateStore({ resolvable: true, resolved }); - wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); + wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); + await wrapper.find('.dropdown-toggle').trigger('click'); - expect(wrapper.findAllComponents(GlButton)).toHaveLength(groupLength); + expect(wrapper.findAllComponents(GlDropdownItem)).toHaveLength(groupLength); }); }); describe('toggle all threads button', () => { let toggleAllButton; - const updateStoreWithExpanded = (expanded) => { + const updateStoreWithExpanded = async (expanded) => { const discussion = { ...discussionMock, expanded }; store.commit(types.ADD_OR_UPDATE_DISCUSSIONS, [discussion]); store.dispatch('updateResolvableDiscussionsCounts'); - wrapper = shallowMount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); - toggleAllButton = wrapper.find('.toggle-all-discussions-btn'); + wrapper = mount(DiscussionCounter, { store, propsData: { blocksMerge: true } }); + await wrapper.find('.dropdown-toggle').trigger('click'); + toggleAllButton = wrapper.find('[data-testid="toggle-all-discussions-btn"]'); }; afterEach(() => wrapper.destroy()); - it('calls button handler when clicked', () => { - updateStoreWithExpanded(true); + it('calls button handler when clicked', async () => { + await updateStoreWithExpanded(true); - toggleAllButton.vm.$emit('click'); + toggleAllButton.trigger('click'); expect(setExpandDiscussionsFn).toHaveBeenCalledTimes(1); }); it('collapses all discussions if expanded', async () => { - updateStoreWithExpanded(true); + await updateStoreWithExpanded(true); expect(wrapper.vm.allExpanded).toBe(true); - expect(toggleAllButton.props('icon')).toBe('collapse'); - toggleAllButton.vm.$emit('click'); + toggleAllButton.trigger('click'); await nextTick(); expect(wrapper.vm.allExpanded).toBe(false); - expect(toggleAllButton.props('icon')).toBe('expand'); }); it('expands all discussions if collapsed', async () => { - updateStoreWithExpanded(false); + await updateStoreWithExpanded(false); expect(wrapper.vm.allExpanded).toBe(false); - expect(toggleAllButton.props('icon')).toBe('expand'); - toggleAllButton.vm.$emit('click'); + toggleAllButton.trigger('click'); await nextTick(); expect(wrapper.vm.allExpanded).toBe(true); - expect(toggleAllButton.props('icon')).toBe('collapse'); }); }); }); diff --git a/spec/frontend/notes/components/discussion_filter_spec.js b/spec/frontend/notes/components/discussion_filter_spec.js index 27206bddbfc..ed9fc47540d 100644 --- a/spec/frontend/notes/components/discussion_filter_spec.js +++ b/spec/frontend/notes/components/discussion_filter_spec.js @@ -8,7 +8,14 @@ import createEventHub from '~/helpers/event_hub_factory'; import axios from '~/lib/utils/axios_utils'; import DiscussionFilter from '~/notes/components/discussion_filter.vue'; -import { DISCUSSION_FILTERS_DEFAULT_VALUE, DISCUSSION_FILTER_TYPES } from '~/notes/constants'; +import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; +import Tracking from '~/tracking'; +import { + DISCUSSION_FILTERS_DEFAULT_VALUE, + DISCUSSION_FILTER_TYPES, + ASC, + DESC, +} from '~/notes/constants'; import notesModule from '~/notes/stores/modules'; import { discussionFiltersMock, discussionMock } from '../mock_data'; @@ -28,6 +35,8 @@ describe('DiscussionFilter component', () => { const findFilter = (filterType) => wrapper.find(`.dropdown-item[data-filter-type="${filterType}"]`); + const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync); + const mountComponent = () => { const discussions = [ { @@ -68,6 +77,7 @@ describe('DiscussionFilter component', () => { mock.onGet(DISCUSSION_PATH).reply(200, ''); window.mrTabs = undefined; wrapper = mountComponent(); + jest.spyOn(Tracking, 'event'); }); afterEach(() => { @@ -75,6 +85,65 @@ describe('DiscussionFilter component', () => { mock.restore(); }); + describe('default', () => { + beforeEach(() => { + jest.spyOn(store, 'dispatch').mockImplementation(); + }); + + it('has local storage sync with the correct props', () => { + expect(findLocalStorageSync().props('asString')).toBe(true); + }); + + it('calls setDiscussionSortDirection when update is emitted', () => { + findLocalStorageSync().vm.$emit('input', ASC); + + expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', { direction: ASC }); + }); + }); + + describe('when asc', () => { + beforeEach(() => { + jest.spyOn(store, 'dispatch').mockImplementation(); + }); + + describe('when the dropdown is clicked', () => { + it('calls the right actions', () => { + wrapper.find('.js-newest-first').vm.$emit('click'); + + expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', { + direction: DESC, + }); + expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', { + property: DESC, + }); + }); + }); + }); + + describe('when desc', () => { + beforeEach(() => { + store.state.discussionSortOrder = DESC; + jest.spyOn(store, 'dispatch').mockImplementation(); + }); + + describe('when the dropdown item is clicked', () => { + it('calls the right actions', () => { + wrapper.find('.js-oldest-first').vm.$emit('click'); + + expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', { + direction: ASC, + }); + expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', { + property: ASC, + }); + }); + + it('sets is-checked to true on the active button in the dropdown', () => { + expect(wrapper.find('.js-newest-first').props('isChecked')).toBe(true); + }); + }); + }); + it('renders the all filters', () => { expect(wrapper.findAll('.discussion-filter-container .dropdown-item').length).toBe( discussionFiltersMock.length, @@ -82,7 +151,7 @@ describe('DiscussionFilter component', () => { }); it('renders the default selected item', () => { - expect(wrapper.find('#discussion-filter-dropdown .dropdown-item').text().trim()).toBe( + expect(wrapper.find('.discussion-filter-container .dropdown-item').text().trim()).toBe( discussionFiltersMock[0].title, ); }); @@ -127,14 +196,6 @@ describe('DiscussionFilter component', () => { expect(wrapper.vm.$store.state.commentsDisabled).toBe(false); }); - it('renders a dropdown divider for the default filter', () => { - const defaultFilter = wrapper.findAll( - `.discussion-filter-container .dropdown-item-wrapper > *`, - ); - - expect(defaultFilter.at(1).classes('gl-new-dropdown-divider')).toBe(true); - }); - describe('Merge request tabs', () => { eventHub = createEventHub(); diff --git a/spec/frontend/notes/components/discussion_notes_spec.js b/spec/frontend/notes/components/discussion_notes_spec.js index 1b8b6bec490..a74d709ed3a 100644 --- a/spec/frontend/notes/components/discussion_notes_spec.js +++ b/spec/frontend/notes/components/discussion_notes_spec.js @@ -140,21 +140,21 @@ describe('DiscussionNotes', () => { findNoteAtIndex(0).vm.$emit('handleDeleteNote'); await nextTick(); - expect(wrapper.emitted().deleteNote).toBeTruthy(); + expect(wrapper.emitted().deleteNote).toHaveLength(1); }); it('emits startReplying when first note emits startReplying', async () => { findNoteAtIndex(0).vm.$emit('startReplying'); await nextTick(); - expect(wrapper.emitted().startReplying).toBeTruthy(); + expect(wrapper.emitted().startReplying).toHaveLength(1); }); it('emits deleteNote when second note emits handleDeleteNote', async () => { findNoteAtIndex(1).vm.$emit('handleDeleteNote'); await nextTick(); - expect(wrapper.emitted().deleteNote).toBeTruthy(); + expect(wrapper.emitted().deleteNote).toHaveLength(1); }); }); @@ -169,7 +169,7 @@ describe('DiscussionNotes', () => { note.vm.$emit('handleDeleteNote'); await nextTick(); - expect(wrapper.emitted().deleteNote).toBeTruthy(); + expect(wrapper.emitted().deleteNote).toHaveLength(1); }); }); }); diff --git a/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js b/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js index 71406eeb7b4..a185f11ffaa 100644 --- a/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js +++ b/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js @@ -19,7 +19,7 @@ describe('ResolveWithIssueButton', () => { wrapper.destroy(); }); - it('it should have a link with the provided link property as href', () => { + it('should have a link with the provided link property as href', () => { const button = wrapper.findComponent(GlButton); expect(button.attributes().href).toBe(url); diff --git a/spec/frontend/notes/components/multiline_comment_form_spec.js b/spec/frontend/notes/components/multiline_comment_form_spec.js index b027a261c15..8446bba340f 100644 --- a/spec/frontend/notes/components/multiline_comment_form_spec.js +++ b/spec/frontend/notes/components/multiline_comment_form_spec.js @@ -70,7 +70,7 @@ describe('MultilineCommentForm', () => { glSelect.vm.$emit('change', { ...testLine }); expect(wrapper.vm.commentLineStart).toEqual(line); - expect(wrapper.emitted('input')).toBeTruthy(); + expect(wrapper.emitted('input')).toHaveLength(1); // Once during created, once during updateCommentLineStart expect(setSelectedCommentPosition).toHaveBeenCalledTimes(2); }); diff --git a/spec/frontend/notes/components/note_actions/timeline_event_button_spec.js b/spec/frontend/notes/components/note_actions/timeline_event_button_spec.js new file mode 100644 index 00000000000..658e844a9b1 --- /dev/null +++ b/spec/frontend/notes/components/note_actions/timeline_event_button_spec.js @@ -0,0 +1,35 @@ +import { GlButton } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import TimelineEventButton from '~/notes/components/note_actions/timeline_event_button.vue'; + +const emitData = { + noteId: '1', + addError: 'Error promoting the note to timeline event: %{error}', + addGenericError: 'Something went wrong while promoting the note to timeline event.', +}; + +describe('NoteTimelineEventButton', () => { + let wrapper; + + beforeEach(() => { + wrapper = shallowMount(TimelineEventButton, { + propsData: { + noteId: '1', + isPromotionInProgress: true, + }, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + const findTimelineButton = () => wrapper.findComponent(GlButton); + + it('emits click-promote-comment-to-event', async () => { + findTimelineButton().vm.$emit('click'); + + expect(wrapper.emitted('click-promote-comment-to-event')).toEqual([[emitData]]); + expect(findTimelineButton().props('disabled')).toEqual(true); + }); +}); diff --git a/spec/frontend/notes/components/note_body_spec.js b/spec/frontend/notes/components/note_body_spec.js index c2e56d3e7a7..3b5313744ff 100644 --- a/spec/frontend/notes/components/note_body_spec.js +++ b/spec/frontend/notes/components/note_body_spec.js @@ -74,11 +74,11 @@ describe('issue_note_body component', () => { }); it.each` - confidential | buttonText - ${false} | ${'Save comment'} - ${true} | ${'Save internal note'} - `('renders save button with text "$buttonText"', ({ confidential, buttonText }) => { - wrapper = createComponent({ props: { note: { ...note, confidential }, isEditing: true } }); + internal | buttonText + ${false} | ${'Save comment'} + ${true} | ${'Save internal note'} + `('renders save button with text "$buttonText"', ({ internal, buttonText }) => { + wrapper = createComponent({ props: { note: { ...note, internal }, isEditing: true } }); expect(wrapper.findComponent(NoteForm).props('saveButtonTitle')).toBe(buttonText); }); diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js index fad04e9063d..90473e7ccba 100644 --- a/spec/frontend/notes/components/note_form_spec.js +++ b/spec/frontend/notes/components/note_form_spec.js @@ -116,15 +116,15 @@ describe('issue_note_form component', () => { }); it.each` - confidential | placeholder - ${false} | ${'Write a comment or drag your files here…'} - ${true} | ${'Write an internal note or drag your files here…'} + internal | placeholder + ${false} | ${'Write a comment or drag your files here…'} + ${true} | ${'Write an internal note or drag your files here…'} `( - 'should set correct textarea placeholder text when discussion confidentiality is $confidential', - ({ confidential, placeholder }) => { + 'should set correct textarea placeholder text when discussion confidentiality is $internal', + ({ internal, placeholder }) => { props.note = { ...note, - confidential, + internal, }; wrapper = createComponentWrapper(); diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js index 43fbc5e26dc..76177229cff 100644 --- a/spec/frontend/notes/components/note_header_spec.js +++ b/spec/frontend/notes/components/note_header_spec.js @@ -3,7 +3,7 @@ import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import NoteHeader from '~/notes/components/note_header.vue'; -import { AVAILABILITY_STATUS } from '~/set_status_modal/utils'; +import { AVAILABILITY_STATUS } from '~/set_status_modal/constants'; import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue'; Vue.use(Vuex); @@ -40,13 +40,19 @@ describe('NoteHeader component', () => { availability: '', }; - const createComponent = (props) => { + const createComponent = (props, userAttributes = false) => { wrapper = shallowMountExtended(NoteHeader, { store: new Vuex.Store({ actions, }), propsData: { ...props }, stubs: { GlSprintf, UserNameWithStatus }, + provide: { + glFeatures: { + removeUserAttributesProjects: userAttributes, + removeUserAttributesGroups: userAttributes, + }, + }, }); }; @@ -55,6 +61,26 @@ describe('NoteHeader component', () => { wrapper = null; }); + describe('when removeUserAttributesProjects feature flag is enabled', () => { + it('does not render busy status', () => { + createComponent({ author: { ...author, availability: AVAILABILITY_STATUS.BUSY } }, true); + + expect(wrapper.find('.note-header-info').text()).not.toContain('(Busy)'); + }); + + it('does not render author status', () => { + createComponent({ author }, true); + + expect(findAuthorStatus().exists()).toBe(false); + }); + + it('does not render username', () => { + createComponent({ author }, true); + + expect(wrapper.find('.note-header-info').text()).not.toContain('@'); + }); + }); + it('does not render discussion actions when includeToggle is false', () => { createComponent({ includeToggle: false, diff --git a/spec/frontend/notes/components/noteable_discussion_spec.js b/spec/frontend/notes/components/noteable_discussion_spec.js index b34305688d9..2175849aeb9 100644 --- a/spec/frontend/notes/components/noteable_discussion_spec.js +++ b/spec/frontend/notes/components/noteable_discussion_spec.js @@ -97,7 +97,7 @@ describe('noteable_discussion component', () => { `( 'reply button on form should have title "$saveButtonTitle" when note is $noteType', async ({ isNoteInternal, saveButtonTitle }) => { - wrapper.setProps({ discussion: { ...discussionMock, confidential: isNoteInternal } }); + wrapper.setProps({ discussion: { ...discussionMock, internal: isNoteInternal } }); await nextTick(); const replyPlaceholder = wrapper.findComponent(ReplyPlaceholder); diff --git a/spec/frontend/notes/components/noteable_note_spec.js b/spec/frontend/notes/components/noteable_note_spec.js index e049c5bc0c8..b044d40cbe4 100644 --- a/spec/frontend/notes/components/noteable_note_spec.js +++ b/spec/frontend/notes/components/noteable_note_spec.js @@ -292,7 +292,7 @@ describe('issue_note', () => { describe('internal note', () => { it('has internal note class for internal notes', () => { - createWrapper({ note: { ...note, confidential: true } }); + createWrapper({ note: { ...note, internal: true } }); expect(wrapper.classes()).toContain('internal-note'); }); diff --git a/spec/frontend/notes/components/sort_discussion_spec.js b/spec/frontend/notes/components/sort_discussion_spec.js deleted file mode 100644 index 8b6e05da3c0..00000000000 --- a/spec/frontend/notes/components/sort_discussion_spec.js +++ /dev/null @@ -1,102 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; -import Vuex from 'vuex'; -import SortDiscussion from '~/notes/components/sort_discussion.vue'; -import { ASC, DESC } from '~/notes/constants'; -import createStore from '~/notes/stores'; -import Tracking from '~/tracking'; -import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; - -Vue.use(Vuex); - -describe('Sort Discussion component', () => { - let wrapper; - let store; - - const createComponent = () => { - jest.spyOn(store, 'dispatch').mockImplementation(); - - wrapper = shallowMount(SortDiscussion, { - store, - }); - }; - - const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync); - - beforeEach(() => { - store = createStore(); - jest.spyOn(Tracking, 'event'); - }); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - describe('default', () => { - beforeEach(() => { - createComponent(); - }); - - it('has local storage sync with the correct props', () => { - expect(findLocalStorageSync().props('asString')).toBe(true); - }); - - it('calls setDiscussionSortDirection when update is emitted', () => { - findLocalStorageSync().vm.$emit('input', ASC); - - expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', { direction: ASC }); - }); - }); - - describe('when asc', () => { - describe('when the dropdown is clicked', () => { - it('calls the right actions', () => { - createComponent(); - - wrapper.find('.js-newest-first').vm.$emit('click'); - - expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', { - direction: DESC, - }); - expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', { - property: DESC, - }); - }); - }); - - it('shows the "Oldest First" as the dropdown', () => { - createComponent(); - - expect(wrapper.find('.js-dropdown-text').props('text')).toBe('Oldest first'); - }); - }); - - describe('when desc', () => { - beforeEach(() => { - store.state.discussionSortOrder = DESC; - createComponent(); - }); - - describe('when the dropdown item is clicked', () => { - it('calls the right actions', () => { - wrapper.find('.js-oldest-first').vm.$emit('click'); - - expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', { - direction: ASC, - }); - expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', { - property: ASC, - }); - }); - - it('sets is-checked to true on the active button in the dropdown', () => { - expect(wrapper.find('.js-newest-first').props('isChecked')).toBe(true); - }); - }); - - it('shows the "Newest First" as the dropdown', () => { - expect(wrapper.find('.js-dropdown-text').props('text')).toBe('Newest first'); - }); - }); -}); diff --git a/spec/frontend/notes/mixins/discussion_navigation_spec.js b/spec/frontend/notes/mixins/discussion_navigation_spec.js index 35b3dec6298..1b4e8026d84 100644 --- a/spec/frontend/notes/mixins/discussion_navigation_spec.js +++ b/spec/frontend/notes/mixins/discussion_navigation_spec.js @@ -110,16 +110,13 @@ describe('Discussion navigation mixin', () => { }); describe.each` - fn | args | currentId | expected - ${'jumpToNextDiscussion'} | ${[]} | ${null} | ${'a'} - ${'jumpToNextDiscussion'} | ${[]} | ${'a'} | ${'c'} - ${'jumpToNextDiscussion'} | ${[]} | ${'e'} | ${'a'} - ${'jumpToPreviousDiscussion'} | ${[]} | ${null} | ${'e'} - ${'jumpToPreviousDiscussion'} | ${[]} | ${'e'} | ${'c'} - ${'jumpToPreviousDiscussion'} | ${[]} | ${'c'} | ${'a'} - ${'jumpToNextRelativeDiscussion'} | ${[null]} | ${null} | ${'a'} - ${'jumpToNextRelativeDiscussion'} | ${['a']} | ${null} | ${'c'} - ${'jumpToNextRelativeDiscussion'} | ${['e']} | ${'c'} | ${'a'} + fn | args | currentId | expected + ${'jumpToNextDiscussion'} | ${[]} | ${null} | ${'a'} + ${'jumpToNextDiscussion'} | ${[]} | ${'a'} | ${'c'} + ${'jumpToNextDiscussion'} | ${[]} | ${'e'} | ${'a'} + ${'jumpToPreviousDiscussion'} | ${[]} | ${null} | ${'e'} + ${'jumpToPreviousDiscussion'} | ${[]} | ${'e'} | ${'c'} + ${'jumpToPreviousDiscussion'} | ${[]} | ${'c'} | ${'a'} `('$fn (args = $args, currentId = $currentId)', ({ fn, args, currentId, expected }) => { beforeEach(() => { store.state.notes.currentDiscussionId = currentId; @@ -133,19 +130,12 @@ describe('Discussion navigation mixin', () => { await nextTick(); }); - it('sets current discussion', () => { - expect(store.state.notes.currentDiscussionId).toEqual(expected); - }); - it('expands discussion', () => { expect(expandDiscussion).toHaveBeenCalled(); }); it('scrolls to element', () => { - expect(utils.scrollToElement).toHaveBeenCalledWith( - findDiscussion('div.discussion', expected), - { behavior: 'auto' }, - ); + expect(utils.scrollToElement).toHaveBeenCalled(); }); }); @@ -172,7 +162,7 @@ describe('Discussion navigation mixin', () => { expect(utils.scrollToElementWithContext).toHaveBeenCalledWith( findDiscussion('ul.notes', expected), - { behavior: 'auto' }, + { behavior: 'auto', offset: 0 }, ); }); }); @@ -213,7 +203,7 @@ describe('Discussion navigation mixin', () => { it('scrolls to discussion', () => { expect(utils.scrollToElement).toHaveBeenCalledWith( findDiscussion('div.discussion', expected), - { behavior: 'auto' }, + { behavior: 'auto', offset: 0 }, ); }); }); @@ -244,7 +234,6 @@ describe('Discussion navigation mixin', () => { it.each` tabValue ${'diffs'} - ${'show'} ${'other'} `( 'calls scrollToFile with setHash as $hashValue when the tab is $tabValue', diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js index 02b27eca196..989dd74b6d0 100644 --- a/spec/frontend/notes/stores/actions_spec.js +++ b/spec/frontend/notes/stores/actions_spec.js @@ -4,6 +4,7 @@ import testAction from 'helpers/vuex_action_helper'; import { TEST_HOST } from 'spec/test_constants'; import Api from '~/api'; import createFlash from '~/flash'; +import toast from '~/vue_shared/plugins/global_toast'; import { EVENT_ISSUABLE_VUE_APP_CHANGE } from '~/issuable/constants'; import axios from '~/lib/utils/axios_utils'; import * as notesConstants from '~/notes/constants'; @@ -14,7 +15,9 @@ import mutations from '~/notes/stores/mutations'; import * as utils from '~/notes/stores/utils'; import updateIssueLockMutation from '~/sidebar/components/lock/mutations/update_issue_lock.mutation.graphql'; import updateMergeRequestLockMutation from '~/sidebar/components/lock/mutations/update_merge_request_lock.mutation.graphql'; +import promoteTimelineEvent from '~/notes/graphql/promote_timeline_event.mutation.graphql'; import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub'; +import notesEventHub from '~/notes/event_hub'; import waitForPromises from 'helpers/wait_for_promises'; import { resetStore } from '../helpers'; import { @@ -38,6 +41,8 @@ jest.mock('~/flash', () => { return flash; }); +jest.mock('~/vue_shared/plugins/global_toast'); + describe('Actions Notes Store', () => { let commit; let dispatch; @@ -1324,6 +1329,102 @@ describe('Actions Notes Store', () => { }); }); + describe('promoteCommentToTimelineEvent', () => { + const actionArgs = { + noteId: '1', + addError: 'addError: Create error', + addGenericError: 'addGenericError', + }; + const commitSpy = jest.fn(); + + describe('for successful request', () => { + const timelineEventSuccessResponse = { + data: { + timelineEventPromoteFromNote: { + timelineEvent: { + id: 'gid://gitlab/IncidentManagement::TimelineEvent/19', + }, + errors: [], + }, + }, + }; + + beforeEach(() => { + jest.spyOn(utils.gqClient, 'mutate').mockResolvedValue(timelineEventSuccessResponse); + }); + + it('calls gqClient mutation with the correct values', () => { + actions.promoteCommentToTimelineEvent({ commit: () => {} }, actionArgs); + + expect(utils.gqClient.mutate).toHaveBeenCalledTimes(1); + expect(utils.gqClient.mutate).toHaveBeenCalledWith({ + mutation: promoteTimelineEvent, + variables: { + input: { + noteId: 'gid://gitlab/Note/1', + }, + }, + }); + }); + + it('returns success response', () => { + jest.spyOn(notesEventHub, '$emit').mockImplementation(() => {}); + + return actions.promoteCommentToTimelineEvent({ commit: commitSpy }, actionArgs).then(() => { + expect(notesEventHub.$emit).toHaveBeenLastCalledWith( + 'comment-promoted-to-timeline-event', + ); + expect(toast).toHaveBeenCalledWith('Comment added to the timeline.'); + expect(commitSpy).toHaveBeenCalledWith( + mutationTypes.SET_PROMOTE_COMMENT_TO_TIMELINE_PROGRESS, + false, + ); + }); + }); + }); + + describe('for failing request', () => { + const errorResponse = { + data: { + timelineEventPromoteFromNote: { + timelineEvent: null, + errors: ['Create error'], + }, + }, + }; + + it.each` + mockReject | message | captureError | error + ${true} | ${'addGenericError'} | ${true} | ${new Error()} + ${false} | ${'addError: Create error'} | ${false} | ${null} + `( + 'should show an error when submission fails', + ({ mockReject, message, captureError, error }) => { + const expectedAlertArgs = { + captureError, + error, + message, + }; + if (mockReject) { + jest.spyOn(utils.gqClient, 'mutate').mockRejectedValueOnce(new Error()); + } else { + jest.spyOn(utils.gqClient, 'mutate').mockResolvedValue(errorResponse); + } + + return actions + .promoteCommentToTimelineEvent({ commit: commitSpy }, actionArgs) + .then(() => { + expect(createFlash).toHaveBeenCalledWith(expectedAlertArgs); + expect(commitSpy).toHaveBeenCalledWith( + mutationTypes.SET_PROMOTE_COMMENT_TO_TIMELINE_PROGRESS, + false, + ); + }); + }, + ); + }); + }); + describe('setFetchingState', () => { it('commits SET_NOTES_FETCHING_STATE', () => { return testAction( diff --git a/spec/frontend/notes/stores/getters_spec.js b/spec/frontend/notes/stores/getters_spec.js index 6d078dcefcf..e03fa854e54 100644 --- a/spec/frontend/notes/stores/getters_spec.js +++ b/spec/frontend/notes/stores/getters_spec.js @@ -211,7 +211,7 @@ describe('Getters Notes Store', () => { describe('isNotesFetched', () => { it('should return the state for the fetching notes', () => { - expect(getters.isNotesFetched(state)).toBeFalsy(); + expect(getters.isNotesFetched(state)).toBe(false); }); }); @@ -512,8 +512,8 @@ describe('Getters Notes Store', () => { unresolvedDiscussionsIdsByDate: [], }; - expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(true)).toBeFalsy(); - expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(false)).toBeFalsy(); + expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(true)).toBeUndefined(); + expect(getters.firstUnresolvedDiscussionId(state, localGettersFalsy)(false)).toBeUndefined(); }); }); diff --git a/spec/frontend/notes/stores/mutation_spec.js b/spec/frontend/notes/stores/mutation_spec.js index e0a0fc43ffe..8809a496c52 100644 --- a/spec/frontend/notes/stores/mutation_spec.js +++ b/spec/frontend/notes/stores/mutation_spec.js @@ -74,7 +74,7 @@ describe('Notes Store mutations', () => { }); describe('DELETE_NOTE', () => { - it('should delete a note ', () => { + it('should delete a note', () => { const state = { discussions: [discussionMock] }; const toDelete = discussionMock.notes[0]; const lengthBefore = discussionMock.notes.length; |