diff options
Diffstat (limited to 'spec/frontend/notes')
15 files changed, 174 insertions, 104 deletions
diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js index a2c7f0b3767..dc68c4371aa 100644 --- a/spec/frontend/notes/components/comment_form_spec.js +++ b/spec/frontend/notes/components/comment_form_spec.js @@ -9,12 +9,7 @@ import CommentForm from '~/notes/components/comment_form.vue'; import * as constants from '~/notes/constants'; import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests'; import { keyboardDownEvent } from '../../issue_show/helpers'; -import { - loggedOutnoteableData, - notesDataMock, - userDataMock, - noteableDataMock, -} from '../../notes/mock_data'; +import { loggedOutnoteableData, notesDataMock, userDataMock, noteableDataMock } from '../mock_data'; jest.mock('autosize'); jest.mock('~/commons/nav/user_merge_requests'); diff --git a/spec/frontend/notes/components/discussion_actions_spec.js b/spec/frontend/notes/components/discussion_actions_spec.js index 5101b81e3ee..44dc148933c 100644 --- a/spec/frontend/notes/components/discussion_actions_spec.js +++ b/spec/frontend/notes/components/discussion_actions_spec.js @@ -1,5 +1,5 @@ import { shallowMount, mount } from '@vue/test-utils'; -import { discussionMock } from '../../notes/mock_data'; +import { discussionMock } from '../mock_data'; import DiscussionActions from '~/notes/components/discussion_actions.vue'; import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue'; import ResolveDiscussionButton from '~/notes/components/discussion_resolve_button.vue'; diff --git a/spec/frontend/notes/components/discussion_counter_spec.js b/spec/frontend/notes/components/discussion_counter_spec.js index 77603c16f82..04535aa17c5 100644 --- a/spec/frontend/notes/components/discussion_counter_spec.js +++ b/spec/frontend/notes/components/discussion_counter_spec.js @@ -75,15 +75,14 @@ describe('DiscussionCounter component', () => { }); it.each` - title | resolved | isActive | icon | groupLength - ${'not allResolved'} | ${false} | ${false} | ${'check-circle'} | ${3} - ${'allResolved'} | ${true} | ${true} | ${'check-circle-filled'} | ${1} - `('renders correctly if $title', ({ resolved, isActive, icon, groupLength }) => { + title | resolved | isActive | groupLength + ${'not allResolved'} | ${false} | ${false} | ${3} + ${'allResolved'} | ${true} | ${true} | ${1} + `('renders correctly if $title', ({ resolved, isActive, groupLength }) => { updateStore({ resolvable: true, resolved }); wrapper = shallowMount(DiscussionCounter, { store, localVue }); expect(wrapper.find(`.is-active`).exists()).toBe(isActive); - expect(wrapper.find({ name: icon }).exists()).toBe(true); expect(wrapper.findAll('[role="group"').length).toBe(groupLength); }); }); diff --git a/spec/frontend/notes/components/discussion_filter_spec.js b/spec/frontend/notes/components/discussion_filter_spec.js index b8d2d721443..7f042c0e9de 100644 --- a/spec/frontend/notes/components/discussion_filter_spec.js +++ b/spec/frontend/notes/components/discussion_filter_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import createEventHub from '~/helpers/event_hub_factory'; import Vuex from 'vuex'; import { createLocalVue, mount } from '@vue/test-utils'; @@ -132,7 +132,7 @@ describe('DiscussionFilter component', () => { }); describe('Merge request tabs', () => { - eventHub = new Vue(); + eventHub = createEventHub(); beforeEach(() => { window.mrTabs = { diff --git a/spec/frontend/notes/components/discussion_notes_spec.js b/spec/frontend/notes/components/discussion_notes_spec.js index 81773752037..5a10deefd09 100644 --- a/spec/frontend/notes/components/discussion_notes_spec.js +++ b/spec/frontend/notes/components/discussion_notes_spec.js @@ -7,7 +7,7 @@ import PlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue' import PlaceholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue'; import SystemNote from '~/vue_shared/components/notes/system_note.vue'; import createStore from '~/notes/stores'; -import { noteableDataMock, discussionMock, notesDataMock } from '../../notes/mock_data'; +import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data'; describe('DiscussionNotes', () => { let wrapper; diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js index bccac03126c..8270c148fb5 100644 --- a/spec/frontend/notes/components/note_form_spec.js +++ b/spec/frontend/notes/components/note_form_spec.js @@ -161,18 +161,18 @@ describe('issue_note_form component', () => { describe('actions', () => { it('should be possible to cancel', () => { - // TODO: do not spy on vm - jest.spyOn(wrapper.vm, 'cancelHandler'); + const cancelHandler = jest.fn(); wrapper.setProps({ ...props, isEditing: true, }); + wrapper.setMethods({ cancelHandler }); return wrapper.vm.$nextTick().then(() => { - const cancelButton = wrapper.find('.note-edit-cancel'); + const cancelButton = wrapper.find('[data-testid="cancel"]'); cancelButton.trigger('click'); - expect(wrapper.vm.cancelHandler).toHaveBeenCalled(); + expect(cancelHandler).toHaveBeenCalledWith(true); }); }); diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js index d477de69716..2bb08b60569 100644 --- a/spec/frontend/notes/components/note_header_spec.js +++ b/spec/frontend/notes/components/note_header_spec.js @@ -1,7 +1,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { nextTick } from 'vue'; import Vuex from 'vuex'; import NoteHeader from '~/notes/components/note_header.vue'; -import GitlabTeamMemberBadge from '~/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue'; const localVue = createLocalVue(); localVue.use(Vuex); @@ -18,6 +18,7 @@ describe('NoteHeader component', () => { const findActionText = () => wrapper.find({ ref: 'actionText' }); const findTimestampLink = () => wrapper.find({ ref: 'noteTimestampLink' }); const findTimestamp = () => wrapper.find({ ref: 'noteTimestamp' }); + const findConfidentialIndicator = () => wrapper.find('[data-testid="confidentialIndicator"]'); const findSpinner = () => wrapper.find({ ref: 'spinner' }); const author = { @@ -140,20 +141,6 @@ describe('NoteHeader component', () => { }); }); - test.each` - props | expected | message1 | message2 - ${{ author: { ...author, is_gitlab_employee: true } }} | ${true} | ${'renders'} | ${'true'} - ${{ author: { ...author, is_gitlab_employee: false } }} | ${false} | ${"doesn't render"} | ${'false'} - ${{ author }} | ${false} | ${"doesn't render"} | ${'undefined'} - `( - '$message1 GitLab team member badge when `is_gitlab_employee` is $message2', - ({ props, expected }) => { - createComponent(props); - - expect(wrapper.find(GitlabTeamMemberBadge).exists()).toBe(expected); - }, - ); - describe('loading spinner', () => { it('shows spinner when showSpinner is true', () => { createComponent(); @@ -179,4 +166,81 @@ describe('NoteHeader component', () => { expect(findTimestamp().exists()).toBe(true); }); }); + + describe('author username link', () => { + it('proxies `mouseenter` event to author name link', () => { + createComponent({ author }); + + const dispatchEvent = jest.spyOn(wrapper.vm.$refs.authorNameLink, 'dispatchEvent'); + + wrapper.find({ ref: 'authorUsernameLink' }).trigger('mouseenter'); + + expect(dispatchEvent).toHaveBeenCalledWith(new Event('mouseenter')); + }); + + it('proxies `mouseleave` event to author name link', () => { + createComponent({ author }); + + const dispatchEvent = jest.spyOn(wrapper.vm.$refs.authorNameLink, 'dispatchEvent'); + + wrapper.find({ ref: 'authorUsernameLink' }).trigger('mouseleave'); + + expect(dispatchEvent).toHaveBeenCalledWith(new Event('mouseleave')); + }); + }); + + describe('when author status tooltip is opened', () => { + it('removes `title` attribute from emoji to prevent duplicate tooltips', () => { + createComponent({ + author: { + ...author, + status_tooltip_html: + '"<span class="user-status-emoji has-tooltip" title="foo bar" data-html="true" data-placement="top"><gl-emoji title="basketball and hoop" data-name="basketball" data-unicode-version="6.0">🏀</gl-emoji></span>"', + }, + }); + + return nextTick().then(() => { + const authorStatus = wrapper.find({ ref: 'authorStatus' }); + authorStatus.trigger('mouseenter'); + + expect(authorStatus.find('gl-emoji').attributes('title')).toBeUndefined(); + }); + }); + }); + + describe('when author username link is hovered', () => { + it('toggles hover specific CSS classes on author name link', done => { + createComponent({ author }); + + const authorUsernameLink = wrapper.find({ ref: 'authorUsernameLink' }); + const authorNameLink = wrapper.find({ ref: 'authorNameLink' }); + + authorUsernameLink.trigger('mouseenter'); + + nextTick(() => { + expect(authorNameLink.classes()).toContain('hover'); + expect(authorNameLink.classes()).toContain('text-underline'); + + authorUsernameLink.trigger('mouseleave'); + + nextTick(() => { + expect(authorNameLink.classes()).not.toContain('hover'); + expect(authorNameLink.classes()).not.toContain('text-underline'); + + done(); + }); + }); + }); + }); + + describe('with confidentiality indicator', () => { + it.each` + status | condition + ${true} | ${'shows'} + ${false} | ${'hides'} + `('$condition icon indicator when isConfidential is $status', ({ status }) => { + createComponent({ isConfidential: status }); + expect(findConfidentialIndicator().exists()).toBe(status); + }); + }); }); diff --git a/spec/frontend/notes/components/noteable_discussion_spec.js b/spec/frontend/notes/components/noteable_discussion_spec.js index b91f599f158..b14ec2a65be 100644 --- a/spec/frontend/notes/components/noteable_discussion_spec.js +++ b/spec/frontend/notes/components/noteable_discussion_spec.js @@ -138,7 +138,7 @@ describe('noteable_discussion component', () => { describe('signout widget', () => { beforeEach(() => { - originalGon = Object.assign({}, window.gon); + originalGon = { ...window.gon }; window.gon = window.gon || {}; }); diff --git a/spec/frontend/notes/components/notes_app_spec.js b/spec/frontend/notes/components/notes_app_spec.js index e22dd85f221..fbfba2efb1d 100644 --- a/spec/frontend/notes/components/notes_app_spec.js +++ b/spec/frontend/notes/components/notes_app_spec.js @@ -10,7 +10,7 @@ import createStore from '~/notes/stores'; import * as constants from '~/notes/constants'; import '~/behaviors/markdown/render_gfm'; // TODO: use generated fixture (https://gitlab.com/gitlab-org/gitlab-foss/issues/62491) -import * as mockData from '../../notes/mock_data'; +import * as mockData from '../mock_data'; import * as urlUtility from '~/lib/utils/url_utility'; import OrderedLayout from '~/vue_shared/components/ordered_layout.vue'; diff --git a/spec/frontend/notes/mixins/discussion_navigation_spec.js b/spec/frontend/notes/mixins/discussion_navigation_spec.js index 4e5325b8bc3..120de023099 100644 --- a/spec/frontend/notes/mixins/discussion_navigation_spec.js +++ b/spec/frontend/notes/mixins/discussion_navigation_spec.js @@ -3,6 +3,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; import * as utils from '~/lib/utils/common_utils'; import discussionNavigation from '~/notes/mixins/discussion_navigation'; import eventHub from '~/notes/event_hub'; +import createEventHub from '~/helpers/event_hub_factory'; import notesModule from '~/notes/stores/modules'; import { setHTMLFixture } from 'helpers/fixtures'; @@ -67,8 +68,7 @@ describe('Discussion navigation mixin', () => { describe('cycle through discussions', () => { beforeEach(() => { - // eslint-disable-next-line new-cap - window.mrTabs = { eventHub: new localVue(), tabShown: jest.fn() }; + window.mrTabs = { eventHub: createEventHub(), tabShown: jest.fn() }; }); describe.each` diff --git a/spec/frontend/notes/mock_data.js b/spec/frontend/notes/mock_data.js index 9ed79c61c22..980faac2b04 100644 --- a/spec/frontend/notes/mock_data.js +++ b/spec/frontend/notes/mock_data.js @@ -57,6 +57,7 @@ export const noteableDataMock = { updated_by_id: 1, web_url: '/gitlab-org/gitlab-foss/issues/26', noteableType: 'issue', + blocked_by_issues: [], }; export const lastFetchedAt = '1501862675'; diff --git a/spec/frontend/notes/old_notes_spec.js b/spec/frontend/notes/old_notes_spec.js index 49b887b21b4..cb1d563ece7 100644 --- a/spec/frontend/notes/old_notes_spec.js +++ b/spec/frontend/notes/old_notes_spec.js @@ -33,7 +33,6 @@ gl.utils.disableButtonIfEmptyField = () => {}; // eslint-disable-next-line jest/no-disabled-tests describe.skip('Old Notes (~/notes.js)', () => { beforeEach(() => { - jest.useFakeTimers(); loadFixtures(fixture); // Re-declare this here so that test_setup.js#beforeEach() doesn't @@ -194,7 +193,7 @@ describe.skip('Old Notes (~/notes.js)', () => { $('.js-comment-button').click(); const $targetNote = $notesContainer.find(`#note_${noteEntity.id}`); - const updatedNote = Object.assign({}, noteEntity); + const updatedNote = { ...noteEntity }; updatedNote.note = 'bar'; notes.updateNote(updatedNote, $targetNote); @@ -213,13 +212,6 @@ describe.skip('Old Notes (~/notes.js)', () => { jest.spyOn($note, 'toggleClass'); }); - afterEach(() => { - expect(typeof urlUtility.getLocationHash.mock).toBe('object'); - urlUtility.getLocationHash.mockRestore(); - expect(urlUtility.getLocationHash.mock).toBeUndefined(); - expect(urlUtility.getLocationHash()).toBeNull(); - }); - // urlUtility is a dependency of the notes module. Its getLocatinHash() method should be called internally. it('sets target when hash matches', () => { @@ -630,48 +622,6 @@ describe.skip('Old Notes (~/notes.js)', () => { done(); }); }); - - // This is a bad test carried over from the Karma -> Jest migration. - // The corresponding test in the Karma suite tests for - // elements and methods that don't actually exist, and gives a false - // positive pass. - /* - it('should show flash error message when comment failed to be updated', done => { - mockNotesPost(); - jest.spyOn(notes, 'addFlash').mockName('addFlash'); - - $('.js-comment-button').click(); - - deferredPromise() - .then(() => { - const $noteEl = $notesContainer.find(`#note_${note.id}`); - $noteEl.find('.js-note-edit').click(); - $noteEl.find('textarea.js-note-text').val(updatedComment); - - mockNotesPostError(); - - $noteEl.find('.js-comment-save-button').click(); - notes.updateComment({preventDefault: () => {}}); - }) - .then(() => deferredPromise()) - .then(() => { - const $updatedNoteEl = $notesContainer.find(`#note_${note.id}`); - - expect($updatedNoteEl.hasClass('.being-posted')).toEqual(false); // Remove being-posted visuals - expect( - $updatedNoteEl - .find('.note-text') - .text() - .trim(), - ).toEqual(sampleComment); // See if comment reverted back to original - - expect(notes.addFlash).toHaveBeenCalled(); - expect(notes.flashContainer.style.display).not.toBe('none'); - done(); - }) - .catch(done.fail); - }, 5000); - */ }); describe('postComment with Slash commands', () => { diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js index 544d482e7fc..cbfb9597159 100644 --- a/spec/frontend/notes/stores/actions_spec.js +++ b/spec/frontend/notes/stores/actions_spec.js @@ -34,6 +34,11 @@ describe('Actions Notes Store', () => { dispatch = jest.fn(); state = {}; axiosMock = new AxiosMockAdapter(axios); + + // This is necessary as we query Close issue button at the top of issue page when clicking bottom button + setFixtures( + '<div class="detail-page-header-actions"><button class="btn-close btn-grouped"></button></div>', + ); }); afterEach(() => { @@ -242,9 +247,31 @@ describe('Actions Notes Store', () => { }); }); - describe('poll', () => { - jest.useFakeTimers(); + describe('toggleBlockedIssueWarning', () => { + it('should set issue warning as true', done => { + testAction( + actions.toggleBlockedIssueWarning, + true, + {}, + [{ type: 'TOGGLE_BLOCKED_ISSUE_WARNING', payload: true }], + [], + done, + ); + }); + it('should set issue warning as false', done => { + testAction( + actions.toggleBlockedIssueWarning, + false, + {}, + [{ type: 'TOGGLE_BLOCKED_ISSUE_WARNING', payload: false }], + [], + done, + ); + }); + }); + + describe('poll', () => { beforeEach(done => { jest.spyOn(axios, 'get'); diff --git a/spec/frontend/notes/stores/collapse_utils_spec.js b/spec/frontend/notes/stores/collapse_utils_spec.js index d3019f4b9a4..a74809eed79 100644 --- a/spec/frontend/notes/stores/collapse_utils_spec.js +++ b/spec/frontend/notes/stores/collapse_utils_spec.js @@ -18,9 +18,7 @@ describe('Collapse utils', () => { }); it('returns false when a system note is not a description type', () => { - expect(isDescriptionSystemNote(Object.assign({}, mockSystemNote, { note: 'foo' }))).toEqual( - false, - ); + expect(isDescriptionSystemNote({ ...mockSystemNote, note: 'foo' })).toEqual(false); }); it('gets the time difference between two notes', () => { diff --git a/spec/frontend/notes/stores/mutation_spec.js b/spec/frontend/notes/stores/mutation_spec.js index 06d2654ceca..27e3490d64b 100644 --- a/spec/frontend/notes/stores/mutation_spec.js +++ b/spec/frontend/notes/stores/mutation_spec.js @@ -50,7 +50,7 @@ describe('Notes Store mutations', () => { }); describe('ADD_NEW_REPLY_TO_DISCUSSION', () => { - const newReply = Object.assign({}, note, { discussion_id: discussionMock.id }); + const newReply = { ...note, discussion_id: discussionMock.id }; let state; @@ -86,7 +86,7 @@ describe('Notes Store mutations', () => { describe('EXPAND_DISCUSSION', () => { it('should expand a collapsed discussion', () => { - const discussion = Object.assign({}, discussionMock, { expanded: false }); + const discussion = { ...discussionMock, expanded: false }; const state = { discussions: [discussion], @@ -100,7 +100,7 @@ describe('Notes Store mutations', () => { describe('COLLAPSE_DISCUSSION', () => { it('should collapse an expanded discussion', () => { - const discussion = Object.assign({}, discussionMock, { expanded: true }); + const discussion = { ...discussionMock, expanded: true }; const state = { discussions: [discussion], @@ -114,7 +114,7 @@ describe('Notes Store mutations', () => { describe('REMOVE_PLACEHOLDER_NOTES', () => { it('should remove all placeholder notes in indivudal notes and discussion', () => { - const placeholderNote = Object.assign({}, individualNote, { isPlaceholderNote: true }); + const placeholderNote = { ...individualNote, isPlaceholderNote: true }; const state = { discussions: [placeholderNote] }; mutations.REMOVE_PLACEHOLDER_NOTES(state); @@ -298,7 +298,7 @@ describe('Notes Store mutations', () => { describe('TOGGLE_DISCUSSION', () => { it('should open a closed discussion', () => { - const discussion = Object.assign({}, discussionMock, { expanded: false }); + const discussion = { ...discussionMock, expanded: false }; const state = { discussions: [discussion], @@ -348,8 +348,8 @@ describe('Notes Store mutations', () => { }); it('should open all closed discussions', () => { - const discussion1 = Object.assign({}, discussionMock, { id: 0, expanded: false }); - const discussion2 = Object.assign({}, discussionMock, { id: 1, expanded: true }); + const discussion1 = { ...discussionMock, id: 0, expanded: false }; + const discussion2 = { ...discussionMock, id: 1, expanded: true }; const discussionIds = [discussion1.id, discussion2.id]; const state = { discussions: [discussion1, discussion2] }; @@ -362,8 +362,8 @@ describe('Notes Store mutations', () => { }); it('should close all opened discussions', () => { - const discussion1 = Object.assign({}, discussionMock, { id: 0, expanded: false }); - const discussion2 = Object.assign({}, discussionMock, { id: 1, expanded: true }); + const discussion1 = { ...discussionMock, id: 0, expanded: false }; + const discussion2 = { ...discussionMock, id: 1, expanded: true }; const discussionIds = [discussion1.id, discussion2.id]; const state = { discussions: [discussion1, discussion2] }; @@ -382,7 +382,7 @@ describe('Notes Store mutations', () => { discussions: [individualNote], }; - const updated = Object.assign({}, individualNote.notes[0], { note: 'Foo' }); + const updated = { ...individualNote.notes[0], note: 'Foo' }; mutations.UPDATE_NOTE(state, updated); @@ -664,4 +664,40 @@ describe('Notes Store mutations', () => { expect(state.discussionSortOrder).toBe(DESC); }); }); + + describe('TOGGLE_BLOCKED_ISSUE_WARNING', () => { + it('should set isToggleBlockedIssueWarning as true', () => { + const state = { + discussions: [], + targetNoteHash: null, + lastFetchedAt: null, + isToggleStateButtonLoading: false, + isToggleBlockedIssueWarning: false, + notesData: {}, + userData: {}, + noteableData: {}, + }; + + mutations.TOGGLE_BLOCKED_ISSUE_WARNING(state, true); + + expect(state.isToggleBlockedIssueWarning).toEqual(true); + }); + + it('should set isToggleBlockedIssueWarning as false', () => { + const state = { + discussions: [], + targetNoteHash: null, + lastFetchedAt: null, + isToggleStateButtonLoading: false, + isToggleBlockedIssueWarning: true, + notesData: {}, + userData: {}, + noteableData: {}, + }; + + mutations.TOGGLE_BLOCKED_ISSUE_WARNING(state, false); + + expect(state.isToggleBlockedIssueWarning).toEqual(false); + }); + }); }); |