Commit 5f8f555a authored by Jacob Schatz's avatar Jacob Schatz

Merge branch '30281-report-abusive-content-from-an-issue-or-merge-request' into 'master'

Resolve "Report abusive content from an issue or merge request"

Closes #30281

See merge request !11786
parents 5b257778 63fc3935
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 = Object.assign({}, ISetter);
class CloseReopenReportToggle {
constructor(opts = {}) {
this.dropdownTrigger = opts.dropdownTrigger;
this.dropdownList = opts.dropdownList;
this.button = opts.button;
}
initDroplab() {
this.reopenItem = this.dropdownList.querySelector('.reopen-item');
this.closeItem = this.dropdownList.querySelector('.close-item');
this.droplab = new DropLab();
const config = this.setConfig();
this.droplab.init(this.dropdownTrigger, this.dropdownList, [InputSetter], config);
}
updateButton(isClosed) {
this.toggleButtonType(isClosed);
this.button.blur();
}
toggleButtonType(isClosed) {
const [showItem, hideItem] = this.getButtonTypes(isClosed);
showItem.classList.remove('hidden');
showItem.classList.add('droplab-item-selected');
hideItem.classList.add('hidden');
hideItem.classList.remove('droplab-item-selected');
showItem.click();
}
getButtonTypes(isClosed) {
return isClosed ? [this.reopenItem, this.closeItem] : [this.closeItem, this.reopenItem];
}
setDisable(shouldDisable = true) {
if (shouldDisable) {
this.button.setAttribute('disabled', 'true');
this.dropdownTrigger.setAttribute('disabled', 'true');
} else {
this.button.removeAttribute('disabled');
this.dropdownTrigger.removeAttribute('disabled');
}
}
setConfig() {
const config = {
InputSetter: [
{
input: this.button,
valueAttribute: 'data-text',
inputAttribute: 'data-value',
},
{
input: this.button,
valueAttribute: 'data-text',
inputAttribute: 'title',
},
{
input: this.button,
valueAttribute: 'data-button-class',
inputAttribute: 'class',
},
{
input: this.dropdownTrigger,
valueAttribute: 'data-toggle-class',
inputAttribute: 'class',
},
{
input: this.button,
valueAttribute: 'data-url',
inputAttribute: 'href',
},
{
input: this.button,
valueAttribute: 'data-method',
inputAttribute: 'data-method',
},
],
};
return config;
}
}
export default CloseReopenReportToggle;
import DropLab from './droplab/drop_lab'; import DropLab from './droplab/drop_lab';
import InputSetter from './droplab/plugins/input_setter'; import ISetter from './droplab/plugins/input_setter';
// Todo: Remove this when fixing issue in input_setter plugin
const InputSetter = Object.assign({}, ISetter);
class CommentTypeToggle { class CommentTypeToggle {
constructor(opts = {}) { constructor(opts = {}) {
......
import CloseReopenReportToggle from '../close_reopen_report_toggle';
function initCloseReopenReport() {
const container = document.querySelector('.js-issuable-close-dropdown');
if (!container) return undefined;
const dropdownTrigger = container.querySelector('.js-issuable-close-toggle');
const dropdownList = container.querySelector('.js-issuable-close-menu');
const button = container.querySelector('.js-issuable-close-button');
const closeReopenReportToggle = new CloseReopenReportToggle({
dropdownTrigger,
dropdownList,
button,
});
closeReopenReportToggle.initDroplab();
return closeReopenReportToggle;
}
const IssuablesHelper = {
initCloseReopenReport,
};
export default IssuablesHelper;
...@@ -6,6 +6,7 @@ import '~/lib/utils/text_utility'; ...@@ -6,6 +6,7 @@ import '~/lib/utils/text_utility';
import './flash'; import './flash';
import TaskList from './task_list'; import TaskList from './task_list';
import CreateMergeRequestDropdown from './create_merge_request_dropdown'; import CreateMergeRequestDropdown from './create_merge_request_dropdown';
import IssuablesHelper from './helpers/issuables_helper';
class Issue { class Issue {
constructor() { constructor() {
...@@ -28,6 +29,11 @@ class Issue { ...@@ -28,6 +29,11 @@ class Issue {
Issue.initMergeRequests(); Issue.initMergeRequests();
Issue.initRelatedBranches(); Issue.initRelatedBranches();
this.closeButtons = $('a.btn-close');
this.reopenButtons = $('a.btn-reopen');
this.initCloseReopenReport();
if (Issue.createMrDropdownWrap) { if (Issue.createMrDropdownWrap) {
this.createMergeRequestDropdown = new CreateMergeRequestDropdown(Issue.createMrDropdownWrap); this.createMergeRequestDropdown = new CreateMergeRequestDropdown(Issue.createMrDropdownWrap);
} }
...@@ -35,13 +41,8 @@ class Issue { ...@@ -35,13 +41,8 @@ class Issue {
initIssueBtnEventListeners() { initIssueBtnEventListeners() {
const issueFailMessage = 'Unable to update this issue at this time.'; const issueFailMessage = 'Unable to update this issue at this time.';
const closeButtons = $('a.btn-close');
const isClosedBadge = $('div.status-box-closed');
const isOpenBadge = $('div.status-box-open');
const projectIssuesCounter = $('.issue_counter');
const reopenButtons = $('a.btn-reopen');
return closeButtons.add(reopenButtons).on('click', (e) => { return $(document).on('click', 'a.btn-close, a.btn-reopen', (e) => {
var $button, shouldSubmit, url; var $button, shouldSubmit, url;
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
...@@ -50,7 +51,9 @@ class Issue { ...@@ -50,7 +51,9 @@ class Issue {
if (shouldSubmit) { if (shouldSubmit) {
Issue.submitNoteForm($button.closest('form')); Issue.submitNoteForm($button.closest('form'));
} }
$button.prop('disabled', true);
this.disableCloseReopenButton($button);
url = $button.attr('href'); url = $button.attr('href');
return $.ajax({ return $.ajax({
type: 'PUT', type: 'PUT',
...@@ -58,15 +61,19 @@ class Issue { ...@@ -58,15 +61,19 @@ class Issue {
}) })
.fail(() => new Flash(issueFailMessage)) .fail(() => new Flash(issueFailMessage))
.done((data) => { .done((data) => {
const isClosedBadge = $('div.status-box-closed');
const isOpenBadge = $('div.status-box-open');
const projectIssuesCounter = $('.issue_counter');
if ('id' in data) { if ('id' in data) {
$(document).trigger('issuable:change'); $(document).trigger('issuable:change');
const isClosed = $button.hasClass('btn-close'); const isClosed = $button.hasClass('btn-close');
closeButtons.toggleClass('hidden', isClosed);
reopenButtons.toggleClass('hidden', !isClosed);
isClosedBadge.toggleClass('hidden', !isClosed); isClosedBadge.toggleClass('hidden', !isClosed);
isOpenBadge.toggleClass('hidden', isClosed); isOpenBadge.toggleClass('hidden', isClosed);
this.toggleCloseReopenButton(isClosed);
let numProjectIssues = Number(projectIssuesCounter.text().replace(/[^\d]/, '')); let numProjectIssues = Number(projectIssuesCounter.text().replace(/[^\d]/, ''));
numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1; numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
projectIssuesCounter.text(gl.text.addDelimiter(numProjectIssues)); projectIssuesCounter.text(gl.text.addDelimiter(numProjectIssues));
...@@ -83,12 +90,34 @@ class Issue { ...@@ -83,12 +90,34 @@ class Issue {
} else { } else {
new Flash(issueFailMessage); new Flash(issueFailMessage);
} }
})
$button.prop('disabled', false); .then(() => {
this.disableCloseReopenButton($button, false);
}); });
}); });
} }
initCloseReopenReport() {
this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
if (this.closeButtons) this.closeButtons = this.closeButtons.not('.issuable-close-button');
if (this.reopenButtons) this.reopenButtons = this.reopenButtons.not('.issuable-close-button');
}
disableCloseReopenButton($button, shouldDisable) {
if (this.closeReopenReportToggle) {
this.closeReopenReportToggle.setDisable(shouldDisable);
} else {
$button.prop('disabled', shouldDisable);
}
}
toggleCloseReopenButton(isClosed) {
if (this.closeReopenReportToggle) this.closeReopenReportToggle.updateButton(isClosed);
this.closeButtons.toggleClass('hidden', isClosed);
this.reopenButtons.toggleClass('hidden', !isClosed);
}
static submitNoteForm(form) { static submitNoteForm(form) {
var noteText; var noteText;
noteText = form.find("textarea.js-note-text").val(); noteText = form.find("textarea.js-note-text").val();
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'vendor/jquery.waitforimages'; import 'vendor/jquery.waitforimages';
import TaskList from './task_list'; import TaskList from './task_list';
import './merge_request_tabs'; import './merge_request_tabs';
import IssuablesHelper from './helpers/issuables_helper';
(function() { (function() {
this.MergeRequest = (function() { this.MergeRequest = (function() {
...@@ -21,9 +22,12 @@ import './merge_request_tabs'; ...@@ -21,9 +22,12 @@ import './merge_request_tabs';
return _this.showAllCommits(); return _this.showAllCommits();
}; };
})(this)); })(this));
this.initTabs(); this.initTabs();
this.initMRBtnListeners(); this.initMRBtnListeners();
this.initCommitMessageListeners(); this.initCommitMessageListeners();
this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
if ($("a.btn-close").length) { if ($("a.btn-close").length) {
this.taskList = new TaskList({ this.taskList = new TaskList({
dataType: 'merge_request', dataType: 'merge_request',
...@@ -64,11 +68,15 @@ import './merge_request_tabs'; ...@@ -64,11 +68,15 @@ import './merge_request_tabs';
if (shouldSubmit && $this.data('submitted')) { if (shouldSubmit && $this.data('submitted')) {
return; return;
} }
if (this.closeReopenReportToggle) this.closeReopenReportToggle.setDisable();
if (shouldSubmit) { if (shouldSubmit) {
if ($this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')) { if ($this.hasClass('btn-comment-and-close') || $this.hasClass('btn-comment-and-reopen')) {
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
return _this.submitNoteForm($this.closest('form'), $this);
_this.submitNoteForm($this.closest('form'), $this);
} }
} }
}); });
......
...@@ -20,17 +20,29 @@ ...@@ -20,17 +20,29 @@
color: $text; color: $text;
border-color: $border; border-color: $border;
> .icon {
color: $text;
}
&:hover, &:hover,
&:focus { &:focus {
background-color: $hover-background; background-color: $hover-background;
border-color: $hover-border; border-color: $hover-border;
color: $hover-text; color: $hover-text;
> .icon {
color: $hover-text;
}
} }
&:active { &:active {
background-color: $active-background; background-color: $active-background;
border-color: $active-border; border-color: $active-border;
color: $hover-text; color: $hover-text;
> .icon {
color: $hover-text;
}
} }
} }
...@@ -163,7 +175,8 @@ ...@@ -163,7 +175,8 @@
@include btn-orange; @include btn-orange;
} }
&.btn-close { &.btn-close,
&.btn-close-color {
@include btn-outline($white-light, $orange-600, $orange-500, $orange-500, $white-light, $orange-600, $orange-600, $orange-700); @include btn-outline($white-light, $orange-600, $orange-500, $orange-500, $white-light, $orange-600, $orange-600, $orange-700);
} }
...@@ -181,7 +194,8 @@ ...@@ -181,7 +194,8 @@
float: right; float: right;
} }
&.btn-reopen { &.btn-reopen,
.btn-reopen-color {
/* should be same as parent class for now */ /* should be same as parent class for now */
} }
......
...@@ -295,9 +295,74 @@ ...@@ -295,9 +295,74 @@
} }
} }
.filtered-search-box-input-container .dropdown-menu, .droplab-dropdown {
.filtered-search-box-input-container .dropdown-menu-nav, .description {
.comment-type-dropdown .dropdown-menu { display: inline-block;
white-space: normal;
margin-left: 5px;
}
.dropdown-toggle > i {
pointer-events: none;
}
li {
padding: $gl-btn-padding $gl-btn-padding 2px;
cursor: pointer;
> a,
> button {
display: flex;
margin: 0;
padding: 0;
border-radius: 0;
text-overflow: inherit;
background-color: inherit;
color: inherit;
border: inherit;
text-align: left;
&:hover,
&:focus {
background-color: inherit;
color: inherit;
}
&.btn .fa:not(:last-child) {
margin-left: 5px;
}
}
&:hover,
&:focus {
background-color: $dropdown-hover-color;
color: $white-light;
}
&.droplab-item-selected i {
visibility: visible;
}
.icon {
visibility: hidden;
}
}
.icon {
display: inline-block;
vertical-align: top;
padding-top: 2px;
}
.divider {
margin: 0 8px;
padding: 0;
border-top: $gray-darkest;
}
}
.droplab-dropdown .dropdown-menu,
.droplab-dropdown .dropdown-menu-nav {
display: none; display: none;
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
......
...@@ -70,6 +70,13 @@ ...@@ -70,6 +70,13 @@
.input-token { .input-token {
max-width: 200px; max-width: 200px;
padding: 0;
&:hover,
&:focus {
background-color: inherit;
color: inherit;
}
} }
.input-token:only-child, .input-token:only-child,
...@@ -156,6 +163,16 @@ ...@@ -156,6 +163,16 @@
} }
} }
.droplab-dropdown li.filtered-search-token {
padding: 0;
&:hover,
&:focus {
background-color: inherit;
color: inherit;
}
}
.filtered-search-term { .filtered-search-term {
.name { .name {
background-color: inherit; background-color: inherit;
......
...@@ -799,3 +799,28 @@ ...@@ -799,3 +799,28 @@
} }
} }
} }
.issuable-close-button,
.issuable-close-toggle {
@include transition(border-color, color);
}
.issuable-close-dropdown {
.dropdown-menu {
min-width: 270px;
left: auto;
right: 0;
}
.description {
margin-bottom: 10px;
.text {
margin: 0;
}
}
.dropdown-toggle > .icon {
margin: 0 3px;
}
}
...@@ -356,7 +356,6 @@ ...@@ -356,7 +356,6 @@
color: $white-light; color: $white-light;
padding-right: 2px; padding-right: 2px;
margin-top: 2px; margin-top: 2px;
pointer-events: none;
} }
} }
...@@ -366,56 +365,6 @@ ...@@ -366,56 +365,6 @@
width: 298px; width: 298px;
} }
.description {
display: inline-block;
white-space: normal;
margin-left: 8px;
padding-right: 33px;
}
li {
padding-top: 6px;
& > a {
margin: 0;
padding: 0;
color: inherit;
border-radius: 0;
text-overflow: inherit;
&:hover,
&:focus {
background-color: inherit;
color: inherit;
}
}
&:hover,
&:focus {
background-color: $dropdown-hover-color;
color: $white-light;
}
&.droplab-item-selected i {
visibility: visible;
}
i {
visibility: hidden;
}
}
i {
display: inline-block;
vertical-align: top;
padding-top: 2px;
}
.divider {
margin: 0 8px;
padding: 0;
border-top: $gray-darkest;
}
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
display: flex; display: flex;
......
...@@ -17,8 +17,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont ...@@ -17,8 +17,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
end end
def merge_request_params def merge_request_params
params.require(:merge_request) params.require(:merge_request).permit(merge_request_params_attributes)
.permit(merge_request_params_attributes)
end end
def merge_request_params_attributes def merge_request_params_attributes
......
...@@ -245,6 +245,53 @@ module IssuablesHelper ...@@ -245,6 +245,53 @@ module IssuablesHelper
@counts[cache_key][state] @counts[cache_key][state]
end end
def close_issuable_url(issuable)
issuable_url(issuable, close_reopen_params(issuable, :close))
end
def reopen_issuable_url(issuable)
issuable_url(issuable, close_reopen_params(issuable, :reopen))
end
def close_reopen_issuable_url(issuable, should_inverse = false)
issuable.closed? ^ should_inverse ? reopen_issuable_url(issuable) : close_issuable_url(issuable)
end
def issuable_url(issuable, *options)
case issuable
when Issue
issue_url(issuable, *options)
when MergeRequest
merge_request_url(issuable, *options)
end
end
def issuable_button_visibility(issuable, closed)
case issuable
when Issue
issue_button_visibility(issuable, closed)
when MergeRequest
merge_request_button_visibility(issuable, closed)
end
end
def issuable_close_reopen_button_method(issuable)
case issuable
when Issue
''
when MergeRequest
'put'
end
end
def issuable_author_is_current_user(issuable)
issuable.author == current_user
end
def issuable_display_type(issuable)
issuable.model_name.human.downcase
end
private private
def sidebar_gutter_collapsed? def sidebar_gutter_collapsed?
...@@ -270,8 +317,6 @@ module IssuablesHelper ...@@ -270,8 +317,6 @@ module IssuablesHelper
issue_template_names issue_template_names
when MergeRequest when MergeRequest
merge_request_template_names merge_request_template_names
else
raise 'Unknown issuable type!'
end end
end end
...@@ -301,4 +346,12 @@ module IssuablesHelper ...@@ -301,4 +346,12 @@ module IssuablesHelper
container: (is_collapsed ? 'body' : nil) container: (is_collapsed ? 'body' : nil)
} }
end end
def close_reopen_params(issuable, action)
{
issuable.model_name.to_s.underscore => { state_event: action }
}.tap do |params|
params[:format] = :json if issuable.is_a?(Issue)
end
end
end end
...@@ -30,24 +30,23 @@ ...@@ -30,24 +30,23 @@
.dropdown-menu.dropdown-menu-align-right.hidden-lg .dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul %ul
- if can_update_issue - if can_update_issue
%li %li= link_to 'Edit', edit_project_issue_path(@project, @issue)
= link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'issuable-edit' - unless current_user == @issue.author
%li %li= link_to 'Report abuse', new_abuse_report_path(user_id: @issue.author.id, ref_url: issue_url(@issue))
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' - if can_update_issue
%li %li= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' %li= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- if can_report_spam - if can_report_spam
%li %li= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
- if can_update_issue || can_report_spam - if can_update_issue || can_report_spam
%li.divider %li.divider
%li %li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
- if can_update_issue - if can_update_issue
= link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit' = link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit'
= link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue'
= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' = render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue
- if can_report_spam - if can_report_spam
= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam' = link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
= link_to new_project_issue_path(@project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do = link_to new_project_issue_path(@project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
......
- can_update_merge_request = can?(current_user, :update_merge_request, @merge_request)
- if @merge_request.closed_without_fork? - if @merge_request.closed_without_fork?
.alert.alert-danger .alert.alert-danger
%p The source project of this merge request has been removed. %p The source project of this merge request has been removed.
...@@ -15,21 +17,24 @@ ...@@ -15,21 +17,24 @@
.issuable-meta .issuable-meta
= issuable_meta(@merge_request, @project, "Merge request") = issuable_meta(@merge_request, @project, "Merge request")
- if can?(current_user, :update_merge_request, @merge_request) .issuable-actions
.issuable-actions .clearfix.issue-btn-group.dropdown
.clearfix.issue-btn-group.dropdown %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } }
%button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } Options
Options = icon('caret-down')
= icon('caret-down') .dropdown-menu.dropdown-menu-align-right.hidden-lg
.dropdown-menu.dropdown-menu-align-right.hidden-lg %ul
%ul - if can_update_merge_request
%li= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit'
- unless current_user == @merge_request.author
%li= link_to 'Report abuse', new_abuse_report_path(user_id: @merge_request.author.id, ref_url: merge_request_url(@merge_request))
- if can_update_merge_request
%li{ class: merge_request_button_visibility(@merge_request, true) } %li{ class: merge_request_button_visibility(@merge_request, true) }
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request' = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
%li{ class: merge_request_button_visibility(@merge_request, false) } %li{ class: merge_request_button_visibility(@merge_request, false) }
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request' = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
%li
= link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: 'issuable-edit' - if can_update_merge_request
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{merge_request_button_visibility(@merge_request, true)}", title: 'Close merge request' = link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit"
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen reopen-mr-link #{merge_request_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
= link_to edit_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" do = render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request
Edit
- is_current_user = issuable_author_is_current_user(issuable)
- display_issuable_type = issuable_display_type(issuable)
- button_method = issuable_close_reopen_button_method(issuable)
- if can_update && is_current_user
= link_to "Close #{display_issuable_type}", close_issuable_url(issuable), method: button_method,
class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issuable_button_visibility(issuable, true)}", title: "Close #{display_issuable_type}"
= link_to "Reopen #{display_issuable_type}", reopen_issuable_url(issuable), method: button_method,
class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}"
- elsif can_update && !is_current_user
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable
- else
= link_to 'Report abuse', new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
class: 'hidden-xs hidden-sm btn btn-grouped btn-close-color', title: 'Report abuse'
- display_issuable_type = issuable_display_type(issuable)
- button_action = issuable.closed? ? 'reopen' : 'close'
- display_button_action = button_action.capitalize
- button_responsive_class = 'hidden-xs hidden-sm'
- button_class = "#{button_responsive_class} btn btn-grouped js-issuable-close-button issuable-close-button"
- toggle_class = "#{button_responsive_class} btn btn-nr dropdown-toggle js-issuable-close-toggle"
- button_method = issuable_close_reopen_button_method(issuable)
.pull-left.btn-group.prepend-left-10.issuable-close-dropdown.droplab-dropdown.js-issuable-close-dropdown
= link_to "#{display_button_action} #{display_issuable_type}", close_reopen_issuable_url(issuable),
method: button_method, class: "#{button_class} btn-#{button_action}", title: "#{display_button_action} #{display_issuable_type}"
= button_tag type: 'button', class: "#{toggle_class} btn-#{button_action}-color",
data: { 'dropdown-trigger' => '#issuable-close-menu' }, 'aria-label' => 'Toggle dropdown' do
= icon('caret-down', class: 'toggle-icon icon')
%ul#issuable-close-menu.js-issuable-close-menu.dropdown-menu{ class: button_responsive_class, data: { dropdown: true } }
%li.close-item{ class: "#{issuable_button_visibility(issuable, true) || 'droplab-item-selected'}",
data: { text: "Close #{display_issuable_type}", url: close_issuable_url(issuable),
button_class: "#{button_class} btn-close", toggle_class: "#{toggle_class} btn-close-color", method: button_method } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
.description
%strong.title
Close
= display_issuable_type
%li.reopen-item{ class: "#{issuable_button_visibility(issuable, false) || 'droplab-item-selected'}",
data: { text: "Reopen #{display_issuable_type}", url: reopen_issuable_url(issuable),
button_class: "#{button_class} btn-reopen", toggle_class: "#{toggle_class} btn-reopen-color", method: button_method } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
.description
%strong.title
Reopen
= display_issuable_type
%li.divider.droplab-item-ignore
%li.report-item{ data: { text: 'Report abuse', url: new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
button_class: "#{button_class} btn-close-color", toggle_class: "#{toggle_class} btn-close-color", method: '' } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
.description
%strong.title Report abuse
%p.text
Report
= display_issuable_type.pluralize
that are abusive, inappropriate or spam.
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
content_class: "filtered-search-history-dropdown-content", content_class: "filtered-search-history-dropdown-content",
title: "Recent searches" }) do title: "Recent searches" }) do
.js-filtered-search-history-dropdown{ data: { project_full_path: @project.full_path } } .js-filtered-search-history-dropdown{ data: { project_full_path: @project.full_path } }
.filtered-search-box-input-container .filtered-search-box-input-container.droplab-dropdown
.scroll-container .scroll-container
%ul.tokens-container.list-unstyled %ul.tokens-container.list-unstyled
%li.input-token %li.input-token
......
- noteable_name = @note.noteable.human_class_name - noteable_name = @note.noteable.human_class_name
.pull-left.btn-group.append-right-10.comment-type-dropdown.js-comment-type-dropdown .pull-left.btn-group.append-right-10.droplab-dropdown.comment-type-dropdown.js-comment-type-dropdown
%input.btn.btn-nr.btn-create.comment-btn.js-comment-button.js-comment-submit-button{ type: 'submit', value: 'Comment' } %input.btn.btn-nr.btn-create.comment-btn.js-comment-button.js-comment-submit-button{ type: 'submit', value: 'Comment' }
- if @note.can_be_discussion_note? - if @note.can_be_discussion_note?
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
%ul#resolvable-comment-menu.dropdown-menu{ data: { dropdown: true } } %ul#resolvable-comment-menu.dropdown-menu{ data: { dropdown: true } }
%li#comment.droplab-item-selected{ data: { value: '', 'submit-text' => 'Comment', 'close-text' => "Comment & close #{noteable_name}", 'reopen-text' => "Comment & reopen #{noteable_name}" } } %li#comment.droplab-item-selected{ data: { value: '', 'submit-text' => 'Comment', 'close-text' => "Comment & close #{noteable_name}", 'reopen-text' => "Comment & reopen #{noteable_name}" } }
%a{ href: '#' } %button.btn.btn-transparent
= icon('check') = icon('check', class: 'icon')
.description .description
%strong Comment %strong Comment
%p %p
...@@ -19,8 +19,8 @@ ...@@ -19,8 +19,8 @@
%li.divider.droplab-item-ignore %li.divider.droplab-item-ignore
%li#discussion{ data: { value: 'DiscussionNote', 'submit-text' => 'Start discussion', 'close-text' => "Start discussion & close #{noteable_name}", 'reopen-text' => "Start discussion & reopen #{noteable_name}" } } %li#discussion{ data: { value: 'DiscussionNote', 'submit-text' => 'Start discussion', 'close-text' => "Start discussion & close #{noteable_name}", 'reopen-text' => "Start discussion & reopen #{noteable_name}" } }
%a{ href: '#' } %button.btn.btn-transparent
= icon('check') = icon('check', class: 'icon')
.description .description
%strong Start discussion %strong Start discussion
%p %p
......
require 'spec_helper'
describe 'Issuables Close/Reopen/Report toggle', :feature do
let(:user) { create(:user) }
shared_examples 'an issuable close/reopen/report toggle' do
let(:container) { find('.issuable-close-dropdown') }
let(:human_model_name) { issuable.model_name.human.downcase }
it 'shows toggle' do
expect(page).to have_link("Close #{human_model_name}")
expect(page).to have_selector('.issuable-close-dropdown')
end
it 'opens a dropdown when toggle is clicked' do
container.find('.dropdown-toggle').click
expect(container).to have_selector('.dropdown-menu')
expect(container).to have_content("Close #{human_model_name}")
expect(container).to have_content('Report abuse')
expect(container).to have_content("Report #{human_model_name.pluralize} that are abusive, inappropriate or spam.")
expect(container).to have_selector('.close-item.droplab-item-selected')
expect(container).to have_selector('.report-item')
expect(container).not_to have_selector('.report-item.droplab-item-selected')
expect(container).not_to have_selector('.reopen-item')
end
it 'changes the button when an item is selected' do
button = container.find('.issuable-close-button')
container.find('.dropdown-toggle').click
container.find('.report-item').click
expect(container).not_to have_selector('.dropdown-menu')
expect(button).to have_content('Report abuse')
container.find('.dropdown-toggle').click
container.find('.close-item').click
expect(button).to have_content("Close #{human_model_name}")
end
end
context 'on an issue' do
let(:project) { create(:empty_project) }
let(:issuable) { create(:issue, project: project) }
before do
project.add_master(user)
login_as user
end
context 'when user has permission to update', :js do
before do
visit project_issue_path(project, issuable)
end
it_behaves_like 'an issuable close/reopen/report toggle'
end
context 'when user doesnt have permission to update' do
let(:cant_project) { create(:empty_project) }
let(:cant_issuable) { create(:issue, project: cant_project) }
before do
cant_project.add_guest(user)
visit project_issue_path(cant_project, cant_issuable)
end
it 'only shows the `Report abuse` and `New issue` buttons' do
expect(page).to have_link('Report abuse')
expect(page).to have_link('New issue')
expect(page).not_to have_link('Close issue')
expect(page).not_to have_link('Reopen issue')
expect(page).not_to have_link('Edit')
end
end
end
context 'on a merge request' do
let(:project) { create(:project) }
let(:issuable) { create(:merge_request, source_project: project) }
before do
project.add_master(user)
login_as user
end
context 'when user has permission to update', :js do
before do
visit project_merge_request_path(project, issuable)
end
it_behaves_like 'an issuable close/reopen/report toggle'
end
context 'when user doesnt have permission to update' do
let(:cant_project) { create(:project) }
let(:cant_issuable) { create(:merge_request, source_project: cant_project) }
before do
cant_project.add_reporter(user)
visit project_merge_request_path(cant_project, cant_issuable)
end
it 'only shows a `Report abuse` button' do
expect(page).to have_link('Report abuse')
expect(page).not_to have_link('Close merge request')
expect(page).not_to have_link('Reopen merge request')
expect(page).not_to have_link('Edit')
end
end
end
end
...@@ -133,7 +133,7 @@ describe 'Visual tokens', js: true, feature: true do ...@@ -133,7 +133,7 @@ describe 'Visual tokens', js: true, feature: true do
describe 'editing milestone token' do describe 'editing milestone token' do
before do before do
input_filtered_search('milestone:%10.0 author:none', submit: false) input_filtered_search('milestone:%10.0 author:none', submit: false)
first('.tokens-container .filtered-search-token').double_click first('.tokens-container .filtered-search-token').click
first('#js-dropdown-milestone .filter-dropdown .filter-dropdown-item') first('#js-dropdown-milestone .filter-dropdown .filter-dropdown-item')
end end
......
import CloseReopenReportToggle from '~/close_reopen_report_toggle';
import DropLab from '~/droplab/drop_lab';
describe('CloseReopenReportToggle', () => {
describe('class constructor', () => {
const dropdownTrigger = {};
const dropdownList = {};
const button = {};
let commentTypeToggle;
beforeEach(function () {
commentTypeToggle = new CloseReopenReportToggle({
dropdownTrigger,
dropdownList,
button,
});
});
it('sets .dropdownTrigger', function () {
expect(commentTypeToggle.dropdownTrigger).toBe(dropdownTrigger);
});
it('sets .dropdownList', function () {
expect(commentTypeToggle.dropdownList).toBe(dropdownList);
});
it('sets .button', function () {
expect(commentTypeToggle.button).toBe(button);
});
});
describe('initDroplab', () => {
let closeReopenReportToggle;
const dropdownList = jasmine.createSpyObj('dropdownList', ['querySelector']);
const dropdownTrigger = {};
const button = {};
const reopenItem = {};
const closeItem = {};
const config = {};
beforeEach(() => {
spyOn(DropLab.prototype, 'init');
dropdownList.querySelector.and.returnValues(reopenItem, closeItem);
closeReopenReportToggle = new CloseReopenReportToggle({
dropdownTrigger,
dropdownList,
button,
});
spyOn(closeReopenReportToggle, 'setConfig').and.returnValue(config);
closeReopenReportToggle.initDroplab();
});
it('sets .reopenItem and .closeItem', () => {
expect(dropdownList.querySelector).toHaveBeenCalledWith('.reopen-item');
expect(dropdownList.querySelector).toHaveBeenCalledWith('.close-item');
expect(closeReopenReportToggle.reopenItem).toBe(reopenItem);
expect(closeReopenReportToggle.closeItem).toBe(closeItem);
});
it('sets .droplab', () => {
expect(closeReopenReportToggle.droplab).toEqual(jasmine.any(Object));
});
it('calls .setConfig', () => {
expect(closeReopenReportToggle.setConfig).toHaveBeenCalled();
});
it('calls droplab.init', () => {
expect(DropLab.prototype.init).toHaveBeenCalledWith(
dropdownTrigger,
dropdownList,
jasmine.any(Array),
config,
);
});
});
describe('updateButton', () => {
let closeReopenReportToggle;
const dropdownList = {};
const dropdownTrigger = {};
const button = jasmine.createSpyObj('button', ['blur']);
const isClosed = true;
beforeEach(() => {
closeReopenReportToggle = new CloseReopenReportToggle({
dropdownTrigger,
dropdownList,
button,
});
spyOn(closeReopenReportToggle, 'toggleButtonType');
closeReopenReportToggle.updateButton(isClosed);
});
it('calls .toggleButtonType', () => {
expect(closeReopenReportToggle.toggleButtonType).toHaveBeenCalledWith(isClosed);
});
it('calls .button.blur', () => {
expect(closeReopenReportToggle.button.blur).toHaveBeenCalled();
});
});
describe('toggleButtonType', () => {
let closeReopenReportToggle;
const dropdownList = {};
const dropdownTrigger = {};
const button = {};
const isClosed = true;
const showItem = jasmine.createSpyObj('showItem', ['click']);
const hideItem = {};
showItem.classList = jasmine.createSpyObj('classList', ['add', 'remove']);
hideItem.classList = jasmine.createSpyObj('classList', ['add', 'remove']);
beforeEach(() => {
closeReopenReportToggle = new CloseReopenReportToggle({
dropdownTrigger,
dropdownList,
button,
});
spyOn(closeReopenReportToggle, 'getButtonTypes').and.returnValue([showItem, hideItem]);
closeReopenReportToggle.toggleButtonType(isClosed);
});
it('calls .getButtonTypes', () => {
expect(closeReopenReportToggle.getButtonTypes).toHaveBeenCalledWith(isClosed);
});
it('removes hide class and add selected class to showItem, opposite for hideItem', () => {
expect(showItem.classList.remove).toHaveBeenCalledWith('hidden');
expect(showItem.classList.add).toHaveBeenCalledWith('droplab-item-selected');
expect(hideItem.classList.add).toHaveBeenCalledWith('hidden');
expect(hideItem.classList.remove).toHaveBeenCalledWith('droplab-item-selected');
});
it('clicks the showItem', () => {
expect(showItem.click).toHaveBeenCalled();
});
});
describe('getButtonTypes', () => {
let closeReopenReportToggle;
const dropdownList = {};
const dropdownTrigger = {};
const button = {};
const reopenItem = {};
const closeItem = {};
beforeEach(() => {
closeReopenReportToggle = new CloseReopenReportToggle({
dropdownTrigger,
dropdownList,
button,
});
closeReopenReportToggle.reopenItem = reopenItem;
closeReopenReportToggle.closeItem = closeItem;
});
it('returns reopenItem, closeItem if isClosed is true', () => {
const buttonTypes = closeReopenReportToggle.getButtonTypes(true);
expect(buttonTypes).toEqual([reopenItem, closeItem]);
});
it('returns closeItem, reopenItem if isClosed is false', () => {
const buttonTypes = closeReopenReportToggle.getButtonTypes(false);
expect(buttonTypes).toEqual([closeItem, reopenItem]);
});
});
describe('setDisable', () => {
let closeReopenReportToggle;
const dropdownList = {};
const dropdownTrigger = jasmine.createSpyObj('button', ['setAttribute', 'removeAttribute']);
const button = jasmine.createSpyObj('button', ['setAttribute', 'removeAttribute']);
beforeEach(() => {
closeReopenReportToggle = new CloseReopenReportToggle({
dropdownTrigger,
dropdownList,
button,
});
});
it('disable .button and .dropdownTrigger if shouldDisable is true', () => {
closeReopenReportToggle.setDisable(true);
expect(button.setAttribute).toHaveBeenCalledWith('disabled', 'true');
expect(dropdownTrigger.setAttribute).toHaveBeenCalledWith('disabled', 'true');
});
it('disable .button and .dropdownTrigger if shouldDisable is undefined', () => {
closeReopenReportToggle.setDisable();
expect(button.setAttribute).toHaveBeenCalledWith('disabled', 'true');
expect(dropdownTrigger.setAttribute).toHaveBeenCalledWith('disabled', 'true');
});
it('enable .button and .dropdownTrigger if shouldDisable is false', () => {
closeReopenReportToggle.setDisable(false);
expect(button.removeAttribute).toHaveBeenCalledWith('disabled');
expect(dropdownTrigger.removeAttribute).toHaveBeenCalledWith('disabled');
});
});
describe('setConfig', () => {
let closeReopenReportToggle;
const dropdownList = {};
const dropdownTrigger = {};
const button = {};
let config;
beforeEach(() => {
closeReopenReportToggle = new CloseReopenReportToggle({
dropdownTrigger,
dropdownList,
button,
});
config = closeReopenReportToggle.setConfig();
});
it('returns a config object', () => {
expect(config).toEqual({
InputSetter: [
{
input: button,
valueAttribute: 'data-text',
inputAttribute: 'data-value',
},
{
input: button,
valueAttribute: 'data-text',
inputAttribute: 'title',
},
{
input: button,
valueAttribute: 'data-button-class',
inputAttribute: 'class',
},
{
input: dropdownTrigger,
valueAttribute: 'data-toggle-class',
inputAttribute: 'class',
},
{
input: button,
valueAttribute: 'data-url',
inputAttribute: 'href',
},
{
input: button,
valueAttribute: 'data-method',
inputAttribute: 'data-method',
},
],
});
});
});
});
/* eslint-disable space-before-function-paren, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */ /* eslint-disable space-before-function-paren, one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle, max-len */
import Issue from '~/issue'; import Issue from '~/issue';
import CloseReopenReportToggle from '~/close_reopen_report_toggle';
import '~/lib/utils/text_utility'; import '~/lib/utils/text_utility';
describe('Issue', function() { describe('Issue', function() {
let $boxClosed, $boxOpen, $btnClose, $btnReopen; let $boxClosed, $boxOpen, $btn;
preloadFixtures('issues/closed-issue.html.raw'); preloadFixtures('issues/closed-issue.html.raw');
preloadFixtures('issues/issue-with-task-list.html.raw'); preloadFixtures('issues/issue-with-task-list.html.raw');
...@@ -20,9 +20,7 @@ describe('Issue', function() { ...@@ -20,9 +20,7 @@ describe('Issue', function() {
function expectIssueState(isIssueOpen) { function expectIssueState(isIssueOpen) {
expectVisibility($boxClosed, !isIssueOpen); expectVisibility($boxClosed, !isIssueOpen);
expectVisibility($boxOpen, isIssueOpen); expectVisibility($boxOpen, isIssueOpen);
expect($btn).toHaveText(isIssueOpen ? 'Close issue' : 'Reopen issue');
expectVisibility($btnClose, isIssueOpen);
expectVisibility($btnReopen, !isIssueOpen);
} }
function expectNewBranchButtonState(isPending, canCreate) { function expectNewBranchButtonState(isPending, canCreate) {
...@@ -57,7 +55,7 @@ describe('Issue', function() { ...@@ -57,7 +55,7 @@ describe('Issue', function() {
} }
} }
function findElements() { function findElements(isIssueInitiallyOpen) {
$boxClosed = $('div.status-box-closed'); $boxClosed = $('div.status-box-closed');
expect($boxClosed).toExist(); expect($boxClosed).toExist();
expect($boxClosed).toHaveText('Closed'); expect($boxClosed).toHaveText('Closed');
...@@ -66,13 +64,9 @@ describe('Issue', function() { ...@@ -66,13 +64,9 @@ describe('Issue', function() {
expect($boxOpen).toExist(); expect($boxOpen).toExist();
expect($boxOpen).toHaveText('Open'); expect($boxOpen).toHaveText('Open');
$btnClose = $('.btn-close.btn-grouped'); $btn = $('.js-issuable-close-button');
expect($btnClose).toExist(); expect($btn).toExist();
expect($btnClose).toHaveText('Close issue'); expect($btn).toHaveText(isIssueInitiallyOpen ? 'Close issue' : 'Reopen issue');
$btnReopen = $('.btn-reopen.btn-grouped');
expect($btnReopen).toExist();
expect($btnReopen).toHaveText('Reopen issue');
} }
describe('task lists', function() { describe('task lists', function() {
...@@ -99,7 +93,6 @@ describe('Issue', function() { ...@@ -99,7 +93,6 @@ describe('Issue', function() {
function ajaxSpy(req) { function ajaxSpy(req) {
if (req.url === this.$triggeredButton.attr('href')) { if (req.url === this.$triggeredButton.attr('href')) {
expect(req.type).toBe('PUT'); expect(req.type).toBe('PUT');
expect(this.$triggeredButton).toHaveProp('disabled', true);
expectNewBranchButtonState(true, false); expectNewBranchButtonState(true, false);
return this.issueStateDeferred; return this.issueStateDeferred;
} else if (req.url === Issue.createMrDropdownWrap.dataset.canCreatePath) { } else if (req.url === Issue.createMrDropdownWrap.dataset.canCreatePath) {
...@@ -119,10 +112,11 @@ describe('Issue', function() { ...@@ -119,10 +112,11 @@ describe('Issue', function() {
loadFixtures('issues/closed-issue.html.raw'); loadFixtures('issues/closed-issue.html.raw');
} }
findElements(); findElements(isIssueInitiallyOpen);
this.issue = new Issue(); this.issue = new Issue();
expectIssueState(isIssueInitiallyOpen); expectIssueState(isIssueInitiallyOpen);
this.$triggeredButton = isIssueInitiallyOpen ? $btnClose : $btnReopen;
this.$triggeredButton = $btn;
this.$projectIssuesCounter = $('.issue_counter'); this.$projectIssuesCounter = $('.issue_counter');
this.$projectIssuesCounter.text('1,001'); this.$projectIssuesCounter.text('1,001');
...@@ -143,7 +137,7 @@ describe('Issue', function() { ...@@ -143,7 +137,7 @@ describe('Issue', function() {
}); });
expectIssueState(!isIssueInitiallyOpen); expectIssueState(!isIssueInitiallyOpen);
expect(this.$triggeredButton).toHaveProp('disabled', false); expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
expect(this.$projectIssuesCounter.text()).toBe(isIssueInitiallyOpen ? '1,000' : '1,002'); expect(this.$projectIssuesCounter.text()).toBe(isIssueInitiallyOpen ? '1,000' : '1,002');
expectNewBranchButtonState(false, !isIssueInitiallyOpen); expectNewBranchButtonState(false, !isIssueInitiallyOpen);
}); });
...@@ -158,7 +152,7 @@ describe('Issue', function() { ...@@ -158,7 +152,7 @@ describe('Issue', function() {
}); });
expectIssueState(isIssueInitiallyOpen); expectIssueState(isIssueInitiallyOpen);
expect(this.$triggeredButton).toHaveProp('disabled', false); expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
expectErrorMessage(); expectErrorMessage();
expect(this.$projectIssuesCounter.text()).toBe('1,001'); expect(this.$projectIssuesCounter.text()).toBe('1,001');
expectNewBranchButtonState(false, isIssueInitiallyOpen); expectNewBranchButtonState(false, isIssueInitiallyOpen);
...@@ -172,7 +166,7 @@ describe('Issue', function() { ...@@ -172,7 +166,7 @@ describe('Issue', function() {
}); });
expectIssueState(isIssueInitiallyOpen); expectIssueState(isIssueInitiallyOpen);
expect(this.$triggeredButton).toHaveProp('disabled', true); expect(this.$triggeredButton.get(0).getAttribute('disabled')).toBeNull();
expectErrorMessage(); expectErrorMessage();
expect(this.$projectIssuesCounter.text()).toBe('1,001'); expect(this.$projectIssuesCounter.text()).toBe('1,001');
expectNewBranchButtonState(false, isIssueInitiallyOpen); expectNewBranchButtonState(false, isIssueInitiallyOpen);
...@@ -195,4 +189,37 @@ describe('Issue', function() { ...@@ -195,4 +189,37 @@ describe('Issue', function() {
}); });
}); });
}); });
describe('units', () => {
describe('class constructor', () => {
it('calls .initCloseReopenReport', () => {
spyOn(Issue.prototype, 'initCloseReopenReport');
new Issue(); // eslint-disable-line no-new
expect(Issue.prototype.initCloseReopenReport).toHaveBeenCalled();
});
});
describe('initCloseReopenReport', () => {
it('calls .initDroplab', () => {
const container = jasmine.createSpyObj('container', ['querySelector']);
const dropdownTrigger = {};
const dropdownList = {};
const button = {};
spyOn(document, 'querySelector').and.returnValue(container);
spyOn(CloseReopenReportToggle.prototype, 'initDroplab');
container.querySelector.and.returnValues(dropdownTrigger, dropdownList, button);
Issue.prototype.initCloseReopenReport();
expect(document.querySelector).toHaveBeenCalledWith('.js-issuable-close-dropdown');
expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-toggle');
expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-menu');
expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-button');
expect(CloseReopenReportToggle.prototype.initDroplab).toHaveBeenCalled();
});
});
});
}); });
...@@ -2,10 +2,12 @@ ...@@ -2,10 +2,12 @@
/* global MergeRequest */ /* global MergeRequest */
import '~/merge_request'; import '~/merge_request';
import CloseReopenReportToggle from '~/close_reopen_report_toggle';
import IssuablesHelper from '~/helpers/issuables_helper';
(function() { (function() {
describe('MergeRequest', function() { describe('MergeRequest', function() {
return describe('task lists', function() { describe('task lists', function() {
preloadFixtures('merge_requests/merge_request_with_task_list.html.raw'); preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
beforeEach(function() { beforeEach(function() {
loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
...@@ -27,5 +29,34 @@ import '~/merge_request'; ...@@ -27,5 +29,34 @@ import '~/merge_request';
return $('.js-task-list-field').trigger('tasklist:changed'); return $('.js-task-list-field').trigger('tasklist:changed');
}); });
}); });
describe('class constructor', () => {
it('calls .initCloseReopenReport', () => {
spyOn(IssuablesHelper, 'initCloseReopenReport');
new MergeRequest(); // eslint-disable-line no-new
expect(IssuablesHelper.initCloseReopenReport).toHaveBeenCalled();
});
it('calls .initDroplab', () => {
const container = jasmine.createSpyObj('container', ['querySelector']);
const dropdownTrigger = {};
const dropdownList = {};
const button = {};
spyOn(CloseReopenReportToggle.prototype, 'initDroplab');
spyOn(document, 'querySelector').and.returnValue(container);
container.querySelector.and.returnValues(dropdownTrigger, dropdownList, button);
new MergeRequest(); // eslint-disable-line no-new
expect(document.querySelector).toHaveBeenCalledWith('.js-issuable-close-dropdown');
expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-toggle');
expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-menu');
expect(container.querySelector).toHaveBeenCalledWith('.js-issuable-close-button');
expect(CloseReopenReportToggle.prototype.initDroplab).toHaveBeenCalled();
});
});
}); });
}).call(window); }).call(window);
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