Merge branch '58294-discussion-notes-component' into 'master'
Extract discussion notes into new component Closes #58294 See merge request gitlab-org/gitlab-ce!27066
This commit is contained in:
commit
0220e83666
9 changed files with 417 additions and 210 deletions
155
app/assets/javascripts/notes/components/discussion_notes.vue
Normal file
155
app/assets/javascripts/notes/components/discussion_notes.vue
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import { SYSTEM_NOTE } from '../constants';
|
||||||
|
import { __ } from '~/locale';
|
||||||
|
import NoteableNote from './noteable_note.vue';
|
||||||
|
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 ToggleRepliesWidget from './toggle_replies_widget.vue';
|
||||||
|
import NoteEditedText from './note_edited_text.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DiscussionNotes',
|
||||||
|
components: {
|
||||||
|
ToggleRepliesWidget,
|
||||||
|
NoteEditedText,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
discussion: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isExpanded: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
diffLine: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
line: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
shouldGroupReplies: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
helpPagePath: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters(['userCanReply']),
|
||||||
|
hasReplies() {
|
||||||
|
return !!this.replies.length;
|
||||||
|
},
|
||||||
|
replies() {
|
||||||
|
return this.discussion.notes.slice(1);
|
||||||
|
},
|
||||||
|
firstNote() {
|
||||||
|
return this.discussion.notes.slice(0, 1)[0];
|
||||||
|
},
|
||||||
|
resolvedText() {
|
||||||
|
return this.discussion.resolved_by_push ? __('Automatically resolved') : __('Resolved');
|
||||||
|
},
|
||||||
|
commit() {
|
||||||
|
if (!this.discussion.for_commit) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: this.discussion.commit_id,
|
||||||
|
url: this.discussion.discussion_path,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
componentName(note) {
|
||||||
|
if (note.isPlaceholderNote) {
|
||||||
|
if (note.placeholderType === SYSTEM_NOTE) {
|
||||||
|
return PlaceholderSystemNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlaceholderNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (note.system) {
|
||||||
|
return SystemNote;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NoteableNote;
|
||||||
|
},
|
||||||
|
componentData(note) {
|
||||||
|
return note.isPlaceholderNote ? note.notes[0] : note;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="discussion-notes">
|
||||||
|
<ul class="notes">
|
||||||
|
<template v-if="shouldGroupReplies">
|
||||||
|
<component
|
||||||
|
:is="componentName(firstNote)"
|
||||||
|
:note="componentData(firstNote)"
|
||||||
|
:line="line"
|
||||||
|
:commit="commit"
|
||||||
|
:help-page-path="helpPagePath"
|
||||||
|
:show-reply-button="userCanReply"
|
||||||
|
@handle-delete-note="$emit('deleteNote')"
|
||||||
|
@start-replying="$emit('startReplying')"
|
||||||
|
>
|
||||||
|
<note-edited-text
|
||||||
|
v-if="discussion.resolved"
|
||||||
|
slot="discussion-resolved-text"
|
||||||
|
:edited-at="discussion.resolved_at"
|
||||||
|
:edited-by="discussion.resolved_by"
|
||||||
|
:action-text="resolvedText"
|
||||||
|
class-name="discussion-headline-light js-discussion-headline discussion-resolved-text"
|
||||||
|
/>
|
||||||
|
<slot slot="avatar-badge" name="avatar-badge"></slot>
|
||||||
|
</component>
|
||||||
|
<toggle-replies-widget
|
||||||
|
v-if="hasReplies"
|
||||||
|
:collapsed="!isExpanded"
|
||||||
|
:replies="replies"
|
||||||
|
@toggle="$emit('toggleDiscussion')"
|
||||||
|
/>
|
||||||
|
<template v-if="isExpanded">
|
||||||
|
<component
|
||||||
|
:is="componentName(note)"
|
||||||
|
v-for="note in replies"
|
||||||
|
:key="note.id"
|
||||||
|
:note="componentData(note)"
|
||||||
|
:help-page-path="helpPagePath"
|
||||||
|
:line="line"
|
||||||
|
@handle-delete-note="$emit('deleteNote')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<component
|
||||||
|
:is="componentName(note)"
|
||||||
|
v-for="(note, index) in discussion.notes"
|
||||||
|
:key="note.id"
|
||||||
|
:note="componentData(note)"
|
||||||
|
:help-page-path="helpPagePath"
|
||||||
|
:line="diffLine"
|
||||||
|
@handle-delete-note="$emit('deleteNote')"
|
||||||
|
>
|
||||||
|
<slot v-if="index === 0" slot="avatar-badge" name="avatar-badge"></slot>
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
<slot :show-replies="isExpanded || !hasReplies" name="footer"></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -5,44 +5,35 @@ import { GlTooltipDirective } from '@gitlab/ui';
|
||||||
import { truncateSha } from '~/lib/utils/text_utility';
|
import { truncateSha } from '~/lib/utils/text_utility';
|
||||||
import { s__, __, sprintf } from '~/locale';
|
import { s__, __, sprintf } from '~/locale';
|
||||||
import { clearDraft, getDiscussionReplyKey } from '~/lib/utils/autosave';
|
import { clearDraft, getDiscussionReplyKey } from '~/lib/utils/autosave';
|
||||||
import systemNote from '~/vue_shared/components/notes/system_note.vue';
|
|
||||||
import icon from '~/vue_shared/components/icon.vue';
|
import icon from '~/vue_shared/components/icon.vue';
|
||||||
import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
|
import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
|
||||||
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
|
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
|
||||||
import Flash from '../../flash';
|
import Flash from '../../flash';
|
||||||
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 noteHeader from './note_header.vue';
|
import noteHeader from './note_header.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';
|
||||||
import noteForm from './note_form.vue';
|
import noteForm from './note_form.vue';
|
||||||
import diffWithNote from './diff_with_note.vue';
|
import diffWithNote from './diff_with_note.vue';
|
||||||
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
|
|
||||||
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
|
|
||||||
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 eventHub from '../event_hub';
|
import eventHub from '../event_hub';
|
||||||
|
import DiscussionNotes from './discussion_notes.vue';
|
||||||
import DiscussionActions from './discussion_actions.vue';
|
import DiscussionActions from './discussion_actions.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NoteableDiscussion',
|
name: 'NoteableDiscussion',
|
||||||
components: {
|
components: {
|
||||||
icon,
|
icon,
|
||||||
noteableNote,
|
|
||||||
userAvatarLink,
|
userAvatarLink,
|
||||||
noteHeader,
|
noteHeader,
|
||||||
noteSignedOutWidget,
|
noteSignedOutWidget,
|
||||||
noteEditedText,
|
noteEditedText,
|
||||||
noteForm,
|
noteForm,
|
||||||
toggleRepliesWidget,
|
|
||||||
placeholderNote,
|
|
||||||
placeholderSystemNote,
|
|
||||||
systemNote,
|
|
||||||
DraftNote: () => import('ee_component/batch_comments/components/draft_note.vue'),
|
DraftNote: () => import('ee_component/batch_comments/components/draft_note.vue'),
|
||||||
TimelineEntryItem,
|
TimelineEntryItem,
|
||||||
|
DiscussionNotes,
|
||||||
DiscussionActions,
|
DiscussionActions,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
|
@ -91,6 +82,7 @@ export default {
|
||||||
...mapGetters([
|
...mapGetters([
|
||||||
'convertedDisscussionIds',
|
'convertedDisscussionIds',
|
||||||
'getNoteableData',
|
'getNoteableData',
|
||||||
|
'userCanReply',
|
||||||
'nextUnresolvedDiscussionId',
|
'nextUnresolvedDiscussionId',
|
||||||
'unresolvedDiscussionsCount',
|
'unresolvedDiscussionsCount',
|
||||||
'hasUnresolvedDiscussions',
|
'hasUnresolvedDiscussions',
|
||||||
|
@ -102,21 +94,12 @@ export default {
|
||||||
autosaveKey() {
|
autosaveKey() {
|
||||||
return getDiscussionReplyKey(this.firstNote.noteable_type, this.discussion.id);
|
return getDiscussionReplyKey(this.firstNote.noteable_type, this.discussion.id);
|
||||||
},
|
},
|
||||||
canReply() {
|
|
||||||
return this.getNoteableData.current_user.can_create_note;
|
|
||||||
},
|
|
||||||
newNotePath() {
|
newNotePath() {
|
||||||
return this.getNoteableData.create_note_path;
|
return this.getNoteableData.create_note_path;
|
||||||
},
|
},
|
||||||
hasReplies() {
|
|
||||||
return this.discussion.notes.length > 1;
|
|
||||||
},
|
|
||||||
firstNote() {
|
firstNote() {
|
||||||
return this.discussion.notes.slice(0, 1)[0];
|
return this.discussion.notes.slice(0, 1)[0];
|
||||||
},
|
},
|
||||||
replies() {
|
|
||||||
return this.discussion.notes.slice(1);
|
|
||||||
},
|
|
||||||
lastUpdatedBy() {
|
lastUpdatedBy() {
|
||||||
const { notes } = this.discussion;
|
const { notes } = this.discussion;
|
||||||
|
|
||||||
|
@ -222,18 +205,8 @@ export default {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
commit() {
|
|
||||||
if (!this.discussion.for_commit) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: this.discussion.commit_id,
|
|
||||||
url: this.discussion.discussion_path,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
resolveWithIssuePath() {
|
resolveWithIssuePath() {
|
||||||
return !this.discussionResolved && this.discussion.resolve_with_issue_path;
|
return !this.discussionResolved ? this.discussion.resolve_with_issue_path : '';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
@ -252,24 +225,6 @@ export default {
|
||||||
'removeConvertedDiscussion',
|
'removeConvertedDiscussion',
|
||||||
]),
|
]),
|
||||||
truncateSha,
|
truncateSha,
|
||||||
componentName(note) {
|
|
||||||
if (note.isPlaceholderNote) {
|
|
||||||
if (note.placeholderType === SYSTEM_NOTE) {
|
|
||||||
return placeholderSystemNote;
|
|
||||||
}
|
|
||||||
|
|
||||||
return placeholderNote;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (note.system) {
|
|
||||||
return systemNote;
|
|
||||||
}
|
|
||||||
|
|
||||||
return noteableNote;
|
|
||||||
},
|
|
||||||
componentData(note) {
|
|
||||||
return note.isPlaceholderNote ? note.notes[0] : note;
|
|
||||||
},
|
|
||||||
toggleDiscussionHandler() {
|
toggleDiscussionHandler() {
|
||||||
this.toggleDiscussion({ discussionId: this.discussion.id });
|
this.toggleDiscussion({ discussionId: this.discussion.id });
|
||||||
},
|
},
|
||||||
|
@ -399,73 +354,31 @@ Please check your network connection and try again.`;
|
||||||
v-bind="wrapperComponentProps"
|
v-bind="wrapperComponentProps"
|
||||||
class="card discussion-wrapper"
|
class="card discussion-wrapper"
|
||||||
>
|
>
|
||||||
<div class="discussion-notes">
|
<discussion-notes
|
||||||
<ul class="notes">
|
:discussion="discussion"
|
||||||
<template v-if="shouldGroupReplies">
|
:diff-line="diffLine"
|
||||||
<component
|
|
||||||
:is="componentName(firstNote)"
|
|
||||||
:note="componentData(firstNote)"
|
|
||||||
:line="line"
|
|
||||||
:commit="commit"
|
|
||||||
:help-page-path="helpPagePath"
|
:help-page-path="helpPagePath"
|
||||||
:show-reply-button="canReply"
|
:is-expanded="isExpanded"
|
||||||
@handleDeleteNote="deleteNoteHandler"
|
:line="line"
|
||||||
|
:should-group-replies="shouldGroupReplies"
|
||||||
@startReplying="showReplyForm"
|
@startReplying="showReplyForm"
|
||||||
|
@toggleDiscussion="toggleDiscussionHandler"
|
||||||
|
@deleteNote="deleteNoteHandler"
|
||||||
>
|
>
|
||||||
<note-edited-text
|
|
||||||
v-if="discussion.resolved"
|
|
||||||
slot="discussion-resolved-text"
|
|
||||||
:edited-at="discussion.resolved_at"
|
|
||||||
:edited-by="discussion.resolved_by"
|
|
||||||
:action-text="resolvedText"
|
|
||||||
class-name="discussion-headline-light js-discussion-headline discussion-resolved-text"
|
|
||||||
/>
|
|
||||||
<slot slot="avatar-badge" name="avatar-badge"></slot>
|
<slot slot="avatar-badge" name="avatar-badge"></slot>
|
||||||
</component>
|
<template #footer="{ showReplies }">
|
||||||
<toggle-replies-widget
|
|
||||||
v-if="hasReplies"
|
|
||||||
:collapsed="!isExpanded"
|
|
||||||
:replies="replies"
|
|
||||||
@toggle="toggleDiscussionHandler"
|
|
||||||
/>
|
|
||||||
<template v-if="isExpanded">
|
|
||||||
<component
|
|
||||||
:is="componentName(note)"
|
|
||||||
v-for="note in replies"
|
|
||||||
:key="note.id"
|
|
||||||
:note="componentData(note)"
|
|
||||||
:help-page-path="helpPagePath"
|
|
||||||
:line="line"
|
|
||||||
@handleDeleteNote="deleteNoteHandler"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<component
|
|
||||||
:is="componentName(note)"
|
|
||||||
v-for="(note, index) in discussion.notes"
|
|
||||||
:key="note.id"
|
|
||||||
:note="componentData(note)"
|
|
||||||
:help-page-path="helpPagePath"
|
|
||||||
:line="diffLine"
|
|
||||||
@handleDeleteNote="deleteNoteHandler"
|
|
||||||
>
|
|
||||||
<slot v-if="index === 0" slot="avatar-badge" name="avatar-badge"></slot>
|
|
||||||
</component>
|
|
||||||
</template>
|
|
||||||
</ul>
|
|
||||||
<draft-note
|
<draft-note
|
||||||
v-if="showDraft(discussion.reply_id)"
|
v-if="showDraft(discussion.reply_id)"
|
||||||
:key="`draft_${discussion.id}`"
|
:key="`draft_${discussion.id}`"
|
||||||
:draft="draftForDiscussion(discussion.reply_id)"
|
:draft="draftForDiscussion(discussion.reply_id)"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-else-if="isExpanded || !hasReplies"
|
v-else-if="showReplies"
|
||||||
:class="{ 'is-replying': isReplying }"
|
:class="{ 'is-replying': isReplying }"
|
||||||
class="discussion-reply-holder"
|
class="discussion-reply-holder"
|
||||||
>
|
>
|
||||||
<discussion-actions
|
<discussion-actions
|
||||||
v-if="!isReplying && canReply"
|
v-if="!isReplying && userCanReply"
|
||||||
:discussion="discussion"
|
:discussion="discussion"
|
||||||
:is-resolving="isResolving"
|
:is-resolving="isResolving"
|
||||||
:resolve-button-title="resolveButtonTitle"
|
:resolve-button-title="resolveButtonTitle"
|
||||||
|
@ -487,9 +400,10 @@ Please check your network connection and try again.`;
|
||||||
@handleFormUpdate="saveReply"
|
@handleFormUpdate="saveReply"
|
||||||
@cancelForm="cancelReplyForm"
|
@cancelForm="cancelReplyForm"
|
||||||
/>
|
/>
|
||||||
<note-signed-out-widget v-if="!canReply" />
|
<note-signed-out-widget v-if="!userCanReply" />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
</discussion-notes>
|
||||||
</component>
|
</component>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -67,6 +67,7 @@ export default {
|
||||||
'isLoading',
|
'isLoading',
|
||||||
'commentsDisabled',
|
'commentsDisabled',
|
||||||
'getNoteableData',
|
'getNoteableData',
|
||||||
|
'userCanReply',
|
||||||
]),
|
]),
|
||||||
noteableType() {
|
noteableType() {
|
||||||
return this.noteableData.noteableType;
|
return this.noteableData.noteableType;
|
||||||
|
@ -83,7 +84,7 @@ export default {
|
||||||
return this.discussions;
|
return this.discussions;
|
||||||
},
|
},
|
||||||
canReply() {
|
canReply() {
|
||||||
return this.getNoteableData.current_user.can_create_note && !this.commentsDisabled;
|
return this.userCanReply && !this.commentsDisabled;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
|
@ -20,6 +20,8 @@ export const getNoteableData = state => state.noteableData;
|
||||||
|
|
||||||
export const getNoteableDataByProp = state => prop => state.noteableData[prop];
|
export const getNoteableDataByProp = state => prop => state.noteableData[prop];
|
||||||
|
|
||||||
|
export const userCanReply = state => !!state.noteableData.current_user.can_create_note;
|
||||||
|
|
||||||
export const openState = state => state.noteableData.state;
|
export const openState = state => state.noteableData.state;
|
||||||
|
|
||||||
export const getUserData = state => state.userData || {};
|
export const getUserData = state => state.userData || {};
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Extract DiscussionNotes component from NoteableDiscussion
|
||||||
|
merge_request: 27066
|
||||||
|
author:
|
||||||
|
type: other
|
|
@ -24,6 +24,7 @@ module.exports = {
|
||||||
'^helpers(/.*)$': '<rootDir>/spec/frontend/helpers$1',
|
'^helpers(/.*)$': '<rootDir>/spec/frontend/helpers$1',
|
||||||
'^vendor(/.*)$': '<rootDir>/vendor/assets/javascripts$1',
|
'^vendor(/.*)$': '<rootDir>/vendor/assets/javascripts$1',
|
||||||
'\\.(jpg|jpeg|png|svg)$': '<rootDir>/spec/frontend/__mocks__/file_mock.js',
|
'\\.(jpg|jpeg|png|svg)$': '<rootDir>/spec/frontend/__mocks__/file_mock.js',
|
||||||
|
'emojis(/.*).json': '<rootDir>/fixtures/emojis$1.json',
|
||||||
},
|
},
|
||||||
collectCoverageFrom: ['<rootDir>/app/assets/javascripts/**/*.{js,vue}'],
|
collectCoverageFrom: ['<rootDir>/app/assets/javascripts/**/*.{js,vue}'],
|
||||||
coverageDirectory: '<rootDir>/coverage-frontend/',
|
coverageDirectory: '<rootDir>/coverage-frontend/',
|
||||||
|
|
139
spec/frontend/notes/components/discussion_notes_spec.js
Normal file
139
spec/frontend/notes/components/discussion_notes_spec.js
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
import { mount, createLocalVue } from '@vue/test-utils';
|
||||||
|
import '~/behaviors/markdown/render_gfm';
|
||||||
|
import { SYSTEM_NOTE } from '~/notes/constants';
|
||||||
|
import DiscussionNotes from '~/notes/components/discussion_notes.vue';
|
||||||
|
import NoteableNote from '~/notes/components/noteable_note.vue';
|
||||||
|
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 TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
|
||||||
|
import createStore from '~/notes/stores';
|
||||||
|
import {
|
||||||
|
noteableDataMock,
|
||||||
|
discussionMock,
|
||||||
|
notesDataMock,
|
||||||
|
} from '../../../javascripts/notes/mock_data';
|
||||||
|
|
||||||
|
const localVue = createLocalVue();
|
||||||
|
|
||||||
|
describe('DiscussionNotes', () => {
|
||||||
|
let wrapper;
|
||||||
|
|
||||||
|
const createComponent = props => {
|
||||||
|
const store = createStore();
|
||||||
|
store.dispatch('setNoteableData', noteableDataMock);
|
||||||
|
store.dispatch('setNotesData', notesDataMock);
|
||||||
|
|
||||||
|
wrapper = mount(DiscussionNotes, {
|
||||||
|
localVue,
|
||||||
|
store,
|
||||||
|
propsData: {
|
||||||
|
discussion: discussionMock,
|
||||||
|
isExpanded: false,
|
||||||
|
shouldGroupReplies: false,
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
scopedSlots: {
|
||||||
|
footer: '<p slot-scope="{ showReplies }">showReplies:{{showReplies}}</p>',
|
||||||
|
},
|
||||||
|
slots: {
|
||||||
|
'avatar-badge': '<span class="avatar-badge-slot-content" />',
|
||||||
|
},
|
||||||
|
sync: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
wrapper.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('rendering', () => {
|
||||||
|
it('renders an element for each note in the discussion', () => {
|
||||||
|
createComponent();
|
||||||
|
const notesCount = discussionMock.notes.length;
|
||||||
|
const els = wrapper.findAll(TimelineEntryItem);
|
||||||
|
expect(els.length).toBe(notesCount);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders one element if replies groupping is enabled', () => {
|
||||||
|
createComponent({ shouldGroupReplies: true });
|
||||||
|
const els = wrapper.findAll(TimelineEntryItem);
|
||||||
|
expect(els.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses proper component to render each note type', () => {
|
||||||
|
const discussion = { ...discussionMock };
|
||||||
|
const notesData = [
|
||||||
|
// PlaceholderSystemNote
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
isPlaceholderNote: true,
|
||||||
|
placeholderType: SYSTEM_NOTE,
|
||||||
|
notes: [{ body: 'PlaceholderSystemNote' }],
|
||||||
|
},
|
||||||
|
// PlaceholderNote
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
isPlaceholderNote: true,
|
||||||
|
notes: [{ body: 'PlaceholderNote' }],
|
||||||
|
},
|
||||||
|
// SystemNote
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
system: true,
|
||||||
|
note: 'SystemNote',
|
||||||
|
},
|
||||||
|
// NoteableNote
|
||||||
|
discussion.notes[0],
|
||||||
|
];
|
||||||
|
discussion.notes = notesData;
|
||||||
|
createComponent({ discussion });
|
||||||
|
const notes = wrapper.findAll('.notes > li');
|
||||||
|
|
||||||
|
expect(notes.at(0).is(PlaceholderSystemNote)).toBe(true);
|
||||||
|
expect(notes.at(1).is(PlaceholderNote)).toBe(true);
|
||||||
|
expect(notes.at(2).is(SystemNote)).toBe(true);
|
||||||
|
expect(notes.at(3).is(NoteableNote)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders footer scoped slot with showReplies === true when expanded', () => {
|
||||||
|
createComponent({ isExpanded: true });
|
||||||
|
expect(wrapper.text()).toMatch('showReplies:true');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders footer scoped slot with showReplies === false when collapsed', () => {
|
||||||
|
createComponent({ isExpanded: false });
|
||||||
|
expect(wrapper.text()).toMatch('showReplies:false');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes down avatar-badge slot content', () => {
|
||||||
|
createComponent();
|
||||||
|
expect(wrapper.find('.avatar-badge-slot-content').exists()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('componentData', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
createComponent();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return first note object for placeholder note', () => {
|
||||||
|
const data = {
|
||||||
|
isPlaceholderNote: true,
|
||||||
|
notes: [{ body: 'hello world!' }],
|
||||||
|
};
|
||||||
|
const note = wrapper.vm.componentData(data);
|
||||||
|
|
||||||
|
expect(note).toEqual(data.notes[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return given note for nonplaceholder notes', () => {
|
||||||
|
const data = {
|
||||||
|
notes: [{ id: 12 }],
|
||||||
|
};
|
||||||
|
const note = wrapper.vm.componentData(data);
|
||||||
|
|
||||||
|
expect(note).toEqual(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,90 +1,103 @@
|
||||||
import Vue from 'vue';
|
import { mount, createLocalVue } from '@vue/test-utils';
|
||||||
import DiffDiscussions from '~/diffs/components/diff_discussions.vue';
|
import DiffDiscussions from '~/diffs/components/diff_discussions.vue';
|
||||||
|
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
|
||||||
|
import NoteableDiscussion from '~/notes/components/noteable_discussion.vue';
|
||||||
|
import DiscussionNotes from '~/notes/components/discussion_notes.vue';
|
||||||
|
import Icon from '~/vue_shared/components/icon.vue';
|
||||||
import { createStore } from '~/mr_notes/stores';
|
import { createStore } from '~/mr_notes/stores';
|
||||||
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
|
||||||
import '~/behaviors/markdown/render_gfm';
|
import '~/behaviors/markdown/render_gfm';
|
||||||
import discussionsMockData from '../mock_data/diff_discussions';
|
import discussionsMockData from '../mock_data/diff_discussions';
|
||||||
|
|
||||||
|
const localVue = createLocalVue();
|
||||||
|
|
||||||
describe('DiffDiscussions', () => {
|
describe('DiffDiscussions', () => {
|
||||||
let vm;
|
let store;
|
||||||
|
let wrapper;
|
||||||
const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)];
|
const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)];
|
||||||
|
|
||||||
function createComponent(props = {}) {
|
const createComponent = props => {
|
||||||
const store = createStore();
|
store = createStore();
|
||||||
|
wrapper = mount(localVue.extend(DiffDiscussions), {
|
||||||
vm = createComponentWithStore(Vue.extend(DiffDiscussions), store, {
|
store,
|
||||||
|
propsData: {
|
||||||
discussions: getDiscussionsMockData(),
|
discussions: getDiscussionsMockData(),
|
||||||
...props,
|
...props,
|
||||||
}).$mount();
|
},
|
||||||
}
|
localVue,
|
||||||
|
sync: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vm.$destroy();
|
wrapper.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('template', () => {
|
describe('template', () => {
|
||||||
it('should have notes list', () => {
|
it('should have notes list', () => {
|
||||||
createComponent();
|
createComponent();
|
||||||
|
|
||||||
expect(vm.$el.querySelectorAll('.discussion .note.timeline-entry').length).toEqual(5);
|
expect(wrapper.find(NoteableDiscussion).exists()).toBe(true);
|
||||||
|
expect(wrapper.find(DiscussionNotes).exists()).toBe(true);
|
||||||
|
expect(wrapper.find(DiscussionNotes).findAll(TimelineEntryItem).length).toBe(
|
||||||
|
discussionsMockData.notes.length,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('image commenting', () => {
|
describe('image commenting', () => {
|
||||||
|
const findDiffNotesToggle = () => wrapper.find('.js-diff-notes-toggle');
|
||||||
|
|
||||||
it('renders collapsible discussion button', () => {
|
it('renders collapsible discussion button', () => {
|
||||||
createComponent({ shouldCollapseDiscussions: true });
|
createComponent({ shouldCollapseDiscussions: true });
|
||||||
|
const diffNotesToggle = findDiffNotesToggle();
|
||||||
|
|
||||||
expect(vm.$el.querySelector('.js-diff-notes-toggle')).not.toBe(null);
|
expect(diffNotesToggle.exists()).toBe(true);
|
||||||
expect(vm.$el.querySelector('.js-diff-notes-toggle svg')).not.toBe(null);
|
expect(diffNotesToggle.find(Icon).exists()).toBe(true);
|
||||||
expect(vm.$el.querySelector('.js-diff-notes-toggle').classList).toContain(
|
expect(diffNotesToggle.classes('diff-notes-collapse')).toBe(true);
|
||||||
'diff-notes-collapse',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('dispatches toggleDiscussion when clicking collapse button', () => {
|
it('dispatches toggleDiscussion when clicking collapse button', () => {
|
||||||
createComponent({ shouldCollapseDiscussions: true });
|
createComponent({ shouldCollapseDiscussions: true });
|
||||||
|
spyOn(wrapper.vm.$store, 'dispatch').and.stub();
|
||||||
|
const diffNotesToggle = findDiffNotesToggle();
|
||||||
|
diffNotesToggle.trigger('click');
|
||||||
|
|
||||||
spyOn(vm.$store, 'dispatch').and.stub();
|
expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('toggleDiscussion', {
|
||||||
|
discussionId: discussionsMockData.id,
|
||||||
vm.$el.querySelector('.js-diff-notes-toggle').click();
|
|
||||||
|
|
||||||
expect(vm.$store.dispatch).toHaveBeenCalledWith('toggleDiscussion', {
|
|
||||||
discussionId: vm.discussions[0].id,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders expand button when discussion is collapsed', done => {
|
it('renders expand button when discussion is collapsed', () => {
|
||||||
createComponent({ shouldCollapseDiscussions: true });
|
const discussions = getDiscussionsMockData();
|
||||||
|
discussions[0].expanded = false;
|
||||||
|
createComponent({ discussions, shouldCollapseDiscussions: true });
|
||||||
|
const diffNotesToggle = findDiffNotesToggle();
|
||||||
|
|
||||||
vm.discussions[0].expanded = false;
|
expect(diffNotesToggle.text().trim()).toBe('1');
|
||||||
|
expect(diffNotesToggle.classes()).toEqual(
|
||||||
vm.$nextTick(() => {
|
jasmine.arrayContaining(['btn-transparent', 'badge', 'badge-pill']),
|
||||||
expect(vm.$el.querySelector('.js-diff-notes-toggle').textContent.trim()).toBe('1');
|
|
||||||
expect(vm.$el.querySelector('.js-diff-notes-toggle').className).toContain(
|
|
||||||
'btn-transparent badge badge-pill',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hides discussion when collapsed', done => {
|
it('hides discussion when collapsed', () => {
|
||||||
createComponent({ shouldCollapseDiscussions: true });
|
const discussions = getDiscussionsMockData();
|
||||||
|
discussions[0].expanded = false;
|
||||||
|
createComponent({ discussions, shouldCollapseDiscussions: true });
|
||||||
|
|
||||||
vm.discussions[0].expanded = false;
|
expect(wrapper.find(NoteableDiscussion).isVisible()).toBe(false);
|
||||||
|
|
||||||
vm.$nextTick(() => {
|
|
||||||
expect(vm.$el.querySelector('.note-discussion').style.display).toBe('none');
|
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders badge on avatar', () => {
|
it('renders badge on avatar', () => {
|
||||||
createComponent({ renderAvatarBadge: true, discussions: [{ ...discussionsMockData }] });
|
createComponent({ renderAvatarBadge: true });
|
||||||
|
const noteableDiscussion = wrapper.find(NoteableDiscussion);
|
||||||
|
|
||||||
expect(vm.$el.querySelector('.user-avatar-link .badge-pill')).not.toBe(null);
|
expect(noteableDiscussion.find('.badge-pill').exists()).toBe(true);
|
||||||
expect(vm.$el.querySelector('.user-avatar-link .badge-pill').textContent.trim()).toBe('1');
|
expect(
|
||||||
|
noteableDiscussion
|
||||||
|
.find('.badge-pill')
|
||||||
|
.text()
|
||||||
|
.trim(),
|
||||||
|
).toBe('1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -130,29 +130,6 @@ describe('noteable_discussion component', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('componentData', () => {
|
|
||||||
it('should return first note object for placeholder note', () => {
|
|
||||||
const data = {
|
|
||||||
isPlaceholderNote: true,
|
|
||||||
notes: [{ body: 'hello world!' }],
|
|
||||||
};
|
|
||||||
|
|
||||||
const note = wrapper.vm.componentData(data);
|
|
||||||
|
|
||||||
expect(note).toEqual(data.notes[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return given note for nonplaceholder notes', () => {
|
|
||||||
const data = {
|
|
||||||
notes: [{ id: 12 }],
|
|
||||||
};
|
|
||||||
|
|
||||||
const note = wrapper.vm.componentData(data);
|
|
||||||
|
|
||||||
expect(note).toEqual(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('action text', () => {
|
describe('action text', () => {
|
||||||
const commitId = 'razupaltuff';
|
const commitId = 'razupaltuff';
|
||||||
const truncatedCommitId = commitId.substr(0, 8);
|
const truncatedCommitId = commitId.substr(0, 8);
|
||||||
|
|
Loading…
Reference in a new issue