Commit fdd25500 authored by Phil Hughes's avatar Phil Hughes

Move frontend batch comments code into core

This moves all the frontend batch comments related code
out of the EE folder and into core

Part of https://gitlab.com/gitlab-org/gitlab/-/issues/28154
parent 1e7b5fcb
import Vue from 'vue';
import { mapActions } from 'vuex';
import store from '~/mr_notes/stores';
import ReviewBar from './components/review_bar.vue';
// eslint-disable-next-line import/prefer-default-export
export const initReviewBar = () => {
const el = document.getElementById('js-review-bar');
// eslint-disable-next-line no-new
new Vue({
el,
store,
mounted() {
this.fetchDrafts();
},
methods: {
...mapActions('batchComments', ['fetchDrafts']),
},
render(createElement) {
return createElement(ReviewBar);
},
});
};
import { sprintf, __ } from '~/locale'; import { mapGetters } from 'vuex';
import { sprintf, s__, __ } from '~/locale';
export default { export default {
props: {
discussionId: {
type: String,
required: false,
default: null,
},
resolveDiscussion: {
type: Boolean,
required: false,
default: false,
},
isDraft: {
type: Boolean,
required: false,
default: false,
},
},
computed: { computed: {
...mapGetters(['isDiscussionResolved']),
resolvedStatusMessage() {
let message;
const discussionResolved = this.isDiscussionResolved(
this.draft ? this.draft.discussion_id : this.discussionId,
);
const discussionToBeResolved = this.draft
? this.draft.resolve_discussion
: this.resolveDiscussion;
if (discussionToBeResolved && discussionResolved && !this.$options.showStaysResolved) {
return undefined;
}
if (discussionToBeResolved) {
message = discussionResolved
? s__('MergeRequests|Thread stays resolved')
: s__('MergeRequests|Thread will be resolved');
} else if (discussionResolved) {
message = s__('MergeRequests|Thread will be unresolved');
} else if (this.$options.showStaysResolved) {
message = s__('MergeRequests|Thread stays unresolved');
}
return message;
},
componentClasses() {
return this.resolveDiscussion ? 'is-resolving-discussion' : 'is-unresolving-discussion';
},
resolveButtonTitle() { resolveButtonTitle() {
let title = __('Mark comment as resolved'); if (this.isDraft || this.discussionId) return this.resolvedStatusMessage;
let title = __('Mark as resolved');
if (this.resolvedBy) { if (this.resolvedBy) {
title = sprintf(__('Resolved by %{name}'), { name: this.resolvedBy.name }); title = sprintf(__('Resolved by %{name}'), { name: this.resolvedBy.name });
...@@ -12,4 +61,5 @@ export default { ...@@ -12,4 +61,5 @@ export default {
return title; return title;
}, },
}, },
showStaysResolved: true,
}; };
...@@ -5,10 +5,6 @@ import service from '../../../services/drafts_service'; ...@@ -5,10 +5,6 @@ import service from '../../../services/drafts_service';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { CHANGES_TAB, DISCUSSION_TAB, SHOW_TAB } from '../../../constants'; import { CHANGES_TAB, DISCUSSION_TAB, SHOW_TAB } from '../../../constants';
export const enableBatchComments = ({ commit }) => {
commit(types.ENABLE_BATCH_COMMENTS);
};
export const saveDraft = ({ dispatch }, draft) => export const saveDraft = ({ dispatch }, draft) =>
dispatch('saveNote', { ...draft, isDraft: true }, { root: true }); dispatch('saveNote', { ...draft, isDraft: true }, { root: true });
......
...@@ -6,10 +6,6 @@ const processDraft = draft => ({ ...@@ -6,10 +6,6 @@ const processDraft = draft => ({
}); });
export default { export default {
[types.ENABLE_BATCH_COMMENTS](state) {
state.withBatchComments = true;
},
[types.ADD_NEW_DRAFT](state, draft) { [types.ADD_NEW_DRAFT](state, draft) {
state.drafts.push(processDraft(draft)); state.drafts.push(processDraft(draft));
}, },
......
export default () => ({ export default () => ({
withBatchComments: false, withBatchComments: true,
isDraftsFetched: false, isDraftsFetched: false,
drafts: [], drafts: [],
isPublishing: false, isPublishing: false,
......
<script> <script>
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon } from '@gitlab/ui';
import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form'; import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
import draftCommentsMixin from 'ee_else_ce/diffs/mixins/draft_comments'; import draftCommentsMixin from '~/diffs/mixins/draft_comments';
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue'; import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_diffable.vue'; import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_diffable.vue';
import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_preview.vue'; import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_preview.vue';
import DiffFileDrafts from '~/batch_comments/components/diff_file_drafts.vue';
import InlineDiffView from './inline_diff_view.vue'; import InlineDiffView from './inline_diff_view.vue';
import ParallelDiffView from './parallel_diff_view.vue'; import ParallelDiffView from './parallel_diff_view.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
...@@ -29,7 +30,7 @@ export default { ...@@ -29,7 +30,7 @@ export default {
NotDiffableViewer, NotDiffableViewer,
NoPreviewViewer, NoPreviewViewer,
userAvatarLink, userAvatarLink,
DiffFileDrafts: () => import('ee_component/batch_comments/components/diff_file_drafts.vue'), DiffFileDrafts,
}, },
mixins: [diffLineNoteFormMixin, draftCommentsMixin], mixins: [diffLineNoteFormMixin, draftCommentsMixin],
props: { props: {
......
<script> <script>
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form'; import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import noteForm from '../../notes/components/note_form.vue'; import noteForm from '../../notes/components/note_form.vue';
import autosave from '../../notes/mixins/autosave'; import autosave from '../../notes/mixins/autosave';
......
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import draftCommentsMixin from 'ee_else_ce/diffs/mixins/draft_comments'; import draftCommentsMixin from '~/diffs/mixins/draft_comments';
import InlineDraftCommentRow from '~/batch_comments/components/inline_draft_comment_row.vue';
import inlineDiffTableRow from './inline_diff_table_row.vue'; import inlineDiffTableRow from './inline_diff_table_row.vue';
import inlineDiffCommentRow from './inline_diff_comment_row.vue'; import inlineDiffCommentRow from './inline_diff_comment_row.vue';
import inlineDiffExpansionRow from './inline_diff_expansion_row.vue'; import inlineDiffExpansionRow from './inline_diff_expansion_row.vue';
...@@ -9,8 +10,7 @@ export default { ...@@ -9,8 +10,7 @@ export default {
components: { components: {
inlineDiffCommentRow, inlineDiffCommentRow,
inlineDiffTableRow, inlineDiffTableRow,
InlineDraftCommentRow: () => InlineDraftCommentRow,
import('ee_component/batch_comments/components/inline_draft_comment_row.vue'),
inlineDiffExpansionRow, inlineDiffExpansionRow,
}, },
mixins: [draftCommentsMixin], mixins: [draftCommentsMixin],
......
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import draftCommentsMixin from 'ee_else_ce/diffs/mixins/draft_comments'; import draftCommentsMixin from '~/diffs/mixins/draft_comments';
import ParallelDraftCommentRow from '~/batch_comments/components/parallel_draft_comment_row.vue';
import parallelDiffTableRow from './parallel_diff_table_row.vue'; import parallelDiffTableRow from './parallel_diff_table_row.vue';
import parallelDiffCommentRow from './parallel_diff_comment_row.vue'; import parallelDiffCommentRow from './parallel_diff_comment_row.vue';
import parallelDiffExpansionRow from './parallel_diff_expansion_row.vue'; import parallelDiffExpansionRow from './parallel_diff_expansion_row.vue';
...@@ -10,8 +11,7 @@ export default { ...@@ -10,8 +11,7 @@ export default {
parallelDiffExpansionRow, parallelDiffExpansionRow,
parallelDiffTableRow, parallelDiffTableRow,
parallelDiffCommentRow, parallelDiffCommentRow,
ParallelDraftCommentRow: () => ParallelDraftCommentRow,
import('ee_component/batch_comments/components/parallel_draft_comment_row.vue'),
}, },
mixins: [draftCommentsMixin], mixins: [draftCommentsMixin],
props: { props: {
......
import { mapGetters } from 'vuex';
export default { export default {
computed: { computed: {
shouldRenderDraftRow: () => () => false, ...mapGetters('batchComments', [
shouldRenderParallelDraftRow: () => () => false, 'shouldRenderDraftRow',
draftForLine: () => () => ({}), 'shouldRenderParallelDraftRow',
'draftForLine',
'draftsForFile',
'hasParallelDraftLeft',
'hasParallelDraftRight',
]),
imageDiscussions() { imageDiscussions() {
return this.diffFile.discussions; return this.diffFile.discussions.concat(this.draftsForFile(this.diffFile.file_hash));
}, },
hasParallelDraftLeft: () => () => false,
hasParallelDraftRight: () => () => false,
}, },
}; };
import Vue from 'vue'; import Vue from 'vue';
import store from 'ee_else_ce/mr_notes/stores'; import store from '~/mr_notes/stores';
import initNotesApp from './init_notes'; import initNotesApp from './init_notes';
import initDiffsApp from '../diffs'; import initDiffsApp from '../diffs';
import discussionCounter from '../notes/components/discussion_counter.vue'; import discussionCounter from '../notes/components/discussion_counter.vue';
......
import $ from 'jquery'; import $ from 'jquery';
import Vue from 'vue'; import Vue from 'vue';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import store from 'ee_else_ce/mr_notes/stores'; import store from '~/mr_notes/stores';
import notesApp from '../notes/components/notes_app.vue'; import notesApp from '../notes/components/notes_app.vue';
import discussionKeyboardNavigator from '../notes/components/discussion_keyboard_navigator.vue'; import discussionKeyboardNavigator from '../notes/components/discussion_keyboard_navigator.vue';
import initWidget from '../vue_merge_request_widget'; import initWidget from '../vue_merge_request_widget';
......
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import batchCommentsModule from '~/batch_comments/stores/modules/batch_comments';
import notesModule from '~/notes/stores/modules'; import notesModule from '~/notes/stores/modules';
import diffsModule from '~/diffs/store/modules'; import diffsModule from '~/diffs/store/modules';
import mrPageModule from './modules'; import mrPageModule from './modules';
...@@ -12,6 +13,7 @@ export const createStore = () => ...@@ -12,6 +13,7 @@ export const createStore = () =>
page: mrPageModule(), page: mrPageModule(),
notes: notesModule(), notes: notesModule(),
diffs: diffsModule(), diffs: diffsModule(),
batchComments: batchCommentsModule(),
}, },
}); });
......
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui'; import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import resolvedStatusMixin from 'ee_else_ce/batch_comments/mixins/resolved_status'; import resolvedStatusMixin from '~/batch_comments/mixins/resolved_status';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import ReplyButton from './note_actions/reply_button.vue'; import ReplyButton from './note_actions/reply_button.vue';
......
<script> <script>
import { mapActions } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import $ from 'jquery'; import $ from 'jquery';
import '~/behaviors/markdown/render_gfm'; import '~/behaviors/markdown/render_gfm';
import getDiscussion from 'ee_else_ce/notes/mixins/get_discussion';
import noteEditedText from './note_edited_text.vue'; import noteEditedText from './note_edited_text.vue';
import noteAwardsList from './note_awards_list.vue'; import noteAwardsList from './note_awards_list.vue';
import noteAttachment from './note_attachment.vue'; import noteAttachment from './note_attachment.vue';
...@@ -18,7 +17,7 @@ export default { ...@@ -18,7 +17,7 @@ export default {
noteForm, noteForm,
Suggestions, Suggestions,
}, },
mixins: [autosave, getDiscussion], mixins: [autosave],
props: { props: {
note: { note: {
type: Object, type: Object,
...@@ -45,6 +44,12 @@ export default { ...@@ -45,6 +44,12 @@ export default {
}, },
}, },
computed: { computed: {
...mapGetters(['getDiscussion']),
discussion() {
if (!this.note.isDraft) return {};
return this.getDiscussion(this.note.discussion_id);
},
noteBody() { noteBody() {
return this.note.note; return this.note.note;
}, },
......
<script> <script>
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions, mapState } from 'vuex';
import noteFormMixin from 'ee_else_ce/notes/mixins/note_form';
import { mergeUrlParams } from '~/lib/utils/url_utility'; import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue'; import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
...@@ -16,7 +15,7 @@ export default { ...@@ -16,7 +15,7 @@ export default {
issueWarning, issueWarning,
markdownField, markdownField,
}, },
mixins: [issuableStateMixin, resolvable, noteFormMixin], mixins: [issuableStateMixin, resolvable],
props: { props: {
noteBody: { noteBody: {
type: String, type: String,
...@@ -82,6 +81,11 @@ export default { ...@@ -82,6 +81,11 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
isDraft: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
let updatedNoteBody = this.noteBody; let updatedNoteBody = this.noteBody;
...@@ -107,6 +111,16 @@ export default { ...@@ -107,6 +111,16 @@ export default {
'getNotesDataByProp', 'getNotesDataByProp',
'getUserDataByProp', 'getUserDataByProp',
]), ]),
...mapState({
withBatchComments: state => state.batchComments?.withBatchComments,
}),
...mapGetters('batchComments', ['hasDrafts']),
showBatchCommentsActions() {
return this.withBatchComments && this.noteId === '' && !this.discussion.for_commit;
},
showResolveDiscussionToggle() {
return (this.discussion?.id && this.discussion.resolvable) || this.isDraft;
},
noteHash() { noteHash() {
if (this.noteId) { if (this.noteId) {
return `#note_${this.noteId}`; return `#note_${this.noteId}`;
...@@ -202,8 +216,6 @@ export default { ...@@ -202,8 +216,6 @@ export default {
methods: { methods: {
...mapActions(['toggleResolveNote']), ...mapActions(['toggleResolveNote']),
shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState) { shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState) {
// shouldBeResolved() checks the actual resolution state,
// considering batchComments (EEP), if applicable/enabled.
const newResolvedStateAfterUpdate = const newResolvedStateAfterUpdate =
this.shouldBeResolved && this.shouldBeResolved(shouldResolve); this.shouldBeResolved && this.shouldBeResolved(shouldResolve);
...@@ -234,6 +246,50 @@ export default { ...@@ -234,6 +246,50 @@ export default {
updateDraft(autosaveKey, text); updateDraft(autosaveKey, text);
} }
}, },
handleKeySubmit() {
if (this.showBatchCommentsActions) {
this.handleAddToReview();
} else {
this.handleUpdate();
}
},
handleUpdate(shouldResolve) {
const beforeSubmitDiscussionState = this.discussionResolved;
this.isSubmitting = true;
this.$emit(
'handleFormUpdate',
this.updatedNoteBody,
this.$refs.editNoteForm,
() => {
this.isSubmitting = false;
if (this.shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState)) {
this.resolveHandler(beforeSubmitDiscussionState);
}
},
this.discussionResolved ? !this.isUnresolving : this.isResolving,
);
},
shouldBeResolved(resolveStatus) {
if (this.withBatchComments) {
return (
(this.discussionResolved && !this.isUnresolving) ||
(!this.discussionResolved && this.isResolving)
);
}
return resolveStatus;
},
handleAddToReview() {
// check if draft should resolve thread
const shouldResolve =
(this.discussionResolved && !this.isUnresolving) ||
(!this.discussionResolved && this.isResolving);
this.isSubmitting = true;
this.$emit('handleFormUpdateAddToReview', this.updatedNoteBody, shouldResolve);
},
}, },
}; };
</script> </script>
...@@ -293,6 +349,7 @@ export default { ...@@ -293,6 +349,7 @@ export default {
<input <input
v-model="isUnresolving" v-model="isUnresolving"
type="checkbox" type="checkbox"
class="js-unresolve-checkbox"
data-qa-selector="unresolve_review_discussion_checkbox" data-qa-selector="unresolve_review_discussion_checkbox"
/> />
{{ __('Unresolve thread') }} {{ __('Unresolve thread') }}
...@@ -301,6 +358,7 @@ export default { ...@@ -301,6 +358,7 @@ export default {
<input <input
v-model="isResolving" v-model="isResolving"
type="checkbox" type="checkbox"
class="js-resolve-checkbox"
data-qa-selector="resolve_review_discussion_checkbox" data-qa-selector="resolve_review_discussion_checkbox"
/> />
{{ __('Resolve thread') }} {{ __('Resolve thread') }}
...@@ -320,7 +378,7 @@ export default { ...@@ -320,7 +378,7 @@ export default {
<button <button
:disabled="isDisabled" :disabled="isDisabled"
type="button" type="button"
class="btn qa-comment-now" class="btn qa-comment-now js-comment-button"
@click="handleUpdate()" @click="handleUpdate()"
> >
{{ __('Add comment now') }} {{ __('Add comment now') }}
......
<script> <script>
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui'; import { GlTooltipDirective } from '@gitlab/ui';
import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form'; import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import { clearDraft, getDiscussionReplyKey } from '~/lib/utils/autosave'; import { clearDraft, getDiscussionReplyKey } from '~/lib/utils/autosave';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import DraftNote from '~/batch_comments/components/draft_note.vue';
import Flash from '../../flash'; import Flash from '../../flash';
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 diffDiscussionHeader from './diff_discussion_header.vue'; import diffDiscussionHeader from './diff_discussion_header.vue';
...@@ -26,7 +27,7 @@ export default { ...@@ -26,7 +27,7 @@ export default {
diffDiscussionHeader, diffDiscussionHeader,
noteSignedOutWidget, noteSignedOutWidget,
noteForm, noteForm,
DraftNote: () => import('ee_component/batch_comments/components/draft_note.vue'), DraftNote,
TimelineEntryItem, TimelineEntryItem,
DiscussionNotes, DiscussionNotes,
DiscussionActions, DiscussionActions,
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
import $ from 'jquery'; import $ from 'jquery';
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import { escape } from 'lodash'; import { escape } from 'lodash';
import draftMixin from 'ee_else_ce/notes/mixins/draft';
import { truncateSha } from '~/lib/utils/text_utility'; import { truncateSha } from '~/lib/utils/text_utility';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import { __, s__, sprintf } from '../../locale'; import { __, s__, sprintf } from '../../locale';
...@@ -25,7 +24,7 @@ export default { ...@@ -25,7 +24,7 @@ export default {
NoteBody, NoteBody,
TimelineEntryItem, TimelineEntryItem,
}, },
mixins: [noteable, resolvable, draftMixin], mixins: [noteable, resolvable],
props: { props: {
note: { note: {
type: Object, type: Object,
...@@ -105,6 +104,15 @@ export default { ...@@ -105,6 +104,15 @@ export default {
)}</a>`; )}</a>`;
return sprintf(s__('MergeRequests|commented on commit %{commitLink}'), { commitLink }, false); return sprintf(s__('MergeRequests|commented on commit %{commitLink}'), { commitLink }, false);
}, },
isDraft() {
return this.note.isDraft;
},
canResolve() {
return (
this.note.current_user.can_resolve ||
(this.note.isDraft && this.note.discussion_id !== null)
);
},
}, },
created() { created() {
......
import { mapActions, mapGetters, mapState } from 'vuex';
import { getDraftReplyFormData, getDraftFormData } from '~/batch_comments/utils';
import { TEXT_DIFF_POSITION_TYPE, IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import { clearDraft } from '~/lib/utils/autosave';
export default { export default {
computed: { computed: {
draftForDiscussion: () => () => ({}), ...mapState({
noteableData: state => state.notes.noteableData,
notesData: state => state.notes.notesData,
withBatchComments: state => state.batchComments?.withBatchComments,
}),
...mapGetters('diffs', ['getDiffFileByHash']),
...mapGetters('batchComments', ['shouldRenderDraftRowInDiscussion', 'draftForDiscussion']),
...mapState('diffs', ['commit']),
}, },
methods: { methods: {
showDraft: () => false, ...mapActions('diffs', ['cancelCommentForm']),
addReplyToReview: () => {}, ...mapActions('batchComments', ['addDraftToReview', 'saveDraft', 'insertDraftIntoDrafts']),
addToReview: () => {}, addReplyToReview(noteText, isResolving) {
const postData = getDraftReplyFormData({
in_reply_to_discussion_id: this.discussion.reply_id,
target_type: this.getNoteableData.targetType,
notesData: this.notesData,
draft_note: {
note: noteText,
resolve_discussion: isResolving,
},
});
if (this.discussion.for_commit) {
postData.note_project_id = this.discussion.project_id;
}
this.isReplying = false;
this.saveDraft(postData)
.then(() => {
this.handleClearForm(this.discussion.line_code);
})
.catch(() => {
createFlash(s__('MergeRequests|An error occurred while saving the draft comment.'));
});
},
addToReview(note) {
const positionType = this.diffFileCommentForm
? IMAGE_DIFF_POSITION_TYPE
: TEXT_DIFF_POSITION_TYPE;
const selectedDiffFile = this.getDiffFileByHash(this.diffFileHash);
const postData = getDraftFormData({
note,
notesData: this.notesData,
noteableData: this.noteableData,
noteableType: this.noteableType,
noteTargetLine: this.noteTargetLine,
diffViewType: this.diffViewType,
diffFile: selectedDiffFile,
linePosition: this.position,
positionType,
...this.diffFileCommentForm,
});
const diffFileHeadSha = this.commit && this?.diffFile?.diff_refs?.head_sha;
postData.data.note.commit_id = diffFileHeadSha || null;
return this.saveDraft(postData)
.then(() => {
if (positionType === IMAGE_DIFF_POSITION_TYPE) {
this.closeDiffFileCommentForm(this.diffFileHash);
} else {
this.handleClearForm(this.line.line_code);
}
})
.catch(() => {
createFlash(s__('MergeRequests|An error occurred while saving the draft comment.'));
});
},
handleClearForm(lineCode) {
this.cancelCommentForm({
lineCode,
fileHash: this.diffFileHash,
});
this.$nextTick(() => {
if (this.autosaveKey) {
clearDraft(this.autosaveKey);
} else {
// TODO: remove the following after replacing the autosave mixin
// https://gitlab.com/gitlab-org/gitlab-foss/issues/60587
this.resetAutoSave();
}
});
},
showDraft(replyId) {
return this.withBatchComments && this.shouldRenderDraftRowInDiscussion(replyId);
},
}, },
}; };
export default {
computed: {
isDraft: () => false,
canResolve() {
return this.note.current_user.can_resolve;
},
},
};
export default {
computed: {
discussion() {
return {};
},
},
};
export default {
data() {
return {
showBatchCommentsActions: false,
};
},
methods: {
handleKeySubmit() {
this.handleUpdate();
},
handleUpdate(shouldResolve) {
const beforeSubmitDiscussionState = this.discussionResolved;
this.isSubmitting = true;
this.$emit('handleFormUpdate', this.updatedNoteBody, this.$refs.editNoteForm, () => {
this.isSubmitting = false;
if (this.shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState)) {
this.resolveHandler(beforeSubmitDiscussionState);
}
});
},
},
};
import initMrNotes from '~/mr_notes'; import initMrNotes from '~/mr_notes';
import { initReviewBar } from '~/batch_comments';
import initSidebarBundle from '~/sidebar/sidebar_bundle'; import initSidebarBundle from '~/sidebar/sidebar_bundle';
import initShow from '../init_merge_request_show'; import initShow from '../init_merge_request_show';
...@@ -8,4 +9,5 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -8,4 +9,5 @@ document.addEventListener('DOMContentLoaded', () => {
initSidebarBundle(); initSidebarBundle();
} }
initMrNotes(); initMrNotes();
initReviewBar();
}); });
...@@ -169,7 +169,7 @@ module NotesHelper ...@@ -169,7 +169,7 @@ module NotesHelper
end end
def notes_data(issuable) def notes_data(issuable)
{ data = {
discussionsPath: discussions_path(issuable), discussionsPath: discussions_path(issuable),
registerPath: new_session_path(:user, redirect_to_referer: 'yes', anchor: 'register-pane'), registerPath: new_session_path(:user, redirect_to_referer: 'yes', anchor: 'register-pane'),
newSessionPath: new_session_path(:user, redirect_to_referer: 'yes'), newSessionPath: new_session_path(:user, redirect_to_referer: 'yes'),
...@@ -181,6 +181,16 @@ module NotesHelper ...@@ -181,6 +181,16 @@ module NotesHelper
prerenderedNotesCount: issuable.capped_notes_count(MAX_PRERENDERED_NOTES), prerenderedNotesCount: issuable.capped_notes_count(MAX_PRERENDERED_NOTES),
lastFetchedAt: Time.now.to_i lastFetchedAt: Time.now.to_i
} }
if issuable.is_a?(MergeRequest)
data.merge!(
draftsPath: project_merge_request_drafts_path(@project, issuable),
draftsPublishPath: publish_project_merge_request_drafts_path(@project, issuable),
draftsDiscardPath: discard_project_merge_request_drafts_path(@project, issuable)
)
end
data
end end
def discussion_resolved_intro(discussion) def discussion_resolved_intro(discussion)
......
...@@ -100,3 +100,5 @@ ...@@ -100,3 +100,5 @@
= render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title = render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title
- if @merge_request.can_be_cherry_picked? - if @merge_request.can_be_cherry_picked?
= render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title = render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title
#js-review-bar
---
title: Moves merge request reviews into Core
merge_request: 32558
author:
type: other
import Vue from 'vue';
import { mapState, mapActions } from 'vuex';
import store from 'ee_else_ce/mr_notes/stores';
import ReviewBar from './components/review_bar.vue';
// eslint-disable-next-line import/prefer-default-export
export const initReviewBar = () => {
const el = document.getElementById('js-review-bar');
if (el) {
// eslint-disable-next-line no-new
new Vue({
el,
store,
computed: {
...mapState('batchComments', ['withBatchComments']),
},
created() {
this.enableBatchComments();
},
mounted() {
this.fetchDrafts();
},
methods: {
...mapActions('batchComments', ['fetchDrafts', 'enableBatchComments']),
},
render(createElement) {
if (this.withBatchComments) {
return createElement(ReviewBar);
}
return null;
},
});
}
};
import { mapGetters } from 'vuex';
import { sprintf, s__, __ } from '~/locale';
export default {
props: {
discussionId: {
type: String,
required: false,
default: null,
},
resolveDiscussion: {
type: Boolean,
required: false,
default: false,
},
isDraft: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
...mapGetters(['isDiscussionResolved']),
resolvedStatusMessage() {
let message;
const discussionResolved = this.isDiscussionResolved(
this.draft ? this.draft.discussion_id : this.discussionId,
);
const discussionToBeResolved = this.draft
? this.draft.resolve_discussion
: this.resolveDiscussion;
if (discussionToBeResolved && discussionResolved && !this.$options.showStaysResolved) {
return undefined;
}
if (discussionToBeResolved) {
if (discussionResolved) {
message = s__('MergeRequests|Thread stays resolved');
} else {
message = s__('MergeRequests|Thread will be resolved');
}
} else if (discussionResolved) {
message = s__('MergeRequests|Thread will be unresolved');
} else if (this.$options.showStaysResolved) {
message = s__('MergeRequests|Thread stays unresolved');
}
return message;
},
componentClasses() {
return this.resolveDiscussion ? 'is-resolving-discussion' : 'is-unresolving-discussion';
},
resolveButtonTitle() {
if (this.isDraft || this.discussionId) return this.resolvedStatusMessage;
let title = __('Mark as resolved');
if (this.resolvedBy) {
title = sprintf(__('Resolved by %{name}'), { name: this.resolvedBy.name });
}
return title;
},
},
showStaysResolved: true,
};
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters('batchComments', [
'shouldRenderDraftRow',
'shouldRenderParallelDraftRow',
'draftForLine',
'draftsForFile',
'hasParallelDraftLeft',
'hasParallelDraftRight',
]),
imageDiscussions() {
return this.diffFile.discussions.concat(this.draftsForFile(this.diffFile.file_hash));
},
},
};
import Vue from 'vue';
import Vuex from 'vuex';
import batchCommentsModule from 'ee/batch_comments/stores/modules/batch_comments';
import notesModule from '~/notes/stores/modules';
import diffsModule from '~/diffs/store/modules';
import mrPageModule from '~/mr_notes/stores/modules';
Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
modules: {
page: mrPageModule(),
notes: notesModule(),
diffs: diffsModule(),
batchComments: batchCommentsModule(),
},
});
export default createStore();
import { mapActions, mapGetters, mapState } from 'vuex';
import { getDraftReplyFormData, getDraftFormData } from 'ee/batch_comments/utils';
import { TEXT_DIFF_POSITION_TYPE, IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import { clearDraft } from '~/lib/utils/autosave';
export default {
computed: {
...mapState({
noteableData: state => state.notes.noteableData,
notesData: state => state.notes.notesData,
withBatchComments: state => state.batchComments && state.batchComments.withBatchComments,
}),
...mapGetters('diffs', ['getDiffFileByHash']),
...mapGetters('batchComments', ['shouldRenderDraftRowInDiscussion', 'draftForDiscussion']),
...mapState('diffs', ['commit']),
},
methods: {
...mapActions('diffs', ['cancelCommentForm']),
...mapActions('batchComments', ['addDraftToReview', 'saveDraft', 'insertDraftIntoDrafts']),
addReplyToReview(noteText, isResolving) {
const postData = getDraftReplyFormData({
in_reply_to_discussion_id: this.discussion.reply_id,
target_type: this.getNoteableData.targetType,
notesData: this.notesData,
draft_note: {
note: noteText,
resolve_discussion: isResolving,
},
});
if (this.discussion.for_commit) {
postData.note_project_id = this.discussion.project_id;
}
this.isReplying = false;
this.saveDraft(postData)
.then(() => {
this.handleClearForm(this.discussion.line_code);
})
.catch(() => {
createFlash(s__('MergeRequests|An error occurred while saving the draft comment.'));
});
},
addToReview(note) {
const positionType = this.diffFileCommentForm
? IMAGE_DIFF_POSITION_TYPE
: TEXT_DIFF_POSITION_TYPE;
const selectedDiffFile = this.getDiffFileByHash(this.diffFileHash);
const postData = getDraftFormData({
note,
notesData: this.notesData,
noteableData: this.noteableData,
noteableType: this.noteableType,
noteTargetLine: this.noteTargetLine,
diffViewType: this.diffViewType,
diffFile: selectedDiffFile,
linePosition: this.position,
positionType,
...this.diffFileCommentForm,
});
const diffFileHeadSha = this.commit && this?.diffFile?.diff_refs?.head_sha;
postData.data.note.commit_id = diffFileHeadSha || null;
return this.saveDraft(postData)
.then(() => {
if (positionType === IMAGE_DIFF_POSITION_TYPE) {
this.closeDiffFileCommentForm(this.diffFileHash);
} else {
this.handleClearForm(this.line.line_code);
}
})
.catch(() => {
createFlash(s__('MergeRequests|An error occurred while saving the draft comment.'));
});
},
handleClearForm(lineCode) {
this.cancelCommentForm({
lineCode,
fileHash: this.diffFileHash,
});
this.$nextTick(() => {
if (this.autosaveKey) {
clearDraft(this.autosaveKey);
} else {
// TODO: remove the following after replacing the autosave mixin
// https://gitlab.com/gitlab-org/gitlab-foss/issues/60587
this.resetAutoSave();
}
});
},
showDraft(replyId) {
if (this.withBatchComments) {
return this.shouldRenderDraftRowInDiscussion(replyId);
}
return false;
},
},
};
export default {
computed: {
isDraft() {
return this.note.isDraft;
},
canResolve() {
return (
this.note.current_user.can_resolve ||
(this.note.isDraft && this.note.discussion_id !== null)
);
},
},
};
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters(['getDiscussion']),
discussion() {
if (!this.note.isDraft) return {};
return this.getDiscussion(this.note.discussion_id);
},
},
};
import { mapGetters, mapState } from 'vuex';
export default {
props: {
isDraft: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
...mapState({
withBatchComments: state => state.batchComments && state.batchComments.withBatchComments,
}),
...mapGetters('batchComments', ['hasDrafts']),
showBatchCommentsActions() {
return this.withBatchComments && this.noteId === '' && !this.discussion.for_commit;
},
showResolveDiscussionToggle() {
return (
((this.discussion && this.discussion.id && this.discussion.resolvable) || this.isDraft) &&
this.withBatchComments
);
},
},
methods: {
handleKeySubmit() {
if (this.showBatchCommentsActions) {
this.handleAddToReview();
} else {
this.handleUpdate();
}
},
handleUpdate(shouldResolve) {
const beforeSubmitDiscussionState = this.discussionResolved;
this.isSubmitting = true;
this.$emit(
'handleFormUpdate',
this.updatedNoteBody,
this.$refs.editNoteForm,
() => {
this.isSubmitting = false;
if (this.shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState)) {
this.resolveHandler(beforeSubmitDiscussionState);
}
},
this.discussionResolved ? !this.isUnresolving : this.isResolving,
);
},
shouldBeResolved(resolveStatus) {
if (this.withBatchComments) {
return (
(this.discussionResolved && !this.isUnresolving) ||
(!this.discussionResolved && this.isResolving)
);
}
return resolveStatus;
},
handleAddToReview() {
// check if draft should resolve thread
const shouldResolve =
(this.discussionResolved && !this.isUnresolving) ||
(!this.discussionResolved && this.isResolving);
this.isSubmitting = true;
this.$emit('handleFormUpdateAddToReview', this.updatedNoteBody, shouldResolve);
},
},
};
import initSidebarBundle from 'ee/sidebar/sidebar_bundle'; import initSidebarBundle from 'ee/sidebar/sidebar_bundle';
import { initReviewBar } from 'ee/batch_comments'; import { initReviewBar } from '~/batch_comments';
import initMrNotes from '~/mr_notes'; import initMrNotes from '~/mr_notes';
import initShow from '~/pages/projects/merge_requests/init_merge_request_show'; import initShow from '~/pages/projects/merge_requests/init_merge_request_show';
......
...@@ -20,21 +20,6 @@ module EE ...@@ -20,21 +20,6 @@ module EE
super super
end end
override :notes_data
def notes_data(issuable)
data = super
if issuable.is_a?(MergeRequest)
data.merge!(
draftsPath: project_merge_request_drafts_path(@project, issuable),
draftsPublishPath: publish_project_merge_request_drafts_path(@project, issuable),
draftsDiscardPath: discard_project_merge_request_drafts_path(@project, issuable)
)
end
data
end
def description_diff_path(issuable, version_id) def description_diff_path(issuable, version_id)
case issuable case issuable
when Issue when Issue
......
= render_ce "projects/merge_requests/show" = render_ce "projects/merge_requests/show"
- if batch_comments_enabled?
#js-review-bar
= javascript_tag nonce: true do = javascript_tag nonce: true do
:plain :plain
// Append static, server-generated data not included in merge request entity (EE-Only) // Append static, server-generated data not included in merge request entity (EE-Only)
......
import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'spec/test_constants';
import createStore from '~/notes/stores';
import noteActions from '~/notes/components/note_actions.vue';
import { userDataMock } from 'jest/notes/mock_data';
describe('noteActions', () => {
let wrapper;
let store;
let props;
const createWrapper = propsData =>
shallowMount(noteActions, {
store,
propsData,
});
beforeEach(() => {
store = createStore();
props = {
accessLevel: 'Maintainer',
authorId: 26,
canDelete: true,
canEdit: true,
canAwardEmoji: true,
canReportAsAbuse: true,
canResolve: true,
noteId: '539',
noteUrl: `${TEST_HOST}/group/project/-/merge_requests/1#note_1`,
reportAbusePath: `${TEST_HOST}/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-foss%2Fissues%2F7%23note_539&user_id=26`,
showReply: false,
isDraft: true,
};
});
afterEach(() => {
wrapper.destroy();
});
describe('Draft notes', () => {
beforeEach(() => {
store.dispatch('setUserData', userDataMock);
wrapper = createWrapper(props);
});
it('should render the right resolve button title', () => {
const resolveButton = wrapper.find({ ref: 'resolveButton' });
expect(resolveButton.exists()).toBe(true);
expect(resolveButton.attributes('title')).toBe('Thread stays unresolved');
});
});
});
import Vue from 'vue';
import { createStore } from 'ee/batch_comments/stores';
import { keyboardDownEvent } from 'jest/issue_show/helpers';
import { noteableDataMock, discussionMock, notesDataMock } from 'jest/notes/mock_data';
import diffsModule from '~/diffs/store/modules';
import notesModule from '~/notes/stores/modules';
import issueNoteForm from '~/notes/components/note_form.vue';
describe('issue_note_form component', () => {
let store;
let vm;
let props;
beforeEach(() => {
const Component = Vue.extend(issueNoteForm);
store = createStore();
store.registerModule('diffs', diffsModule());
store.registerModule('notes', notesModule());
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
props = {
isEditing: false,
noteBody: 'Magni suscipit eius consectetur enim et ex et commodi.',
discussion: { ...discussionMock, for_commit: false },
};
vm = new Component({
store,
propsData: props,
}).$mount();
});
afterEach(() => {
vm.$destroy();
});
describe('without batch comments', () => {
it('does not show resolve checkbox', () => {
expect(vm.$el.querySelector('[data-qa-selector="resolve_review_discussion_checkbox"]')).toBe(
null,
);
});
describe('on enter', () => {
it('should add comment when cmd+enter is pressed', () => {
jest.spyOn(vm, 'handleUpdate');
vm.$el.querySelector('textarea').value = 'Foo';
vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, true));
expect(vm.handleUpdate).toHaveBeenCalled();
});
it('should add comment when ctrl+enter is pressed', () => {
jest.spyOn(vm, 'handleUpdate');
vm.$el.querySelector('textarea').value = 'Foo';
vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, false, true));
expect(vm.handleUpdate).toHaveBeenCalled();
});
});
});
describe('with batch comments', () => {
beforeEach(() => {
return store.dispatch('batchComments/enableBatchComments').then(vm.$nextTick);
});
it('should be possible to cancel', () => {
jest.spyOn(vm, 'cancelHandler');
return vm.$nextTick().then(() => {
const cancelButton = vm.$el.querySelector('[data-testid="cancelBatchCommentsEnabled"]');
cancelButton.click();
expect(vm.cancelHandler).toHaveBeenCalledWith(true);
});
});
it('shows resolve checkbox', () => {
expect(
vm.$el.querySelector('[data-qa-selector="resolve_review_discussion_checkbox"]'),
).not.toBe(null);
});
it('hides actions for commits', () => {
vm.discussion.for_commit = true;
return vm.$nextTick(() => {
expect(vm.$el.querySelector('.note-form-actions').textContent).not.toContain(
'Start a review',
);
});
});
describe('on enter', () => {
it('should start review or add to review when cmd+enter is pressed', () => {
jest.spyOn(vm, 'handleAddToReview');
vm.$el.querySelector('textarea').value = 'Foo';
vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, true));
expect(vm.handleAddToReview).toHaveBeenCalled();
});
it('should start review or add to review when ctrl+enter is pressed', () => {
jest.spyOn(vm, 'handleAddToReview');
vm.$el.querySelector('textarea').value = 'Foo';
vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, false, true));
expect(vm.handleAddToReview).toHaveBeenCalled();
});
});
});
});
import Vue from 'vue';
import InlineDraftCommentRow from 'ee/batch_comments/components/inline_draft_comment_row.vue';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { createStore } from 'ee/batch_comments/stores';
import '~/behaviors/markdown/render_gfm';
import { createDraft } from '../mock_data';
describe('Batch comments inline draft row component', () => {
let vm;
let Component;
let draft;
beforeAll(() => {
Component = Vue.extend(InlineDraftCommentRow);
});
beforeEach(() => {
const store = createStore();
draft = createDraft();
vm = mountComponentWithStore(Component, { store, props: { draft } });
});
afterEach(() => {
vm.$destroy();
});
it('renders draft', () => {
expect(vm.$el.querySelector('.draft-note-component')).not.toBe(null);
});
});
import Vue from 'vue';
import ParallelDraftCommentRow from 'ee/batch_comments/components/parallel_draft_comment_row.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { createStore } from 'ee/batch_comments/stores';
import '~/behaviors/markdown/render_gfm';
import { createDraft } from '../mock_data';
describe('Batch comments parallel draft row component', () => {
let vm;
let Component;
let draft;
beforeAll(() => {
Component = Vue.extend(ParallelDraftCommentRow);
});
beforeEach(() => {
draft = createDraft();
});
afterEach(() => {
vm.$destroy();
});
['left', 'right'].forEach(side => {
describe(`${side} side of diff`, () => {
beforeEach(() => {
const store = createStore();
vm = createComponentWithStore(Component, store, {
line: { code: '1' },
diffFileContentSha: 'test',
});
spyOnProperty(vm, 'draftForLine').and.returnValue((sha, line, draftSide) => {
if (draftSide === side) return draft;
return {};
});
vm.$mount();
});
it(`it renders draft on ${side} side`, () => {
const sideClass = side === 'left' ? '.old' : '.new';
const oppositeSideClass = side === 'left' ? '.new' : '.old';
expect(vm.$el.querySelector(`.parallel${sideClass} .draft-note-component`)).not.toBe(null);
expect(vm.$el.querySelector(`.parallel${oppositeSideClass} .draft-note-component`)).toBe(
null,
);
});
});
});
});
import Vue from 'vue';
import ReviewBar from 'ee/batch_comments/components/review_bar.vue';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { createStore } from 'ee/batch_comments/stores';
describe('Batch comments review bar component', () => {
let vm;
let Component;
beforeAll(() => {
Component = Vue.extend(ReviewBar);
});
beforeEach(() => {
const store = createStore();
vm = mountComponentWithStore(Component, { store });
spyOn(vm.$store, 'dispatch').and.stub();
});
afterEach(() => {
vm.$destroy();
});
it('hides when no drafts exist', () => {
expect(vm.$el.style.display).toBe('none');
});
describe('with batch comments', () => {
beforeEach(done => {
vm.$store.state.batchComments.drafts.push('comment');
vm.$nextTick(done);
});
it('shows bar', () => {
expect(vm.$el.style.display).not.toBe('none');
});
it('calls discardReview when clicking modal button', done => {
vm.$el.querySelector('.btn.btn-align-content').click();
vm.$nextTick(() => {
const modal = document.querySelector('#discard-draft-review');
modal.querySelector('.btn-danger').click();
expect(vm.$store.dispatch).toHaveBeenCalled();
done();
});
});
it('sets discard button as loading when isDiscarding is true', done => {
vm.$store.state.batchComments.isDiscarding = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.btn-align-content').getAttribute('disabled')).toBe(
'disabled',
);
done();
});
});
});
});
// No new code should be added to this file. Instead, modify the
// file this one re-exports from. For more detail about why, see:
// https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31349
export * from '../../frontend/batch_comments/mock_data';
...@@ -13259,9 +13259,6 @@ msgstr "" ...@@ -13259,9 +13259,6 @@ msgstr ""
msgid "Mark as resolved" msgid "Mark as resolved"
msgstr "" msgstr ""
msgid "Mark comment as resolved"
msgstr ""
msgid "Mark this issue as a duplicate of another issue" msgid "Mark this issue as a duplicate of another issue"
msgstr "" msgstr ""
......
...@@ -17,27 +17,6 @@ module QA ...@@ -17,27 +17,6 @@ module QA
element :head_mismatch, "The source branch HEAD has recently changed." # rubocop:disable QA/ElementWithPattern element :head_mismatch, "The source branch HEAD has recently changed." # rubocop:disable QA/ElementWithPattern
end end
view 'ee/app/assets/javascripts/batch_comments/components/publish_button.vue' do
element :submit_review
end
view 'ee/app/assets/javascripts/batch_comments/components/review_bar.vue' do
element :review_bar
element :discard_review
element :modal_delete_pending_comments
end
view 'app/assets/javascripts/notes/components/note_form.vue' do
element :unresolve_review_discussion_checkbox
element :resolve_review_discussion_checkbox
element :start_review
element :comment_now
end
view 'ee/app/assets/javascripts/batch_comments/components/preview_dropdown.vue' do
element :review_preview_toggle
end
view 'ee/app/views/projects/merge_requests/_code_owner_approval_rules.html.haml' do view 'ee/app/views/projects/merge_requests/_code_owner_approval_rules.html.haml' do
element :approver element :approver
element :approver_list element :approver_list
...@@ -118,49 +97,6 @@ module QA ...@@ -118,49 +97,6 @@ module QA
find_element :approve_button, text: "Revoke approval" find_element :approve_button, text: "Revoke approval"
end end
def start_review
click_element :start_review
# After clicking the button, wait for it to disappear
# before moving on to the next part of the test
has_no_element? :start_review
end
def comment_now
click_element :comment_now
# After clicking the button, wait for it to disappear
# before moving on to the next part of the test
has_no_element? :comment_now
end
def submit_pending_reviews
within_element :review_bar do
click_element :review_preview_toggle
click_element :submit_review
# After clicking the button, wait for it to disappear
# before moving on to the next part of the test
has_no_element? :submit_review
end
end
def discard_pending_reviews
within_element :review_bar do
click_element :discard_review
end
click_element :modal_delete_pending_comments
end
def resolve_review_discussion
scroll_to_element :start_review
check_element :resolve_review_discussion_checkbox
end
def unresolve_review_discussion
check_element :unresolve_review_discussion_checkbox
end
def expand_license_report def expand_license_report
within_element(:license_report_widget) do within_element(:license_report_widget) do
click_element(:expand_report_button) click_element(:expand_report_button)
......
...@@ -73,6 +73,70 @@ module QA ...@@ -73,6 +73,70 @@ module QA
element :edit_button element :edit_button
end end
view 'app/assets/javascripts/batch_comments/components/publish_button.vue' do
element :submit_review
end
view 'app/assets/javascripts/batch_comments/components/review_bar.vue' do
element :review_bar
element :discard_review
element :modal_delete_pending_comments
end
view 'app/assets/javascripts/notes/components/note_form.vue' do
element :unresolve_review_discussion_checkbox
element :resolve_review_discussion_checkbox
element :start_review
element :comment_now
end
view 'app/assets/javascripts/batch_comments/components/preview_dropdown.vue' do
element :review_preview_toggle
end
def start_review
click_element :start_review
# After clicking the button, wait for it to disappear
# before moving on to the next part of the test
has_no_element? :start_review
end
def comment_now
click_element :comment_now
# After clicking the button, wait for it to disappear
# before moving on to the next part of the test
has_no_element? :comment_now
end
def submit_pending_reviews
within_element :review_bar do
click_element :review_preview_toggle
click_element :submit_review
# After clicking the button, wait for it to disappear
# before moving on to the next part of the test
has_no_element? :submit_review
end
end
def discard_pending_reviews
within_element :review_bar do
click_element :discard_review
end
click_element :modal_delete_pending_comments
end
def resolve_review_discussion
scroll_to_element :start_review
check_element :resolve_review_discussion_checkbox
end
def unresolve_review_discussion
check_element :unresolve_review_discussion_checkbox
end
def add_comment_to_diff(text) def add_comment_to_diff(text)
wait_until(sleep_interval: 5) do wait_until(sleep_interval: 5) do
has_text?("No newline at end of file") has_text?("No newline at end of file")
......
...@@ -18,23 +18,9 @@ describe 'Merge request > Batch comments', :js do ...@@ -18,23 +18,9 @@ describe 'Merge request > Batch comments', :js do
sign_in(user) sign_in(user)
end end
context 'Feature is disabled' do
before do
stub_feature_flags(batch_comments: false)
stub_feature_flags(diffs_batch_load: false)
visit_diffs
end
it 'does not have review bar' do
expect(page).not_to have_css('.review-bar-component')
end
end
context 'Feature is enabled' do context 'Feature is enabled' do
before do before do
stub_feature_flags(diffs_batch_load: false) stub_feature_flags(diffs_batch_load: false)
stub_licensed_features(batch_comments: true)
visit_diffs visit_diffs
end end
......
...@@ -27,7 +27,7 @@ describe 'User comments on a diff', :js do ...@@ -27,7 +27,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do page.within('.js-discussion-note-form') do
fill_in('note_note', with: 'Line is wrong') fill_in('note_note', with: 'Line is wrong')
click_button('Comment') click_button('Add comment now')
end end
page.within('.diff-files-holder > div:nth-child(3)') do page.within('.diff-files-holder > div:nth-child(3)') do
...@@ -46,7 +46,7 @@ describe 'User comments on a diff', :js do ...@@ -46,7 +46,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do page.within('.js-discussion-note-form') do
fill_in('note_note', with: 'Line is correct') fill_in('note_note', with: 'Line is correct')
click_button('Comment') click_button('Add comment now')
end end
wait_for_requests wait_for_requests
...@@ -59,7 +59,7 @@ describe 'User comments on a diff', :js do ...@@ -59,7 +59,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do page.within('.js-discussion-note-form') do
fill_in('note_note', with: 'Line is wrong') fill_in('note_note', with: 'Line is wrong')
click_button('Comment') click_button('Add comment now')
end end
wait_for_requests wait_for_requests
...@@ -120,7 +120,7 @@ describe 'User comments on a diff', :js do ...@@ -120,7 +120,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do page.within('.js-discussion-note-form') do
fill_in(:note_note, with: 'Line is wrong') fill_in(:note_note, with: 'Line is wrong')
click_button('Comment') click_button('Add comment now')
end end
page.within('.diff-file:nth-of-type(5) .discussion .note') do page.within('.diff-file:nth-of-type(5) .discussion .note') do
...@@ -146,7 +146,7 @@ describe 'User comments on a diff', :js do ...@@ -146,7 +146,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do page.within('.js-discussion-note-form') do
fill_in(:note_note, with: 'Line is wrong') fill_in(:note_note, with: 'Line is wrong')
click_button('Comment') click_button('Add comment now')
end end
page.within('.notes-tab .badge') do page.within('.notes-tab .badge') do
......
...@@ -22,14 +22,14 @@ describe 'Batch diffs', :js do ...@@ -22,14 +22,14 @@ describe 'Batch diffs', :js do
click_diff_line(find('.diff-file.file-holder:first-of-type tr.line_holder.new:first-of-type')) click_diff_line(find('.diff-file.file-holder:first-of-type tr.line_holder.new:first-of-type'))
page.within('.js-discussion-note-form') do page.within('.js-discussion-note-form') do
fill_in('note_note', with: 'First Line Comment') fill_in('note_note', with: 'First Line Comment')
click_button('Comment') click_button('Add comment now')
end end
# Add discussion to first line of last file # Add discussion to first line of last file
click_diff_line(find('.diff-file.file-holder:last-of-type tr.line_holder.new:first-of-type')) click_diff_line(find('.diff-file.file-holder:last-of-type tr.line_holder.new:first-of-type'))
page.within('.js-discussion-note-form') do page.within('.js-discussion-note-form') do
fill_in('note_note', with: 'Last Line Comment') fill_in('note_note', with: 'Last Line Comment')
click_button('Comment') click_button('Add comment now')
end end
wait_for_requests wait_for_requests
......
...@@ -225,7 +225,7 @@ describe 'Merge request > User posts diff notes', :js do ...@@ -225,7 +225,7 @@ describe 'Merge request > User posts diff notes', :js do
def should_allow_commenting(line_holder, diff_side = nil, asset_form_reset: true) def should_allow_commenting(line_holder, diff_side = nil, asset_form_reset: true)
write_comment_on_line(line_holder, diff_side) write_comment_on_line(line_holder, diff_side)
click_button 'Comment' click_button 'Add comment now'
wait_for_requests wait_for_requests
......
...@@ -105,7 +105,7 @@ describe 'Merge request > User posts notes', :js do ...@@ -105,7 +105,7 @@ describe 'Merge request > User posts notes', :js do
page.within('.discussion-reply-holder') do page.within('.discussion-reply-holder') do
fill_in 'note[note]', with: 'A reply' fill_in 'note[note]', with: 'A reply'
click_button 'Comment' click_button 'Add comment now'
wait_for_requests wait_for_requests
expect(page).to have_content('Your comment could not be submitted because discussion to reply to cannot be found') expect(page).to have_content('Your comment could not be submitted because discussion to reply to cannot be found')
end end
......
...@@ -146,17 +146,16 @@ describe 'Merge request > User resolves diff notes and threads', :js do ...@@ -146,17 +146,16 @@ describe 'Merge request > User resolves diff notes and threads', :js do
describe 'reply form' do describe 'reply form' do
before do before do
click_button 'Toggle thread' click_button 'Toggle thread'
page.within '.diff-content' do
click_button 'Reply...'
end
end end
it 'allows user to comment' do it 'allows user to comment' do
page.within '.diff-content' do page.within '.diff-content' do
click_button 'Reply...'
find(".js-unresolve-checkbox").set false
find('.js-note-text').set 'testing' find('.js-note-text').set 'testing'
click_button 'Comment' click_button 'Add comment now'
wait_for_requests wait_for_requests
end end
...@@ -181,9 +180,11 @@ describe 'Merge request > User resolves diff notes and threads', :js do ...@@ -181,9 +180,11 @@ describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to comment & unresolve thread' do it 'allows user to comment & unresolve thread' do
page.within '.diff-content' do page.within '.diff-content' do
click_button 'Reply...'
find('.js-note-text').set 'testing' find('.js-note-text').set 'testing'
click_button 'Comment & unresolve thread' click_button 'Add comment now'
wait_for_requests wait_for_requests
end end
...@@ -197,8 +198,6 @@ describe 'Merge request > User resolves diff notes and threads', :js do ...@@ -197,8 +198,6 @@ describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to resolve from reply form without a comment' do it 'allows user to resolve from reply form without a comment' do
page.within '.diff-content' do page.within '.diff-content' do
click_button 'Reply...'
click_button 'Resolve thread' click_button 'Resolve thread'
end end
...@@ -214,7 +213,9 @@ describe 'Merge request > User resolves diff notes and threads', :js do ...@@ -214,7 +213,9 @@ describe 'Merge request > User resolves diff notes and threads', :js do
find('.js-note-text').set 'testing' find('.js-note-text').set 'testing'
click_button 'Comment & resolve thread' find('.js-resolve-checkbox').set(true)
click_button 'Add comment now'
end end
page.within '.line-resolve-all-container' do page.within '.line-resolve-all-container' do
...@@ -445,7 +446,9 @@ describe 'Merge request > User resolves diff notes and threads', :js do ...@@ -445,7 +446,9 @@ describe 'Merge request > User resolves diff notes and threads', :js do
find('.js-note-text').set 'testing' find('.js-note-text').set 'testing'
click_button 'Comment & resolve thread' find('.js-resolve-checkbox').set(true)
click_button 'Add comment now'
end end
page.within '.line-resolve-all-container' do page.within '.line-resolve-all-container' do
...@@ -462,7 +465,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do ...@@ -462,7 +465,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
find('.js-note-text').set 'testing' find('.js-note-text').set 'testing'
click_button 'Comment & unresolve thread' click_button 'Add comment now'
end end
page.within '.line-resolve-all-container' do page.within '.line-resolve-all-container' do
......
...@@ -11,8 +11,6 @@ describe 'Merge request > image review', :js do ...@@ -11,8 +11,6 @@ describe 'Merge request > image review', :js do
let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, author: user) } let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, author: user) }
before do before do
stub_licensed_features(batch_comments: true)
sign_in(user) sign_in(user)
allow_any_instance_of(DiffHelper).to receive(:diff_file_blob_raw_url).and_return('/apple-touch-icon.png') allow_any_instance_of(DiffHelper).to receive(:diff_file_blob_raw_url).and_return('/apple-touch-icon.png')
......
...@@ -42,7 +42,7 @@ describe 'Merge request > User sees avatars on diff notes', :js do ...@@ -42,7 +42,7 @@ describe 'Merge request > User sees avatars on diff notes', :js do
page.within('.js-discussion-note-form') do page.within('.js-discussion-note-form') do
find('.note-textarea').native.send_keys('Test comment') find('.note-textarea').native.send_keys('Test comment')
click_button 'Comment' click_button 'Add comment now'
end end
expect(page).to have_content('Test comment') expect(page).to have_content('Test comment')
...@@ -137,7 +137,7 @@ describe 'Merge request > User sees avatars on diff notes', :js do ...@@ -137,7 +137,7 @@ describe 'Merge request > User sees avatars on diff notes', :js do
page.within '.js-discussion-note-form' do page.within '.js-discussion-note-form' do
find('.js-note-text').native.send_keys('Test') find('.js-note-text').native.send_keys('Test')
click_button 'Comment' click_button 'Add comment now'
wait_for_requests wait_for_requests
end end
...@@ -155,7 +155,7 @@ describe 'Merge request > User sees avatars on diff notes', :js do ...@@ -155,7 +155,7 @@ describe 'Merge request > User sees avatars on diff notes', :js do
page.within '.js-discussion-note-form' do page.within '.js-discussion-note-form' do
find('.js-note-text').native.send_keys('Test') find('.js-note-text').native.send_keys('Test')
find('.js-comment-button').click click_button 'Add comment now'
wait_for_requests wait_for_requests
end end
......
...@@ -34,7 +34,7 @@ describe 'Merge request > User sees versions', :js do ...@@ -34,7 +34,7 @@ describe 'Merge request > User sees versions', :js do
page.within("form[data-line-code='#{line_code}']") do page.within("form[data-line-code='#{line_code}']") do
fill_in "note[note]", with: comment fill_in "note[note]", with: comment
find(".js-comment-button").click click_button('Add comment now')
end end
wait_for_requests wait_for_requests
......
...@@ -49,7 +49,7 @@ describe 'User comments on a diff', :js do ...@@ -49,7 +49,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do page.within('.js-discussion-note-form') do
fill_in('note_note', with: "```suggestion\n# change to a comment\n```") fill_in('note_note', with: "```suggestion\n# change to a comment\n```")
click_button('Comment') click_button('Add comment now')
end end
wait_for_requests wait_for_requests
...@@ -77,7 +77,7 @@ describe 'User comments on a diff', :js do ...@@ -77,7 +77,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do page.within('.js-discussion-note-form') do
fill_in('note_note', with: "```suggestion\n# change to a comment\n```") fill_in('note_note', with: "```suggestion\n# change to a comment\n```")
click_button('Comment') click_button('Add comment now')
end end
wait_for_requests wait_for_requests
...@@ -119,7 +119,7 @@ describe 'User comments on a diff', :js do ...@@ -119,7 +119,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do page.within('.js-discussion-note-form') do
fill_in('note_note', with: "```suggestion\n# change to a comment\n```") fill_in('note_note', with: "```suggestion\n# change to a comment\n```")
click_button('Comment') click_button('Add comment now')
wait_for_requests wait_for_requests
end end
...@@ -127,7 +127,7 @@ describe 'User comments on a diff', :js do ...@@ -127,7 +127,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do page.within('.js-discussion-note-form') do
fill_in('note_note', with: "```suggestion\n# 2nd change to a comment\n```") fill_in('note_note', with: "```suggestion\n# 2nd change to a comment\n```")
click_button('Comment') click_button('Add comment now')
wait_for_requests wait_for_requests
end end
...@@ -158,7 +158,7 @@ describe 'User comments on a diff', :js do ...@@ -158,7 +158,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do page.within('.js-discussion-note-form') do
fill_in('note_note', with: "```suggestion\n# change to a comment\n```\n```suggestion:-2\n# or that\n# heh\n```") fill_in('note_note', with: "```suggestion\n# change to a comment\n```\n```suggestion:-2\n# or that\n# heh\n```")
click_button('Comment') click_button('Add comment now')
end end
wait_for_requests wait_for_requests
...@@ -201,7 +201,7 @@ describe 'User comments on a diff', :js do ...@@ -201,7 +201,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do page.within('.js-discussion-note-form') do
fill_in('note_note', with: "```suggestion:-3+5\n# change to a\n# comment\n# with\n# broken\n# lines\n```") fill_in('note_note', with: "```suggestion:-3+5\n# change to a\n# comment\n# with\n# broken\n# lines\n```")
click_button('Comment') click_button('Add comment now')
end end
wait_for_requests wait_for_requests
......
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import DiffFileDrafts from 'ee/batch_comments/components/diff_file_drafts.vue'; import DiffFileDrafts from '~/batch_comments/components/diff_file_drafts.vue';
import DraftNote from 'ee/batch_comments/components/draft_note.vue'; import DraftNote from '~/batch_comments/components/draft_note.vue';
const localVue = createLocalVue(); const localVue = createLocalVue();
......
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import DraftNote from 'ee/batch_comments/components/draft_note.vue'; import DraftNote from '~/batch_comments/components/draft_note.vue';
import { createStore } from 'ee/batch_comments/stores'; import { createStore } from '~/batch_comments/stores';
import NoteableNote from '~/notes/components/noteable_note.vue'; import NoteableNote from '~/notes/components/noteable_note.vue';
import '~/behaviors/markdown/render_gfm'; import '~/behaviors/markdown/render_gfm';
import { createDraft } from '../mock_data'; import { createDraft } from '../mock_data';
......
import Vue from 'vue'; import Vue from 'vue';
import DraftsCount from 'ee/batch_comments/components/drafts_count.vue'; import DraftsCount from '~/batch_comments/components/drafts_count.vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper'; import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from 'ee/batch_comments/stores'; import { createStore } from '~/batch_comments/stores';
describe('Batch comments drafts count component', () => { describe('Batch comments drafts count component', () => {
let vm; let vm;
......
import Vue from 'vue'; import Vue from 'vue';
import PreviewItem from 'ee/batch_comments/components/preview_item.vue'; import PreviewItem from '~/batch_comments/components/preview_item.vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper'; import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from 'ee/batch_comments/stores'; import { createStore } from '~/batch_comments/stores';
import diffsModule from '~/diffs/store/modules'; import diffsModule from '~/diffs/store/modules';
import notesModule from '~/notes/stores/modules'; import notesModule from '~/notes/stores/modules';
import '~/behaviors/markdown/render_gfm'; import '~/behaviors/markdown/render_gfm';
......
import Vue from 'vue'; import Vue from 'vue';
import PublishButton from 'ee/batch_comments/components/publish_button.vue'; import PublishButton from '~/batch_comments/components/publish_button.vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper'; import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from 'ee/batch_comments/stores'; import { createStore } from '~/batch_comments/stores';
describe('Batch comments publish button component', () => { describe('Batch comments publish button component', () => {
let vm; let vm;
......
import Vue from 'vue'; import Vue from 'vue';
import PreviewDropdown from 'ee/batch_comments/components/preview_dropdown.vue'; import PreviewDropdown from '~/batch_comments/components/preview_dropdown.vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper'; import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from 'ee/mr_notes/stores'; import { createStore } from '~/mr_notes/stores';
import '~/behaviors/markdown/render_gfm'; import '~/behaviors/markdown/render_gfm';
import { createDraft } from '../mock_data'; import { createDraft } from '../mock_data';
......
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import * as actions from 'ee/batch_comments/stores/modules/batch_comments/actions'; import * as actions from '~/batch_comments/stores/modules/batch_comments/actions';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
describe('Batch comments store actions', () => { describe('Batch comments store actions', () => {
...@@ -16,19 +16,6 @@ describe('Batch comments store actions', () => { ...@@ -16,19 +16,6 @@ describe('Batch comments store actions', () => {
mock.restore(); mock.restore();
}); });
describe('enableBatchComments', () => {
it('commits ENABLE_BATCH_COMMENTS', done => {
testAction(
actions.enableBatchComments,
null,
null,
[{ type: 'ENABLE_BATCH_COMMENTS' }],
[],
done,
);
});
});
describe('saveDraft', () => { describe('saveDraft', () => {
it('dispatches saveNote on root', () => { it('dispatches saveNote on root', () => {
const dispatch = jest.fn(); const dispatch = jest.fn();
......
import * as getters from 'ee/batch_comments/stores/modules/batch_comments/getters'; import * as getters from '~/batch_comments/stores/modules/batch_comments/getters';
describe('Batch comments store getters', () => { describe('Batch comments store getters', () => {
describe('draftsForFile', () => { describe('draftsForFile', () => {
......
import createState from 'ee/batch_comments/stores/modules/batch_comments/state'; import createState from '~/batch_comments/stores/modules/batch_comments/state';
import mutations from 'ee/batch_comments/stores/modules/batch_comments/mutations'; import mutations from '~/batch_comments/stores/modules/batch_comments/mutations';
import * as types from 'ee/batch_comments/stores/modules/batch_comments/mutation_types'; import * as types from '~/batch_comments/stores/modules/batch_comments/mutation_types';
describe('Batch comments mutations', () => { describe('Batch comments mutations', () => {
let state; let state;
...@@ -9,14 +9,6 @@ describe('Batch comments mutations', () => { ...@@ -9,14 +9,6 @@ describe('Batch comments mutations', () => {
state = createState(); state = createState();
}); });
describe(types.ENABLE_BATCH_COMMENTS, () => {
it('sets withBatchComments to true', () => {
mutations[types.ENABLE_BATCH_COMMENTS](state);
expect(state.withBatchComments).toBe(true);
});
});
describe(types.ADD_NEW_DRAFT, () => { describe(types.ADD_NEW_DRAFT, () => {
it('adds processed object into drafts array', () => { it('adds processed object into drafts array', () => {
const draft = { id: 1, note: 'test' }; const draft = { id: 1, note: 'test' };
......
import Vue from 'vue'; import Vue from 'vue';
import { createStore } from 'ee_else_ce/mr_notes/stores'; import { createStore } from '~/mr_notes/stores';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { mockTracking, triggerEvent } from 'helpers/tracking_helper'; import { mockTracking, triggerEvent } from 'helpers/tracking_helper';
import DiffFileComponent from '~/diffs/components/diff_file.vue'; import DiffFileComponent from '~/diffs/components/diff_file.vue';
......
import Vue from 'vue'; import Vue from 'vue';
import '~/behaviors/markdown/render_gfm'; import '~/behaviors/markdown/render_gfm';
import { createStore } from 'ee_else_ce/mr_notes/stores'; import { createStore } from '~/mr_notes/stores';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import InlineDiffView from '~/diffs/components/inline_diff_view.vue'; import InlineDiffView from '~/diffs/components/inline_diff_view.vue';
import diffFileMockData from '../mock_data/diff_file'; import diffFileMockData from '../mock_data/diff_file';
......
import Vue from 'vue'; import Vue from 'vue';
import { createStore } from 'ee_else_ce/mr_notes/stores'; import { createStore } from '~/mr_notes/stores';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import ParallelDiffView from '~/diffs/components/parallel_diff_view.vue'; import ParallelDiffView from '~/diffs/components/parallel_diff_view.vue';
import * as constants from '~/diffs/constants'; import * as constants from '~/diffs/constants';
......
...@@ -157,4 +157,19 @@ describe('noteActions', () => { ...@@ -157,4 +157,19 @@ describe('noteActions', () => {
expect(replyButton.exists()).toBe(false); expect(replyButton.exists()).toBe(false);
}); });
}); });
describe('Draft notes', () => {
beforeEach(() => {
store.dispatch('setUserData', userDataMock);
wrapper = shallowMountNoteActions({ ...props, canResolve: true, isDraft: true });
});
it('should render the right resolve button title', () => {
const resolveButton = wrapper.find({ ref: 'resolveButton' });
expect(resolveButton.exists()).toBe(true);
expect(resolveButton.attributes('title')).toBe('Thread stays unresolved');
});
});
}); });
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import createStore from '~/notes/stores'; import createStore from '~/notes/stores';
import NoteForm from '~/notes/components/note_form.vue'; import NoteForm from '~/notes/components/note_form.vue';
import batchComments from '~/batch_comments/stores/modules/batch_comments';
import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { noteableDataMock, notesDataMock } from '../mock_data'; import { noteableDataMock, notesDataMock, discussionMock } from '../mock_data';
import { getDraft, updateDraft } from '~/lib/utils/autosave'; import { getDraft, updateDraft } from '~/lib/utils/autosave';
...@@ -245,4 +246,55 @@ describe('issue_note_form component', () => { ...@@ -245,4 +246,55 @@ describe('issue_note_form component', () => {
expect(updateDraft).toHaveBeenCalledWith(dummyAutosaveKey, dummyContent); expect(updateDraft).toHaveBeenCalledWith(dummyAutosaveKey, dummyContent);
}); });
}); });
describe('with batch comments', () => {
beforeEach(() => {
store.registerModule('batchComments', batchComments());
wrapper = createComponentWrapper();
wrapper.setProps({
...props,
noteId: '',
discussion: { ...discussionMock, for_commit: false },
});
});
it('should be possible to cancel', () => {
jest.spyOn(wrapper.vm, 'cancelHandler');
return wrapper.vm.$nextTick().then(() => {
const cancelButton = wrapper.find('[data-testid="cancelBatchCommentsEnabled"]');
cancelButton.trigger('click');
expect(wrapper.vm.cancelHandler).toHaveBeenCalledWith(true);
});
});
it('shows resolve checkbox', () => {
expect(wrapper.find('.js-resolve-checkbox').exists()).toBe(true);
});
it('hides actions for commits', () => {
wrapper.setProps({ discussion: { for_commit: true } });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find('.note-form-actions').text()).not.toContain('Start a review');
});
});
describe('on enter', () => {
it('should start review or add to review when cmd+enter is pressed', () => {
const textarea = wrapper.find('textarea');
jest.spyOn(wrapper.vm, 'handleAddToReview');
textarea.setValue('Foo');
textarea.trigger('keydown.enter', { metaKey: true });
return wrapper.vm.$nextTick(() => {
expect(wrapper.vm.handleAddToReview).toHaveBeenCalled();
});
});
});
});
}); });
...@@ -6,7 +6,7 @@ RSpec.shared_examples 'comment on merge request file' do ...@@ -6,7 +6,7 @@ RSpec.shared_examples 'comment on merge request file' do
page.within('.js-discussion-note-form') do page.within('.js-discussion-note-form') do
fill_in(:note_note, with: 'Line is wrong') fill_in(:note_note, with: 'Line is wrong')
click_button('Comment') find('.js-comment-button').click
end end
wait_for_requests wait_for_requests
......
...@@ -15,7 +15,7 @@ RSpec.shared_examples 'thread comments' do |resource_name| ...@@ -15,7 +15,7 @@ RSpec.shared_examples 'thread comments' do |resource_name|
find("#{form_selector} .note-textarea").send_keys(comment) find("#{form_selector} .note-textarea").send_keys(comment)
click_button 'Comment' find('.js-comment-button').click
expect(page).to have_content(comment) expect(page).to have_content(comment)
...@@ -146,7 +146,7 @@ RSpec.shared_examples 'thread comments' do |resource_name| ...@@ -146,7 +146,7 @@ RSpec.shared_examples 'thread comments' do |resource_name|
find("#{comments_selector} .js-vue-discussion-reply").click find("#{comments_selector} .js-vue-discussion-reply").click
find("#{comments_selector} .note-textarea").send_keys(text) find("#{comments_selector} .note-textarea").send_keys(text)
click_button "Comment" find("#{comments_selector} .js-comment-button").click
wait_for_requests wait_for_requests
end end
......
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