Commit 1a8c4d75 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch...

Merge branch '241800-replace-instances-of-droplab-with-gldropdown-vue-component-in-comment_type_toggle-js' into 'master'

Replace instances of DropLab with GlDropdown Vue component in `comment_type_toggle.js` [RUN-AS-IF-FOSS]

See merge request gitlab-org/gitlab!68110
parents 6b595617 63d66b8f
import DropLab from './droplab/drop_lab';
import ISetter from './droplab/plugins/input_setter';
// Todo: Remove this when fixing issue in input_setter plugin
const InputSetter = { ...ISetter };
class CommentTypeToggle {
constructor(opts = {}) {
this.dropdownTrigger = opts.dropdownTrigger;
this.dropdownList = opts.dropdownList;
this.noteTypeInput = opts.noteTypeInput;
this.submitButton = opts.submitButton;
this.closeButton = opts.closeButton;
this.reopenButton = opts.reopenButton;
}
initDroplab() {
this.droplab = new DropLab();
const config = this.setConfig();
this.droplab.init(this.dropdownTrigger, this.dropdownList, [InputSetter], config);
}
setConfig() {
const config = {
InputSetter: [
{
input: this.noteTypeInput,
valueAttribute: 'data-value',
},
{
input: this.submitButton,
valueAttribute: 'data-submit-text',
},
],
};
if (this.closeButton) {
config.InputSetter.push(
{
input: this.closeButton,
valueAttribute: 'data-close-text',
},
{
input: this.closeButton,
valueAttribute: 'data-close-text',
inputAttribute: 'data-alternative-text',
},
);
}
if (this.reopenButton) {
config.InputSetter.push(
{
input: this.reopenButton,
valueAttribute: 'data-reopen-text',
},
{
input: this.reopenButton,
valueAttribute: 'data-reopen-text',
inputAttribute: 'data-alternative-text',
},
);
}
return config;
}
}
export default CommentTypeToggle;
......@@ -19,9 +19,10 @@ import Vue from 'vue';
import '~/lib/utils/jquery_at_who';
import AjaxCache from '~/lib/utils/ajax_cache';
import syntaxHighlight from '~/syntax_highlight';
import CommentTypeDropdown from '~/notes/components/comment_type_dropdown.vue';
import * as constants from '~/notes/constants';
import Autosave from './autosave';
import loadAwardsHandler from './awards_handler';
import CommentTypeToggle from './comment_type_toggle';
import createFlash from './flash';
import { defaultAutocompleteConfig } from './gfm_auto_complete';
import GLForm from './gl_form';
......@@ -128,7 +129,13 @@ export default class Notes {
this.$wrapperEl.on('click', '.js-note-edit', this.showEditForm.bind(this));
this.$wrapperEl.on('click', '.note-edit-cancel', this.cancelEdit);
// Reopen and close actions for Issue/MR combined with note form submit
this.$wrapperEl.on('click', '.js-comment-submit-button', this.postComment);
this.$wrapperEl.on(
'click',
// this oddly written selector needs to match the old style (input with class) as
// well as the new DOM styling from the Vue-based note form
'input.js-comment-submit-button, .js-comment-submit-button > button:first-child',
this.postComment,
);
this.$wrapperEl.on('click', '.js-comment-save-button', this.updateComment);
this.$wrapperEl.on('keyup input', '.js-note-text', this.updateTargetButtons);
// resolve a discussion
......@@ -201,23 +208,39 @@ export default class Notes {
}
static initCommentTypeToggle(form) {
const dropdownTrigger = form.querySelector('.js-comment-type-dropdown .dropdown-toggle');
const dropdownList = form.querySelector('.js-comment-type-dropdown .dropdown-menu');
const el = form.querySelector('.js-comment-type-dropdown');
const { noteableName } = el.dataset;
const noteTypeInput = form.querySelector('#note_type');
const submitButton = form.querySelector('.js-comment-type-dropdown .js-comment-submit-button');
const closeButton = form.querySelector('.js-note-target-close');
const reopenButton = form.querySelector('.js-note-target-reopen');
const commentTypeToggle = new CommentTypeToggle({
dropdownTrigger,
dropdownList,
noteTypeInput,
submitButton,
closeButton,
reopenButton,
});
const formHasContent = form.querySelector('.js-note-text').value.trim().length > 0;
commentTypeToggle.initDroplab();
form.commentTypeComponent = new Vue({
el,
data() {
return {
noteType: constants.COMMENT,
disabled: !formHasContent,
};
},
render(createElement) {
return createElement(CommentTypeDropdown, {
props: {
noteType: this.noteType,
noteableDisplayName: noteableName,
disabled: this.disabled,
},
on: {
change: (arg) => {
this.noteType = arg;
if (this.noteType === constants.DISCUSSION) {
noteTypeInput.value = constants.DISCUSSION_NOTE;
} else {
noteTypeInput.value = '';
}
},
},
});
},
});
}
keydownNoteText(e) {
......@@ -1107,6 +1130,7 @@ export default class Notes {
const form = textarea.parents('form');
const reopenbtn = form.find('.js-note-target-reopen');
const closebtn = form.find('.js-note-target-close');
const commentTypeComponent = form.get(0)?.commentTypeComponent;
if (textarea.val().trim().length > 0) {
reopentext = reopenbtn.attr('data-alternative-text');
......@@ -1123,6 +1147,9 @@ export default class Notes {
if (closebtn.is(':not(.btn-comment-and-close)')) {
closebtn.addClass('btn-comment-and-close');
}
if (commentTypeComponent) {
commentTypeComponent.disabled = false;
}
} else {
reopentext = reopenbtn.data('originalText');
closetext = closebtn.data('originalText');
......@@ -1138,6 +1165,9 @@ export default class Notes {
if (closebtn.is('.btn-comment-and-close')) {
closebtn.removeClass('btn-comment-and-close');
}
if (commentTypeComponent) {
commentTypeComponent.disabled = true;
}
}
}
......@@ -1308,9 +1338,6 @@ export default class Notes {
}
cleanForm($form) {
// Remove JS classes that are not needed here
$form.find('.js-comment-type-dropdown').removeClass('btn-group');
// Remove dropdown
$form.find('.dropdown-menu').remove();
......@@ -1505,6 +1532,8 @@ export default class Notes {
const $submitBtn = $(e.target);
$submitBtn.prop('disabled', true);
let $form = $submitBtn.parents('form');
const commentTypeComponent = $form.get(0)?.commentTypeComponent;
if (commentTypeComponent) commentTypeComponent.disabled = true;
const $closeBtn = $form.find('.js-note-target-close');
const isDiscussionNote =
$submitBtn.parent().find('li.droplab-item-selected').attr('id') === 'discussion';
......@@ -1584,6 +1613,8 @@ export default class Notes {
const note = res.data;
$submitBtn.prop('disabled', false);
if (commentTypeComponent) commentTypeComponent.disabled = false;
// Submission successful! remove placeholder
$notesContainer.find(`#${noteUniqueId}`).remove();
......@@ -1662,6 +1693,8 @@ export default class Notes {
// Submission failed, remove placeholder note and show Flash error message
$notesContainer.find(`#${noteUniqueId}`).remove();
$submitBtn.prop('disabled', false);
if (commentTypeComponent) commentTypeComponent.disabled = false;
const blurEvent = new CustomEvent('blur.imageDiff', {
detail: e,
});
......
......@@ -96,7 +96,11 @@ export default {
data-track-action="click_button"
@click="$emit('click')"
>
<gl-dropdown-item is-check-item :is-checked="isNoteTypeComment" @click="setNoteTypeToComment">
<gl-dropdown-item
is-check-item
:is-checked="isNoteTypeComment"
@click.stop.prevent="setNoteTypeToComment"
>
<strong>{{ $options.i18n.submitButton.comment }}</strong>
<p class="gl-m-0">{{ commentDescription }}</p>
</gl-dropdown-item>
......@@ -105,7 +109,7 @@ export default {
is-check-item
:is-checked="isNoteTypeDiscussion"
data-qa-selector="discussion_menu_item"
@click="setNoteTypeToDiscussion"
@click.stop.prevent="setNoteTypeToDiscussion"
>
<strong>{{ $options.i18n.submitButton.startThread }}</strong>
<p class="gl-m-0">{{ startDiscussionDescription }}</p>
......
- noteable_name = @note.noteable.human_class_name
.float-left.btn-group.gl-sm-mr-3.droplab-dropdown.comment-type-dropdown.js-comment-type-dropdown
.js-comment-type-dropdown.float-left.gl-sm-mr-3{ data: { noteable_name: noteable_name } }
%input.btn.gl-button.btn-confirm.js-comment-button.js-comment-submit-button{ type: 'submit', value: _('Comment'), data: { qa_selector: 'comment_button' } }
- if @note.can_be_discussion_note?
= button_tag type: 'button', class: 'gl-button btn dropdown-toggle btn-confirm js-note-new-discussion js-disable-on-submit', data: { 'dropdown-trigger' => '#resolvable-comment-menu' }, 'aria-label' => _('Open comment type dropdown') do
= sprite_icon('chevron-down')
%ul#resolvable-comment-menu.dropdown-menu.dropdown-open-top{ data: { dropdown: true } }
%li#comment.droplab-item-selected{ data: { value: '', 'submit-text' => _('Comment'), 'close-text' => _("Comment & close %{noteable_name}") % { noteable_name: noteable_name }, 'reopen-text' => _("Comment & reopen %{noteable_name}") % { noteable_name: noteable_name } } }
%button.btn.gl-button.btn-default-tertiary
= sprite_icon('check', css_class: 'icon')
.description
%strong= _("Comment")
%p
= _("Add a general comment to this %{noteable_name}.") % { noteable_name: noteable_name }
%li.divider.droplab-item-ignore
%li#discussion{ data: { value: 'DiscussionNote', 'submit-text' => _('Start thread'), 'close-text' => _("Start thread & close %{noteable_name}") % { noteable_name: noteable_name }, 'reopen-text' => _("Start thread & reopen %{noteable_name}") % { noteable_name: noteable_name } } }
%button.btn.gl-button.btn-default-tertiary
= sprite_icon('check', css_class: 'icon')
.description
%strong= _("Start thread")
%p
= succeed '.' do
- if @note.noteable.supports_resolvable_notes?
= _('Discuss a specific suggestion or question that needs to be resolved')
- else
= _('Discuss a specific suggestion or question')
......@@ -1931,9 +1931,6 @@ msgstr ""
msgid "Add a general comment to this %{noteableDisplayName}."
msgstr ""
msgid "Add a general comment to this %{noteable_name}."
msgstr ""
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr ""
......@@ -8269,12 +8266,6 @@ msgstr ""
msgid "Comment"
msgstr ""
msgid "Comment & close %{noteable_name}"
msgstr ""
msgid "Comment & reopen %{noteable_name}"
msgstr ""
msgid "Comment & resolve thread"
msgstr ""
......@@ -11987,12 +11978,6 @@ msgstr ""
msgid "Discover|Upgrade now"
msgstr ""
msgid "Discuss a specific suggestion or question"
msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved"
msgstr ""
msgid "Discuss a specific suggestion or question that needs to be resolved."
msgstr ""
......@@ -23839,9 +23824,6 @@ msgstr ""
msgid "Open a CLI and connect to the cluster you want to install the Agent in. Use this installation method to minimize any manual steps. The token is already included in the command."
msgstr ""
msgid "Open comment type dropdown"
msgstr ""
msgid "Open epics"
msgstr ""
......@@ -32216,12 +32198,6 @@ msgstr ""
msgid "Start thread"
msgstr ""
msgid "Start thread & close %{noteable_name}"
msgstr ""
msgid "Start thread & reopen %{noteable_name}"
msgstr ""
msgid "Start your Free Ultimate Trial"
msgstr ""
......
......@@ -70,8 +70,8 @@ RSpec.describe 'Comments on personal snippets', :js do
context 'when submitting a note' do
it 'shows a valid form' do
is_expected.to have_css('.js-main-target-form', visible: true, count: 1)
expect(find('.js-main-target-form .js-comment-button').value)
.to eq('Comment')
expect(find('.js-main-target-form .js-comment-button button', match: :first))
.to have_content('Comment')
page.within('.js-main-target-form') do
expect(page).not_to have_link('Cancel')
......
import CommentTypeToggle from '~/comment_type_toggle';
import DropLab from '~/droplab/drop_lab';
import InputSetter from '~/droplab/plugins/input_setter';
describe('CommentTypeToggle', () => {
const testContext = {};
describe('class constructor', () => {
beforeEach(() => {
testContext.dropdownTrigger = {};
testContext.dropdownList = {};
testContext.noteTypeInput = {};
testContext.submitButton = {};
testContext.closeButton = {};
testContext.commentTypeToggle = new CommentTypeToggle({
dropdownTrigger: testContext.dropdownTrigger,
dropdownList: testContext.dropdownList,
noteTypeInput: testContext.noteTypeInput,
submitButton: testContext.submitButton,
closeButton: testContext.closeButton,
});
});
it('should set .dropdownTrigger', () => {
expect(testContext.commentTypeToggle.dropdownTrigger).toBe(testContext.dropdownTrigger);
});
it('should set .dropdownList', () => {
expect(testContext.commentTypeToggle.dropdownList).toBe(testContext.dropdownList);
});
it('should set .noteTypeInput', () => {
expect(testContext.commentTypeToggle.noteTypeInput).toBe(testContext.noteTypeInput);
});
it('should set .submitButton', () => {
expect(testContext.commentTypeToggle.submitButton).toBe(testContext.submitButton);
});
it('should set .closeButton', () => {
expect(testContext.commentTypeToggle.closeButton).toBe(testContext.closeButton);
});
it('should set .reopenButton', () => {
expect(testContext.commentTypeToggle.reopenButton).toBe(testContext.reopenButton);
});
});
describe('initDroplab', () => {
beforeEach(() => {
testContext.commentTypeToggle = {
dropdownTrigger: {},
dropdownList: {},
noteTypeInput: {},
submitButton: {},
closeButton: {},
setConfig: () => {},
};
testContext.config = {};
jest.spyOn(DropLab.prototype, 'init').mockImplementation();
jest.spyOn(DropLab.prototype, 'constructor').mockImplementation();
jest.spyOn(testContext.commentTypeToggle, 'setConfig').mockReturnValue(testContext.config);
CommentTypeToggle.prototype.initDroplab.call(testContext.commentTypeToggle);
});
it('should instantiate a DropLab instance and set .droplab', () => {
expect(testContext.commentTypeToggle.droplab instanceof DropLab).toBe(true);
});
it('should call .setConfig', () => {
expect(testContext.commentTypeToggle.setConfig).toHaveBeenCalled();
});
it('should call DropLab.prototype.init', () => {
expect(DropLab.prototype.init).toHaveBeenCalledWith(
testContext.commentTypeToggle.dropdownTrigger,
testContext.commentTypeToggle.dropdownList,
[InputSetter],
testContext.config,
);
});
});
describe('setConfig', () => {
describe('if no .closeButton is provided', () => {
beforeEach(() => {
testContext.commentTypeToggle = {
dropdownTrigger: {},
dropdownList: {},
noteTypeInput: {},
submitButton: {},
reopenButton: {},
};
testContext.setConfig = CommentTypeToggle.prototype.setConfig.call(
testContext.commentTypeToggle,
);
});
it('should not add .closeButton related InputSetter config', () => {
expect(testContext.setConfig).toEqual({
InputSetter: [
{
input: testContext.commentTypeToggle.noteTypeInput,
valueAttribute: 'data-value',
},
{
input: testContext.commentTypeToggle.submitButton,
valueAttribute: 'data-submit-text',
},
{
input: testContext.commentTypeToggle.reopenButton,
valueAttribute: 'data-reopen-text',
},
{
input: testContext.commentTypeToggle.reopenButton,
valueAttribute: 'data-reopen-text',
inputAttribute: 'data-alternative-text',
},
],
});
});
});
describe('if no .reopenButton is provided', () => {
beforeEach(() => {
testContext.commentTypeToggle = {
dropdownTrigger: {},
dropdownList: {},
noteTypeInput: {},
submitButton: {},
closeButton: {},
};
testContext.setConfig = CommentTypeToggle.prototype.setConfig.call(
testContext.commentTypeToggle,
);
});
it('should not add .reopenButton related InputSetter config', () => {
expect(testContext.setConfig).toEqual({
InputSetter: [
{
input: testContext.commentTypeToggle.noteTypeInput,
valueAttribute: 'data-value',
},
{
input: testContext.commentTypeToggle.submitButton,
valueAttribute: 'data-submit-text',
},
{
input: testContext.commentTypeToggle.closeButton,
valueAttribute: 'data-close-text',
},
{
input: testContext.commentTypeToggle.closeButton,
valueAttribute: 'data-close-text',
inputAttribute: 'data-alternative-text',
},
],
});
});
});
});
});
......@@ -47,8 +47,18 @@ describe('CommentTypeDropdown component', () => {
it('Should emit `change` event when clicking on an alternate dropdown option', () => {
mountComponent({ props: { noteType: constants.DISCUSSION } });
findCommentDropdownOption().vm.$emit('click');
findDiscussionDropdownOption().vm.$emit('click');
const event = {
type: 'click',
stopPropagation: jest.fn(),
preventDefault: jest.fn(),
};
findCommentDropdownOption().vm.$emit('click', event);
findDiscussionDropdownOption().vm.$emit('click', event);
// ensure the native events don't trigger anything
expect(event.stopPropagation).toHaveBeenCalledTimes(2);
expect(event.preventDefault).toHaveBeenCalledTimes(2);
expect(wrapper.emitted('change')[0]).toEqual([constants.COMMENT]);
expect(wrapper.emitted('change').length).toEqual(1);
......
......@@ -3,9 +3,9 @@
RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name|
let(:form_selector) { '.js-main-target-form' }
let(:dropdown_selector) { "#{form_selector} .comment-type-dropdown" }
let(:toggle_selector) { "#{dropdown_selector} .dropdown-toggle" }
let(:toggle_selector) { "#{dropdown_selector} .gl-dropdown-toggle" }
let(:menu_selector) { "#{dropdown_selector} .dropdown-menu" }
let(:submit_selector) { "#{form_selector} .js-comment-submit-button" }
let(:submit_selector) { "#{form_selector} .js-comment-submit-button > button:first-child" }
let(:close_selector) { "#{form_selector} .btn-comment-and-close" }
let(:comments_selector) { '.timeline > .note.timeline-entry:not(.being-posted)' }
let(:comment) { 'My comment' }
......@@ -43,13 +43,11 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name
expect(items.first).to have_content 'Comment'
expect(items.first).to have_content "Add a general comment to this #{resource_name}."
expect(items.first).to have_selector '[data-testid="check-icon"]'
expect(items.first['class']).to match 'droplab-item-selected'
expect(items.first).to have_selector '[data-testid="dropdown-item-checkbox"]'
expect(items.last).to have_content 'Start thread'
expect(items.last).to have_content "Discuss a specific suggestion or question#{' that needs to be resolved' if resource_name == 'merge request'}."
expect(items.last).not_to have_selector '[data-testid="check-icon"]'
expect(items.last['class']).not_to match 'droplab-item-selected'
expect(items.last).not_to have_selector '[data-testid="dropdown-item-checkbox"]'
end
it 'closes the menu when clicking the toggle or body' do
......@@ -75,14 +73,14 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name
expect(find(dropdown_selector)).to have_content 'Comment'
find(toggle_selector).click
execute_script("document.querySelector('#{menu_selector} .divider').click()")
execute_script("document.querySelector('#{menu_selector} .dropdown-divider').click()")
else
execute_script("document.querySelector('#{menu_selector}').click()")
expect(page).to have_selector menu_selector
expect(find(dropdown_selector)).to have_content 'Comment'
execute_script("document.querySelector('#{menu_selector} .divider').click()")
execute_script("document.querySelector('#{menu_selector} .dropdown-divider').click()")
expect(page).to have_selector menu_selector
end
......@@ -97,7 +95,7 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name
end
it 'updates the submit button text and closes the dropdown' do
expect(find(submit_selector).value).to eq 'Start thread'
expect(find(submit_selector).text).to eq 'Start thread'
expect(page).not_to have_selector menu_selector
end
......@@ -137,12 +135,10 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name
items = all("#{menu_selector} li")
expect(items.first).to have_content 'Comment'
expect(items.first).not_to have_selector '[data-testid="check-icon"]'
expect(items.first['class']).not_to match 'droplab-item-selected'
expect(items.first).not_to have_selector '[data-testid="dropdown-item-checkbox"]'
expect(items.last).to have_content 'Start thread'
expect(items.last).to have_selector '[data-testid="check-icon"]'
expect(items.last['class']).to match 'droplab-item-selected'
expect(items.last).to have_selector '[data-testid="dropdown-item-checkbox"]'
end
describe 'when selecting "Comment"' do
......@@ -153,7 +149,7 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name
it 'updates the submit button text and closes the dropdown' do
button = find(submit_selector)
expect(button.value).to eq 'Comment'
expect(button.text).to eq 'Comment'
expect(page).not_to have_selector menu_selector
end
......@@ -166,12 +162,10 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name
aggregate_failures do
expect(items.first).to have_content 'Comment'
expect(items.first).to have_selector '[data-testid="check-icon"]'
expect(items.first['class']).to match 'droplab-item-selected'
expect(items.first).to have_selector '[data-testid="dropdown-item-checkbox"]'
expect(items.last).to have_content 'Start thread'
expect(items.last).not_to have_selector '[data-testid="check-icon"]'
expect(items.last['class']).not_to match 'droplab-item-selected'
expect(items.last).not_to have_selector '[data-testid="dropdown-item-checkbox"]'
end
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