Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
844e3ef899
commit
e30d680b42
|
|
@ -41,13 +41,17 @@ export default {
|
|||
titleText() {
|
||||
const file = this.discussion ? this.discussion.diff_file : this.draft;
|
||||
|
||||
if (file) {
|
||||
if (file?.file_path) {
|
||||
return file.file_path;
|
||||
}
|
||||
|
||||
return sprintf(__("%{authorsName}'s thread"), {
|
||||
authorsName: this.discussion.notes.find((note) => !note.system).author.name,
|
||||
});
|
||||
if (this.discussion) {
|
||||
return sprintf(__("%{authorsName}'s thread"), {
|
||||
authorsName: this.discussion.notes.find((note) => !note.system).author.name,
|
||||
});
|
||||
}
|
||||
|
||||
return __('Your new comment');
|
||||
},
|
||||
linePosition() {
|
||||
if (this.position?.position_type === IMAGE_DIFF_POSITION_TYPE) {
|
||||
|
|
@ -94,7 +98,7 @@ export default {
|
|||
<span class="review-preview-item-header">
|
||||
<gl-icon class="flex-shrink-0" :name="iconName" />
|
||||
<span class="bold text-nowrap gl-align-items-center">
|
||||
<span class="review-preview-item-header-text block-truncated">
|
||||
<span class="review-preview-item-header-text block-truncated gl-ml-2">
|
||||
{{ titleText }}
|
||||
</span>
|
||||
<template v-if="showLinePosition">
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ export default {
|
|||
'getNoteableDataByProp',
|
||||
'getNotesData',
|
||||
'openState',
|
||||
'hasDrafts',
|
||||
]),
|
||||
...mapState(['isToggleStateButtonLoading']),
|
||||
isNoteTypeComment() {
|
||||
|
|
@ -171,6 +172,9 @@ export default {
|
|||
endpoint() {
|
||||
return this.getNoteableData.create_note_path;
|
||||
},
|
||||
draftEndpoint() {
|
||||
return this.getNotesData.draftsPath;
|
||||
},
|
||||
issuableTypeTitle() {
|
||||
return this.noteableType === constants.MERGE_REQUEST_NOTEABLE_TYPE
|
||||
? this.$options.i18n.mergeRequest
|
||||
|
|
@ -214,12 +218,15 @@ export default {
|
|||
this.errors = [this.$options.i18n.GENERIC_UNSUBMITTABLE_NETWORK];
|
||||
}
|
||||
},
|
||||
handleSave(withIssueAction) {
|
||||
handleSaveDraft() {
|
||||
this.handleSave({ isDraft: true });
|
||||
},
|
||||
handleSave({ withIssueAction = false, isDraft = false } = {}) {
|
||||
this.errors = [];
|
||||
|
||||
if (this.note.length) {
|
||||
const noteData = {
|
||||
endpoint: this.endpoint,
|
||||
endpoint: isDraft ? this.draftEndpoint : this.endpoint,
|
||||
data: {
|
||||
note: {
|
||||
noteable_type: this.noteableType,
|
||||
|
|
@ -229,6 +236,7 @@ export default {
|
|||
},
|
||||
merge_request_diff_head_sha: this.getNoteableData.diff_head_sha,
|
||||
},
|
||||
isDraft,
|
||||
};
|
||||
|
||||
if (this.noteType === constants.DISCUSSION) {
|
||||
|
|
@ -392,62 +400,82 @@ export default {
|
|||
</markdown-field>
|
||||
</comment-field-layout>
|
||||
<div class="note-form-actions">
|
||||
<gl-form-checkbox
|
||||
v-if="confidentialNotesEnabled && canSetConfidential"
|
||||
v-model="noteIsConfidential"
|
||||
class="gl-mb-6"
|
||||
data-testid="confidential-note-checkbox"
|
||||
>
|
||||
{{ $options.i18n.confidential }}
|
||||
<gl-icon
|
||||
v-gl-tooltip:tooltipcontainer.bottom
|
||||
name="question"
|
||||
:size="16"
|
||||
:title="$options.i18n.confidentialVisibility"
|
||||
class="gl-text-gray-500"
|
||||
/>
|
||||
</gl-form-checkbox>
|
||||
<gl-dropdown
|
||||
split
|
||||
:text="commentButtonTitle"
|
||||
class="gl-mr-3 js-comment-button js-comment-submit-button comment-type-dropdown"
|
||||
category="primary"
|
||||
variant="confirm"
|
||||
:disabled="disableSubmitButton"
|
||||
data-testid="comment-button"
|
||||
data-qa-selector="comment_button"
|
||||
:data-track-label="trackingLabel"
|
||||
data-track-event="click_button"
|
||||
@click="handleSave()"
|
||||
>
|
||||
<gl-dropdown-item
|
||||
is-check-item
|
||||
:is-checked="isNoteTypeComment"
|
||||
:selected="isNoteTypeComment"
|
||||
@click="setNoteTypeToComment"
|
||||
<template v-if="hasDrafts">
|
||||
<gl-button
|
||||
:disabled="disableSubmitButton"
|
||||
data-testid="add-to-review-button"
|
||||
type="submit"
|
||||
category="primary"
|
||||
variant="success"
|
||||
@click.prevent="handleSaveDraft()"
|
||||
>{{ __('Add to review') }}</gl-button
|
||||
>
|
||||
<strong>{{ $options.i18n.submitButton.comment }}</strong>
|
||||
<p class="gl-m-0">{{ commentDescription }}</p>
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-divider />
|
||||
<gl-dropdown-item
|
||||
is-check-item
|
||||
:is-checked="isNoteTypeDiscussion"
|
||||
:selected="isNoteTypeDiscussion"
|
||||
data-qa-selector="discussion_menu_item"
|
||||
@click="setNoteTypeToDiscussion"
|
||||
<gl-button
|
||||
:disabled="disableSubmitButton"
|
||||
data-testid="add-comment-now-button"
|
||||
category="secondary"
|
||||
@click.prevent="handleSave()"
|
||||
>{{ __('Add comment now') }}</gl-button
|
||||
>
|
||||
<strong>{{ $options.i18n.submitButton.startThread }}</strong>
|
||||
<p class="gl-m-0">{{ startDiscussionDescription }}</p>
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
</template>
|
||||
<template v-else>
|
||||
<gl-form-checkbox
|
||||
v-if="confidentialNotesEnabled && canSetConfidential"
|
||||
v-model="noteIsConfidential"
|
||||
class="gl-mb-6"
|
||||
data-testid="confidential-note-checkbox"
|
||||
>
|
||||
{{ $options.i18n.confidential }}
|
||||
<gl-icon
|
||||
v-gl-tooltip:tooltipcontainer.bottom
|
||||
name="question"
|
||||
:size="16"
|
||||
:title="$options.i18n.confidentialVisibility"
|
||||
class="gl-text-gray-500"
|
||||
/>
|
||||
</gl-form-checkbox>
|
||||
<gl-dropdown
|
||||
split
|
||||
:text="commentButtonTitle"
|
||||
class="gl-mr-3 js-comment-button js-comment-submit-button comment-type-dropdown"
|
||||
category="primary"
|
||||
variant="confirm"
|
||||
:disabled="disableSubmitButton"
|
||||
data-testid="comment-button"
|
||||
data-qa-selector="comment_button"
|
||||
:data-track-label="trackingLabel"
|
||||
data-track-event="click_button"
|
||||
@click="handleSave()"
|
||||
>
|
||||
<gl-dropdown-item
|
||||
is-check-item
|
||||
:is-checked="isNoteTypeComment"
|
||||
:selected="isNoteTypeComment"
|
||||
@click="setNoteTypeToComment"
|
||||
>
|
||||
<strong>{{ $options.i18n.submitButton.comment }}</strong>
|
||||
<p class="gl-m-0">{{ commentDescription }}</p>
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-divider />
|
||||
<gl-dropdown-item
|
||||
is-check-item
|
||||
:is-checked="isNoteTypeDiscussion"
|
||||
:selected="isNoteTypeDiscussion"
|
||||
data-qa-selector="discussion_menu_item"
|
||||
@click="setNoteTypeToDiscussion"
|
||||
>
|
||||
<strong>{{ $options.i18n.submitButton.startThread }}</strong>
|
||||
<p class="gl-m-0">{{ startDiscussionDescription }}</p>
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
</template>
|
||||
<gl-button
|
||||
v-if="canToggleIssueState"
|
||||
:loading="isToggleStateButtonLoading"
|
||||
:class="[actionButtonClassNames, 'btn-comment btn-comment-and-close']"
|
||||
:disabled="isSubmitting"
|
||||
data-testid="close-reopen-button"
|
||||
@click="handleSave(true)"
|
||||
@click="handleSave({ withIssueAction: true })"
|
||||
>{{ issueActionButtonTitle }}</gl-button
|
||||
>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ import { mapGetters, mapActions } from 'vuex';
|
|||
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
|
||||
import { __ } from '~/locale';
|
||||
import initUserPopovers from '~/user_popovers';
|
||||
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
|
||||
import OrderedLayout from '~/vue_shared/components/ordered_layout.vue';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import draftNote from '../../batch_comments/components/draft_note.vue';
|
||||
import { deprecatedCreateFlash as Flash } from '../../flash';
|
||||
import { getLocationHash, doesHashExistInUrl } from '../../lib/utils/url_utility';
|
||||
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
|
||||
|
|
@ -32,6 +34,8 @@ export default {
|
|||
discussionFilterNote,
|
||||
OrderedLayout,
|
||||
SidebarSubscription,
|
||||
draftNote,
|
||||
TimelineEntryItem,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
props: {
|
||||
|
|
@ -276,6 +280,9 @@ export default {
|
|||
<ul id="notes-list" class="notes main-notes-list timeline">
|
||||
<template v-for="discussion in allDiscussions">
|
||||
<skeleton-loading-container v-if="discussion.isSkeletonNote" :key="discussion.id" />
|
||||
<timeline-entry-item v-else-if="discussion.isDraft" :key="discussion.id">
|
||||
<draft-note :draft="discussion" />
|
||||
</timeline-entry-item>
|
||||
<template v-else-if="discussion.isPlaceholderNote">
|
||||
<placeholder-system-note
|
||||
v-if="discussion.placeholderType === $options.systemNote"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,23 @@ import { flattenDeep, clone } from 'lodash';
|
|||
import * as constants from '../constants';
|
||||
import { collapseSystemNotes } from './collapse_utils';
|
||||
|
||||
export const discussions = (state) => {
|
||||
const getDraftComments = (state) => {
|
||||
if (!state.batchComments) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return state.batchComments.drafts
|
||||
.filter((draft) => !draft.line_code && !draft.discussion_id)
|
||||
.map((x) => ({
|
||||
...x,
|
||||
// Treat a top-level draft note as individual_note so it's not included in
|
||||
// expand/collapse threads
|
||||
individual_note: true,
|
||||
}))
|
||||
.sort((a, b) => a.id - b.id);
|
||||
};
|
||||
|
||||
export const discussions = (state, getters, rootState) => {
|
||||
let discussionsInState = clone(state.discussions);
|
||||
// NOTE: not testing bc will be removed when backend is finished.
|
||||
|
||||
|
|
@ -22,11 +38,15 @@ export const discussions = (state) => {
|
|||
.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
|
||||
}
|
||||
|
||||
discussionsInState = collapseSystemNotes(discussionsInState);
|
||||
|
||||
discussionsInState = discussionsInState.concat(getDraftComments(rootState));
|
||||
|
||||
if (state.discussionSortOrder === constants.DESC) {
|
||||
discussionsInState = discussionsInState.reverse();
|
||||
}
|
||||
|
||||
return collapseSystemNotes(discussionsInState);
|
||||
return discussionsInState;
|
||||
};
|
||||
|
||||
export const convertedDisscussionIds = (state) => state.convertedDisscussionIds;
|
||||
|
|
@ -257,3 +277,6 @@ export const commentsDisabled = (state) => state.commentsDisabled;
|
|||
|
||||
export const suggestionsCount = (state, getters) =>
|
||||
Object.values(getters.notesById).filter((n) => n.suggestions.length).length;
|
||||
|
||||
export const hasDrafts = (state, getters, rootState, rootGetters) =>
|
||||
Boolean(rootGetters['batchComments/hasDrafts']);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
.gl-badge.feature-highlight-badge {
|
||||
background-color: $purple-light;
|
||||
color: $purple;
|
||||
|
||||
&,
|
||||
&.sm {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allow Add Comment To Review
|
||||
merge_request: 51718
|
||||
author: Lee Tickett @leetickett
|
||||
type: added
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
|
|
@ -334,22 +334,28 @@ comment itself.
|
|||
|
||||

|
||||
|
||||
### Adding a new comment
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8225) in GitLab 13.10.
|
||||
|
||||
If you have a review in progress, you will be presented with the option to **Add to review**:
|
||||
|
||||

|
||||
|
||||
### Submitting a review
|
||||
|
||||
If you have any comments that have not been submitted, a bar displays at the
|
||||
bottom of the screen with two buttons:
|
||||
|
||||
- **Discard**: Discards all comments that have not been submitted.
|
||||
- **Finish review**: Opens a list of comments ready to be submitted for review.
|
||||
Clicking **Submit review** publishes all comments. Any quick actions
|
||||
submitted are performed at this time.
|
||||
- **Pending comments**: Opens a list of comments ready to be submitted for review.
|
||||
- **Submit review**: Publishes all comments. Any quick actions submitted are performed at this time.
|
||||
|
||||
Alternatively, to finish the entire review from a pending comment:
|
||||
|
||||
- Click the **Finish review** button on the comment.
|
||||
- Click the **Submit review** button on the comment.
|
||||
- Use the `/submit_review` [quick action](../project/quick_actions.md) in the text of non-review comment.
|
||||
|
||||

|
||||

|
||||
|
||||
Submitting the review sends a single email to every notifiable user of the
|
||||
merge request with all the comments associated to it.
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ The following table depicts the various user permission levels in a project.
|
|||
| Upload [Design Management](project/issues/design_management.md) files | | | ✓ | ✓ | ✓ |
|
||||
| Create/edit [releases](project/releases/index.md)| | | ✓ | ✓ | ✓ |
|
||||
| Delete [releases](project/releases/index.md)| | | | ✓ | ✓ |
|
||||
| Manage merge approval rules (project settings) | | | | ✓ | ✓ |
|
||||
| Create new merge request | | | ✓ | ✓ | ✓ |
|
||||
| Create new branches | | | ✓ | ✓ | ✓ |
|
||||
| Push to non-protected branches | | | ✓ | ✓ | ✓ |
|
||||
|
|
@ -300,6 +301,7 @@ group.
|
|||
| View Value Stream analytics | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| View Billing **(FREE SAAS)** | | | | | ✓ (4) |
|
||||
| View Usage Quotas **(FREE SAAS)** | | | | | ✓ (4) |
|
||||
| Manage [group push rules](group/index.md#group-push-rules) **(PREMIUM)** | | | | ✓ | ✓ |
|
||||
| View 2FA status of members | | | | | ✓ |
|
||||
| Filter members by 2FA status | | | | | ✓ |
|
||||
| Administer project compliance frameworks | | | | | ✓ |
|
||||
|
|
|
|||
|
|
@ -23591,7 +23591,7 @@ msgstr ""
|
|||
msgid "Please enter a valid number"
|
||||
msgstr ""
|
||||
|
||||
msgid "Please enter or upload a license."
|
||||
msgid "Please enter or upload a valid license."
|
||||
msgstr ""
|
||||
|
||||
msgid "Please fill in a descriptive name for your group."
|
||||
|
|
@ -31872,6 +31872,9 @@ msgstr ""
|
|||
msgid "This epic does not exist or you don't have sufficient permission."
|
||||
msgstr ""
|
||||
|
||||
msgid "This feature is part of your GitLab Ultimate trial."
|
||||
msgstr ""
|
||||
|
||||
msgid "This feature requires local storage to be enabled"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -36303,6 +36306,9 @@ msgstr ""
|
|||
msgid "Your new SCIM token"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your new comment"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your new personal access token has been created."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ RSpec.describe 'Merge request > Batch comments', :js do
|
|||
end
|
||||
|
||||
it 'adds draft note' do
|
||||
write_comment
|
||||
write_diff_comment
|
||||
|
||||
expect(find('.draft-note-component')).to have_content('Line is wrong')
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ RSpec.describe 'Merge request > Batch comments', :js do
|
|||
end
|
||||
|
||||
it 'publishes review' do
|
||||
write_comment
|
||||
write_diff_comment
|
||||
|
||||
page.within('.review-bar-content') do
|
||||
click_button 'Submit review'
|
||||
|
|
@ -52,7 +52,7 @@ RSpec.describe 'Merge request > Batch comments', :js do
|
|||
end
|
||||
|
||||
it 'publishes single comment' do
|
||||
write_comment
|
||||
write_diff_comment
|
||||
|
||||
click_button 'Add comment now'
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ RSpec.describe 'Merge request > Batch comments', :js do
|
|||
end
|
||||
|
||||
it 'deletes draft note' do
|
||||
write_comment
|
||||
write_diff_comment
|
||||
|
||||
accept_alert { find('.js-note-delete').click }
|
||||
|
||||
|
|
@ -74,23 +74,59 @@ RSpec.describe 'Merge request > Batch comments', :js do
|
|||
end
|
||||
|
||||
it 'edits draft note' do
|
||||
write_comment
|
||||
write_diff_comment
|
||||
|
||||
find('.js-note-edit').click
|
||||
|
||||
# make sure comment form is in view
|
||||
execute_script("window.scrollBy(0, 200)")
|
||||
|
||||
page.within('.js-discussion-note-form') do
|
||||
fill_in('note_note', with: 'Testing update')
|
||||
click_button('Save comment')
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
write_comment(text: 'Testing update', button_text: 'Save comment')
|
||||
|
||||
expect(page).to have_selector('.draft-note-component', text: 'Testing update')
|
||||
end
|
||||
|
||||
context 'adding single comment to review' do
|
||||
before do
|
||||
visit_overview
|
||||
end
|
||||
|
||||
it 'at first does not show `Add to review` and `Add comment now` buttons' do
|
||||
expect(page).to have_no_button('Add to review')
|
||||
expect(page).to have_no_button('Add comment now')
|
||||
end
|
||||
|
||||
context 'when review has started' do
|
||||
before do
|
||||
visit_diffs
|
||||
|
||||
write_diff_comment
|
||||
|
||||
visit_overview
|
||||
end
|
||||
|
||||
it 'can add comment to review' do
|
||||
write_comment(selector: '.js-main-target-form', field: 'note-body', text: 'Its a draft comment', button_text: 'Add to review')
|
||||
|
||||
expect(page).to have_selector('.draft-note-component', text: 'Its a draft comment')
|
||||
|
||||
click_button('Pending comments')
|
||||
|
||||
expect(page).to have_text('2 pending comments')
|
||||
end
|
||||
|
||||
it 'can add comment right away' do
|
||||
write_comment(selector: '.js-main-target-form', field: 'note-body', text: 'Its a regular comment', button_text: 'Add comment now')
|
||||
|
||||
expect(page).to have_selector('.note:not(.draft-note)', text: 'Its a regular comment')
|
||||
|
||||
click_button('Pending comments')
|
||||
|
||||
expect(page).to have_text('1 pending comment')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'in parallel diff' do
|
||||
before do
|
||||
find('.js-show-diff-settings').click
|
||||
|
|
@ -197,46 +233,51 @@ RSpec.describe 'Merge request > Batch comments', :js do
|
|||
wait_for_requests
|
||||
end
|
||||
|
||||
def write_comment(button_text: 'Start a review', text: 'Line is wrong')
|
||||
click_diff_line(find("[id='#{sample_compare.changes[0][:line_code]}']"))
|
||||
|
||||
page.within('.js-discussion-note-form') do
|
||||
fill_in('note_note', with: text)
|
||||
click_button(button_text)
|
||||
end
|
||||
def visit_overview
|
||||
visit project_merge_request_path(merge_request.project, merge_request)
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
def write_parallel_comment(line, button_text: 'Start a review', text: 'Line is wrong')
|
||||
def write_diff_comment(**params)
|
||||
click_diff_line(find("[id='#{sample_compare.changes[0][:line_code]}']"))
|
||||
|
||||
write_comment(**params)
|
||||
end
|
||||
|
||||
def write_parallel_comment(line, **params)
|
||||
find("td[id='#{line}']").hover
|
||||
find(".is-over button").click
|
||||
|
||||
page.within("form[data-line-code='#{line}']") do
|
||||
write_comment(selector: "form[data-line-code='#{line}']", **params)
|
||||
end
|
||||
|
||||
def write_comment(selector: '.js-discussion-note-form', field: 'note_note', button_text: 'Start a review', text: 'Line is wrong')
|
||||
page.within(selector) do
|
||||
fill_in(field, with: text)
|
||||
click_button(button_text)
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
def write_reply_to_discussion(button_text: 'Start a review', text: 'Line is wrong', resolve: false, unresolve: false)
|
||||
page.within(first('.diff-files-holder .discussion-reply-holder')) do
|
||||
find_field('Reply…', match: :first).click
|
||||
|
||||
fill_in('note_note', with: text)
|
||||
|
||||
if resolve
|
||||
page.check('Resolve thread')
|
||||
end
|
||||
|
||||
if unresolve
|
||||
page.check('Unresolve thread')
|
||||
end
|
||||
|
||||
click_button(button_text)
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
end
|
||||
|
||||
def write_reply_to_discussion(button_text: 'Start a review', text: 'Line is wrong', resolve: false, unresolve: false)
|
||||
page.within(first('.diff-files-holder .discussion-reply-holder')) do
|
||||
find_field('Reply…', match: :first).click
|
||||
|
||||
fill_in('note_note', with: text)
|
||||
|
||||
if resolve
|
||||
page.check('Resolve thread')
|
||||
end
|
||||
|
||||
if unresolve
|
||||
page.check('Unresolve thread')
|
||||
end
|
||||
|
||||
click_button(button_text)
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
|
|
|||
|
|
@ -124,4 +124,16 @@ describe('Batch comments draft preview item component', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('for new comment', () => {
|
||||
it('renders title', () => {
|
||||
createComponent(false, {}, (store) => {
|
||||
store.state.notes.discussions.push({});
|
||||
});
|
||||
|
||||
expect(vm.$el.querySelector('.review-preview-item-header-text').textContent).toContain(
|
||||
'Your new comment',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { GlDropdown, GlAlert } from '@gitlab/ui';
|
||||
import { GlAlert } from '@gitlab/ui';
|
||||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import Autosize from 'autosize';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import batchComments from '~/batch_comments/stores/modules/batch_comments';
|
||||
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
|
||||
import { deprecatedCreateFlash as flash } from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
|
@ -29,8 +30,10 @@ describe('issue_comment_form component', () => {
|
|||
|
||||
const findCloseReopenButton = () => wrapper.findByTestId('close-reopen-button');
|
||||
const findTextArea = () => wrapper.findByTestId('comment-field');
|
||||
const findAddToReviewButton = () => wrapper.findByTestId('add-to-review-button');
|
||||
const findAddCommentNowButton = () => wrapper.findByTestId('add-comment-now-button');
|
||||
const findConfidentialNoteCheckbox = () => wrapper.findByTestId('confidential-note-checkbox');
|
||||
const findCommentGlDropdown = () => wrapper.find(GlDropdown);
|
||||
const findCommentGlDropdown = () => wrapper.findByTestId('comment-button');
|
||||
const findCommentButton = () => findCommentGlDropdown().find('button');
|
||||
const findErrorAlerts = () => wrapper.findAllComponents(GlAlert).wrappers;
|
||||
|
||||
|
|
@ -582,4 +585,64 @@ describe('issue_comment_form component', () => {
|
|||
expect(findTextArea().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with batchComments in store', () => {
|
||||
beforeEach(() => {
|
||||
store.registerModule('batchComments', batchComments());
|
||||
});
|
||||
|
||||
describe('add to review and comment now buttons', () => {
|
||||
it('when no drafts exist, should not render', () => {
|
||||
mountComponent();
|
||||
|
||||
expect(findCommentGlDropdown().exists()).toBe(true);
|
||||
expect(findAddToReviewButton().exists()).toBe(false);
|
||||
expect(findAddCommentNowButton().exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('when drafts exist', () => {
|
||||
beforeEach(() => {
|
||||
store.state.batchComments.drafts = [{ note: 'A' }];
|
||||
});
|
||||
|
||||
it('should render', () => {
|
||||
mountComponent();
|
||||
|
||||
expect(findCommentGlDropdown().exists()).toBe(false);
|
||||
expect(findAddToReviewButton().exists()).toBe(true);
|
||||
expect(findAddCommentNowButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('clicking `add to review`, should call draft endpoint, set `isDraft` true', () => {
|
||||
mountComponent({ mountFunction: mount, initialData: { note: 'a draft note' } });
|
||||
|
||||
jest.spyOn(store, 'dispatch').mockResolvedValue();
|
||||
findAddToReviewButton().trigger('click');
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith(
|
||||
'saveNote',
|
||||
expect.objectContaining({
|
||||
endpoint: notesDataMock.draftsPath,
|
||||
isDraft: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('clicking `add comment now`, should call note endpoint, set `isDraft` false ', () => {
|
||||
mountComponent({ mountFunction: mount, initialData: { note: 'a comment' } });
|
||||
|
||||
jest.spyOn(store, 'dispatch').mockResolvedValue();
|
||||
findAddCommentNowButton().trigger('click');
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith(
|
||||
'saveNote',
|
||||
expect.objectContaining({
|
||||
endpoint: noteableDataMock.create_note_path,
|
||||
isDraft: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import AxiosMockAdapter from 'axios-mock-adapter';
|
|||
import $ from 'jquery';
|
||||
import Vue from 'vue';
|
||||
import { setTestTimeout } from 'helpers/timeout';
|
||||
import DraftNote from '~/batch_comments/components/draft_note.vue';
|
||||
import batchComments from '~/batch_comments/stores/modules/batch_comments';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import * as urlUtility from '~/lib/utils/url_utility';
|
||||
import CommentForm from '~/notes/components/comment_form.vue';
|
||||
|
|
@ -400,4 +402,34 @@ describe('note_app', () => {
|
|||
expect(getComponentOrder()).toStrictEqual([TYPE_NOTES_LIST, TYPE_COMMENT_FORM]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when multiple draft types are present', () => {
|
||||
beforeEach(() => {
|
||||
store = createStore();
|
||||
store.registerModule('batchComments', batchComments());
|
||||
store.state.batchComments.drafts = [
|
||||
{ line_code: 1, isDraft: true },
|
||||
{ discussion_id: 1, isDraft: true },
|
||||
{ note: 'A', isDraft: true },
|
||||
{ note: 'B', isDraft: true },
|
||||
];
|
||||
store.state.isLoading = false;
|
||||
wrapper = shallowMount(NotesApp, {
|
||||
propsData,
|
||||
store,
|
||||
stubs: {
|
||||
OrderedLayout,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('correctly finds only draft comments', () => {
|
||||
const drafts = wrapper.findAll(DraftNote).wrappers;
|
||||
|
||||
expect(drafts.map((x) => x.props('draft'))).toEqual([
|
||||
expect.objectContaining({ note: 'A' }),
|
||||
expect.objectContaining({ note: 'B' }),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ export const notesDataMock = {
|
|||
markdownDocsPath: '/help/user/markdown',
|
||||
newSessionPath: '/users/sign_in?redirect_to_referer=yes',
|
||||
notesPath: '/gitlab-org/gitlab-foss/noteable/issue/98/notes',
|
||||
draftsPath: '/flightjs/flight/-/merge_requests/4/drafts',
|
||||
quickActionsDocsPath: '/help/user/project/quick_actions',
|
||||
registerPath: '/users/sign_up?redirect_to_referer=yes',
|
||||
prerenderedNotesCount: 1,
|
||||
|
|
@ -1270,3 +1271,12 @@ export const batchSuggestionsInfoMock = [
|
|||
discussionId: 'c003',
|
||||
},
|
||||
];
|
||||
|
||||
export const draftComments = [
|
||||
{ id: 7, note: 'test draft note' },
|
||||
{ id: 9, note: 'draft note 2' },
|
||||
];
|
||||
|
||||
export const draftReply = { id: 8, note: 'draft reply', discussion_id: 1 };
|
||||
|
||||
export const draftDiffDiscussion = { id: 6, note: 'draft diff discussion', line_code: 1 };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { DESC } from '~/notes/constants';
|
||||
import { DESC, ASC } from '~/notes/constants';
|
||||
import * as getters from '~/notes/stores/getters';
|
||||
import {
|
||||
notesDataMock,
|
||||
|
|
@ -12,6 +12,9 @@ import {
|
|||
discussion3,
|
||||
resolvedDiscussion1,
|
||||
unresolvableDiscussion,
|
||||
draftComments,
|
||||
draftReply,
|
||||
draftDiffDiscussion,
|
||||
} from '../mock_data';
|
||||
|
||||
const discussionWithTwoUnresolvedNotes = 'merge_requests/resolved_diff_discussion.json';
|
||||
|
|
@ -23,6 +26,8 @@ const createDiscussionNeighborParams = (discussionId, diffOrder, step) => ({
|
|||
step,
|
||||
});
|
||||
|
||||
const asDraftDiscussion = (x) => ({ ...x, individual_note: true });
|
||||
|
||||
describe('Getters Notes Store', () => {
|
||||
let state;
|
||||
|
||||
|
|
@ -61,20 +66,58 @@ describe('Getters Notes Store', () => {
|
|||
});
|
||||
|
||||
describe('discussions', () => {
|
||||
it('should return all discussions in the store', () => {
|
||||
expect(getters.discussions(state)).toEqual([individualNote]);
|
||||
let batchComments = null;
|
||||
|
||||
const getDiscussions = () => getters.discussions(state, {}, { batchComments });
|
||||
|
||||
describe('without batchComments module', () => {
|
||||
it('should return all discussions in the store', () => {
|
||||
expect(getDiscussions()).toEqual([individualNote]);
|
||||
});
|
||||
|
||||
it('should transform discussion to individual notes in timeline view', () => {
|
||||
state.discussions = [discussionMock];
|
||||
state.isTimelineEnabled = true;
|
||||
|
||||
const discussions = getDiscussions();
|
||||
|
||||
expect(discussions.length).toEqual(discussionMock.notes.length);
|
||||
discussions.forEach((discussion) => {
|
||||
expect(discussion.individual_note).toBe(true);
|
||||
expect(discussion.id).toBe(discussion.notes[0].id);
|
||||
expect(discussion.created_at).toBe(discussion.notes[0].created_at);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should transform discussion to individual notes in timeline view', () => {
|
||||
state.discussions = [discussionMock];
|
||||
state.isTimelineEnabled = true;
|
||||
|
||||
expect(getters.discussions(state).length).toEqual(discussionMock.notes.length);
|
||||
getters.discussions(state).forEach((discussion) => {
|
||||
expect(discussion.individual_note).toBe(true);
|
||||
expect(discussion.id).toBe(discussion.notes[0].id);
|
||||
expect(discussion.created_at).toBe(discussion.notes[0].created_at);
|
||||
describe('with batchComments', () => {
|
||||
beforeEach(() => {
|
||||
batchComments = { drafts: [...draftComments, draftReply, draftDiffDiscussion] };
|
||||
});
|
||||
|
||||
it.each`
|
||||
discussionSortOrder | expectation
|
||||
${ASC} | ${[individualNote, ...draftComments.map(asDraftDiscussion)]}
|
||||
${DESC} | ${[...draftComments.reverse().map(asDraftDiscussion), individualNote]}
|
||||
`(
|
||||
'only appends draft comments (discussionSortOrder=$discussionSortOrder)',
|
||||
({ discussionSortOrder, expectation }) => {
|
||||
state.discussionSortOrder = discussionSortOrder;
|
||||
|
||||
expect(getDiscussions()).toEqual(expectation);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasDrafts', () => {
|
||||
it.each`
|
||||
rootGetters | expected
|
||||
${{}} | ${false}
|
||||
${{ 'batchComments/hasDrafts': true }} | ${true}
|
||||
${{ 'batchComments/hasDrafts': false }} | ${false}
|
||||
`('with rootGetters=$rootGetters, returns $expected', ({ rootGetters, expected }) => {
|
||||
expect(getters.hasDrafts({}, {}, {}, rootGetters)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -103,7 +146,7 @@ describe('Getters Notes Store', () => {
|
|||
};
|
||||
|
||||
it('should return a single system note when a description was updated multiple times', () => {
|
||||
expect(getters.discussions(stateCollapsedNotes).length).toEqual(1);
|
||||
expect(getters.discussions(stateCollapsedNotes, {}, {}).length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue