Commit d66eda3f authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch '58293-extract-discussion-actions' into 'master'

Extract discussion actions into separate component

Closes #58293

See merge request gitlab-org/gitlab-ce!27227
parents 10b0883b e555c8f4
<script>
import ReplyPlaceholder from './discussion_reply_placeholder.vue';
import ResolveDiscussionButton from './discussion_resolve_button.vue';
import ResolveWithIssueButton from './discussion_resolve_with_issue_button.vue';
import JumpToNextDiscussionButton from './discussion_jump_to_next_button.vue';
export default {
name: 'DiscussionActions',
components: {
ReplyPlaceholder,
ResolveDiscussionButton,
ResolveWithIssueButton,
JumpToNextDiscussionButton,
},
props: {
discussion: {
type: Object,
required: true,
},
isResolving: {
type: Boolean,
required: true,
},
resolveButtonTitle: {
type: String,
required: true,
},
resolveWithIssuePath: {
type: String,
required: false,
default: '',
},
shouldShowJumpToNextDiscussion: {
type: Boolean,
required: true,
},
},
};
</script>
<template>
<div class="discussion-with-resolve-btn">
<reply-placeholder class="qa-discussion-reply" @onClick="$emit('showReplyForm')" />
<resolve-discussion-button
v-if="discussion.resolvable"
:is-resolving="isResolving"
:button-title="resolveButtonTitle"
@onClick="$emit('resolve')"
/>
<div v-if="discussion.resolvable" class="btn-group discussion-actions ml-sm-2" role="group">
<resolve-with-issue-button v-if="resolveWithIssuePath" :url="resolveWithIssuePath" />
<jump-to-next-discussion-button
v-if="shouldShowJumpToNextDiscussion"
@onClick="$emit('jumpToNextDiscussion')"
/>
</div>
</div>
</template>
...@@ -14,7 +14,6 @@ import { SYSTEM_NOTE } from '../constants'; ...@@ -14,7 +14,6 @@ import { SYSTEM_NOTE } from '../constants';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteableNote from './noteable_note.vue'; import noteableNote from './noteable_note.vue';
import noteHeader from './note_header.vue'; import noteHeader from './note_header.vue';
import resolveDiscussionButton from './discussion_resolve_button.vue';
import toggleRepliesWidget from './toggle_replies_widget.vue'; import toggleRepliesWidget from './toggle_replies_widget.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue'; import noteSignedOutWidget from './note_signed_out_widget.vue';
import noteEditedText from './note_edited_text.vue'; import noteEditedText from './note_edited_text.vue';
...@@ -25,10 +24,8 @@ import placeholderSystemNote from '../../vue_shared/components/notes/placeholder ...@@ -25,10 +24,8 @@ import placeholderSystemNote from '../../vue_shared/components/notes/placeholder
import noteable from '../mixins/noteable'; import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable'; import resolvable from '../mixins/resolvable';
import discussionNavigation from '../mixins/discussion_navigation'; import discussionNavigation from '../mixins/discussion_navigation';
import ReplyPlaceholder from './discussion_reply_placeholder.vue';
import ResolveWithIssueButton from './discussion_resolve_with_issue_button.vue';
import jumpToNextDiscussionButton from './discussion_jump_to_next_button.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import DiscussionActions from './discussion_actions.vue';
export default { export default {
name: 'NoteableDiscussion', name: 'NoteableDiscussion',
...@@ -40,16 +37,13 @@ export default { ...@@ -40,16 +37,13 @@ export default {
noteSignedOutWidget, noteSignedOutWidget,
noteEditedText, noteEditedText,
noteForm, noteForm,
resolveDiscussionButton,
jumpToNextDiscussionButton,
toggleRepliesWidget, toggleRepliesWidget,
ReplyPlaceholder,
placeholderNote, placeholderNote,
placeholderSystemNote, placeholderSystemNote,
ResolveWithIssueButton,
systemNote, systemNote,
DraftNote: () => import('ee_component/batch_comments/components/draft_note.vue'), DraftNote: () => import('ee_component/batch_comments/components/draft_note.vue'),
TimelineEntryItem, TimelineEntryItem,
DiscussionActions,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -465,31 +459,17 @@ Please check your network connection and try again.`; ...@@ -465,31 +459,17 @@ Please check your network connection and try again.`;
:class="{ 'is-replying': isReplying }" :class="{ 'is-replying': isReplying }"
class="discussion-reply-holder" class="discussion-reply-holder"
> >
<template v-if="!isReplying && canReply"> <discussion-actions
<div class="discussion-with-resolve-btn"> v-if="!isReplying && canReply"
<reply-placeholder class="qa-discussion-reply" @onClick="showReplyForm" /> :discussion="discussion"
<resolve-discussion-button :is-resolving="isResolving"
v-if="discussion.resolvable" :resolve-button-title="resolveButtonTitle"
:is-resolving="isResolving" :resolve-with-issue-path="resolveWithIssuePath"
:button-title="resolveButtonTitle" :should-show-jump-to-next-discussion="shouldShowJumpToNextDiscussion"
@onClick="resolveHandler" @showReplyForm="showReplyForm"
/> @resolve="resolveHandler"
<div @jumpToNextDiscussion="jumpToNextDiscussion"
v-if="discussion.resolvable" />
class="btn-group discussion-actions ml-sm-2"
role="group"
>
<resolve-with-issue-button
v-if="resolveWithIssuePath"
:url="resolveWithIssuePath"
/>
<jump-to-next-discussion-button
v-if="shouldShowJumpToNextDiscussion"
@onClick="jumpToNextDiscussion"
/>
</div>
</div>
</template>
<note-form <note-form
v-if="isReplying" v-if="isReplying"
ref="noteForm" ref="noteForm"
......
---
title: Extract DiscussionActions component from NoteableDiscussion
merge_request: 27227
author:
type: other
...@@ -15,7 +15,7 @@ module QA ...@@ -15,7 +15,7 @@ module QA
element :reply_comment_button element :reply_comment_button
end end
base.view 'app/assets/javascripts/notes/components/noteable_discussion.vue' do base.view 'app/assets/javascripts/notes/components/discussion_actions.vue' do
element :discussion_reply element :discussion_reply
end end
......
import createStore from '~/notes/stores';
import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
import { discussionMock } from '../../../javascripts/notes/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';
import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue';
import JumpToNextDiscussionButton from '~/notes/components/discussion_jump_to_next_button.vue';
describe('DiscussionActions', () => {
let wrapper;
const createComponentFactory = (shallow = true) => props => {
const localVue = createLocalVue();
const store = createStore();
const mountFn = shallow ? shallowMount : mount;
wrapper = mountFn(DiscussionActions, {
localVue,
store,
propsData: {
discussion: discussionMock,
isResolving: false,
resolveButtonTitle: 'Resolve discussion',
resolveWithIssuePath: '/some/issue/path',
shouldShowJumpToNextDiscussion: true,
...props,
},
});
};
afterEach(() => {
wrapper.destroy();
});
describe('rendering', () => {
const createComponent = createComponentFactory();
it('renders reply placeholder, resolve discussion button, resolve with issue button and jump to next discussion button', () => {
createComponent();
expect(wrapper.find(ReplyPlaceholder).exists()).toBe(true);
expect(wrapper.find(ResolveDiscussionButton).exists()).toBe(true);
expect(wrapper.find(ResolveWithIssueButton).exists()).toBe(true);
expect(wrapper.find(JumpToNextDiscussionButton).exists()).toBe(true);
});
it('only renders reply placholder if disccusion is not resolvable', () => {
const discussion = { ...discussionMock };
discussion.resolvable = false;
createComponent({ discussion });
expect(wrapper.find(ReplyPlaceholder).exists()).toBe(true);
expect(wrapper.find(ResolveDiscussionButton).exists()).toBe(false);
expect(wrapper.find(ResolveWithIssueButton).exists()).toBe(false);
expect(wrapper.find(JumpToNextDiscussionButton).exists()).toBe(false);
});
it('does not render resolve with issue button if resolveWithIssuePath is falsy', () => {
createComponent({ resolveWithIssuePath: '' });
expect(wrapper.find(ResolveWithIssueButton).exists()).toBe(false);
});
it('does not render jump to next discussion button if shouldShowJumpToNextDiscussion is false', () => {
createComponent({ shouldShowJumpToNextDiscussion: false });
expect(wrapper.find(JumpToNextDiscussionButton).exists()).toBe(false);
});
});
describe('events handling', () => {
const createComponent = createComponentFactory(false);
beforeEach(() => {
createComponent();
});
it('emits showReplyForm event when clicking on reply placeholder', () => {
jest.spyOn(wrapper.vm, '$emit');
wrapper
.find(ReplyPlaceholder)
.find('button')
.trigger('click');
expect(wrapper.vm.$emit).toHaveBeenCalledWith('showReplyForm');
});
it('emits resolve event when clicking on resolve button', () => {
jest.spyOn(wrapper.vm, '$emit');
wrapper
.find(ResolveDiscussionButton)
.find('button')
.trigger('click');
expect(wrapper.vm.$emit).toHaveBeenCalledWith('resolve');
});
it('emits jumpToNextDiscussion event when clicking on jump to next discussion button', () => {
jest.spyOn(wrapper.vm, '$emit');
wrapper
.find(JumpToNextDiscussionButton)
.find('button')
.trigger('click');
expect(wrapper.vm.$emit).toHaveBeenCalledWith('jumpToNextDiscussion');
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment