diff options
-rw-r--r-- | app/assets/javascripts/close_reopen_report_toggle.js | 8 | ||||
-rw-r--r-- | app/assets/javascripts/issue.js | 4 | ||||
-rw-r--r-- | app/assets/javascripts/merge_request.js | 7 | ||||
-rw-r--r-- | app/views/shared/issuable/_close_reopen_report_toggle.html.haml | 2 | ||||
-rw-r--r-- | spec/features/issuables/close_reopen_report_toggle_spec.rb | 116 | ||||
-rw-r--r-- | spec/javascripts/close_reopen_report_toggle_spec.js | 265 | ||||
-rw-r--r-- | spec/javascripts/issue_spec.js | 41 | ||||
-rw-r--r-- | spec/javascripts/merge_request_spec.js | 40 |
8 files changed, 472 insertions, 11 deletions
diff --git a/app/assets/javascripts/close_reopen_report_toggle.js b/app/assets/javascripts/close_reopen_report_toggle.js index e56ae598092..0824f124984 100644 --- a/app/assets/javascripts/close_reopen_report_toggle.js +++ b/app/assets/javascripts/close_reopen_report_toggle.js @@ -1,4 +1,4 @@ -import DropLab from './droplab/drop_lab'; +import * as DropLab from './droplab/drop_lab'; import ISetter from './droplab/plugins/input_setter'; // Todo: Remove this when fixing issue in input_setter plugin @@ -9,13 +9,13 @@ class CloseReopenReportToggle { 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'); - } - initDroplab() { - this.droplab = new DropLab(); + this.droplab = new DropLab.default(); const config = this.setConfig(); diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index 62ffde6c93c..b61570e88e0 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -6,7 +6,7 @@ import '~/lib/utils/text_utility'; import './flash'; import './task_list'; import CreateMergeRequestDropdown from './create_merge_request_dropdown'; -import CloseReopenReportToggle from './close_reopen_report_toggle'; +import * as CloseReopenReportToggle from './close_reopen_report_toggle'; class Issue { constructor() { @@ -98,7 +98,7 @@ class Issue { const dropdownList = container.querySelector('.js-issuable-close-menu'); const button = container.querySelector('.js-issuable-close-button'); - this.closeReopenReportToggle = new CloseReopenReportToggle({ + this.closeReopenReportToggle = new CloseReopenReportToggle.default({ dropdownTrigger, dropdownList, button, diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index c00978bfaea..eebf45aa581 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -4,7 +4,7 @@ import 'vendor/jquery.waitforimages'; import './task_list'; import './merge_request_tabs'; -import CloseReopenReportToggle from './close_reopen_report_toggle'; +import * as CloseReopenReportToggle from './close_reopen_report_toggle'; (function() { this.MergeRequest = (function() { @@ -123,11 +123,14 @@ import CloseReopenReportToggle from './close_reopen_report_toggle'; MergeRequest.initCloseReopenReport = function () { const container = document.querySelector('.js-issuable-close-dropdown'); + + if (!container) return; + 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({ + const closeReopenReportToggle = new CloseReopenReportToggle.default({ dropdownTrigger, dropdownList, button, diff --git a/app/views/shared/issuable/_close_reopen_report_toggle.html.haml b/app/views/shared/issuable/_close_reopen_report_toggle.html.haml index ece307c6b8d..27b10cdccd1 100644 --- a/app/views/shared/issuable/_close_reopen_report_toggle.html.haml +++ b/app/views/shared/issuable/_close_reopen_report_toggle.html.haml @@ -33,7 +33,7 @@ %li.divider.droplab-item-ignore - %li{ 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: '' } } + %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 diff --git a/spec/features/issuables/close_reopen_report_toggle_spec.rb b/spec/features/issuables/close_reopen_report_toggle_spec.rb new file mode 100644 index 00000000000..38e8ca3eb36 --- /dev/null +++ b/spec/features/issuables/close_reopen_report_toggle_spec.rb @@ -0,0 +1,116 @@ +require 'spec_helper' + +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 + +describe 'Issuables Close/Reopen/Report toggle', :feature do + let(:user) { create(:user) } + + 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 namespace_project_issue_path(project.namespace, 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 namespace_project_issue_path(cant_project.namespace, 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 namespace_project_merge_request_path(project.namespace, 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 namespace_project_merge_request_path(cant_project.namespace, 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 diff --git a/spec/javascripts/close_reopen_report_toggle_spec.js b/spec/javascripts/close_reopen_report_toggle_spec.js new file mode 100644 index 00000000000..9641a7e43d6 --- /dev/null +++ b/spec/javascripts/close_reopen_report_toggle_spec.js @@ -0,0 +1,265 @@ +import CloseReopenReportToggle from '~/close_reopen_report_toggle'; +import * as 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 droplab = jasmine.createSpyObj('droplab', ['init']); + const dropdownTrigger = {}; + const button = {}; + const reopenItem = {}; + const closeItem = {}; + const config = {}; + + beforeEach(() => { + spyOn(DropLab, 'default').and.returnValue(droplab); + 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('instantiates DropLab and set .droplab', () => { + expect(DropLab.default).toHaveBeenCalled(); + expect(closeReopenReportToggle.droplab).toBe(droplab); + }); + + it('calls .setConfig', () => { + expect(closeReopenReportToggle.setConfig).toHaveBeenCalled(); + }); + + it('calls .droplab.init', () => { + expect(droplab.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('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', + }, + ], + }); + }); + }); +}); diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index df97a100b0d..43420076be0 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -1,6 +1,6 @@ /* 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 * as CloseReopenReportToggle from '~/close_reopen_report_toggle'; import '~/lib/utils/text_utility'; describe('Issue', function() { @@ -195,4 +195,43 @@ 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('inits a new CloseReopenReportToggle instance and calls .initDroplab', () => { + const container = jasmine.createSpyObj('container', ['querySelector']); + const closeReopenReportToggle = jasmine.createSpyObj('closeReopenReportToggle', ['initDroplab']); + const dropdownTrigger = {}; + const dropdownList = {}; + const button = {}; + + spyOn(document, 'querySelector').and.returnValue(container); + spyOn(CloseReopenReportToggle, 'default').and.returnValue(closeReopenReportToggle); + 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.default).toHaveBeenCalledWith({ + dropdownTrigger, + dropdownList, + button, + }); + expect(closeReopenReportToggle.initDroplab).toHaveBeenCalled(); + }); + }); + }); }); diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js index f444bcaf847..3235df50456 100644 --- a/spec/javascripts/merge_request_spec.js +++ b/spec/javascripts/merge_request_spec.js @@ -2,10 +2,11 @@ /* global MergeRequest */ import '~/merge_request'; +import * as CloseReopenReportToggle from '~/close_reopen_report_toggle'; (function() { describe('MergeRequest', function() { - return describe('task lists', function() { + describe('task lists', function() { preloadFixtures('merge_requests/merge_request_with_task_list.html.raw'); beforeEach(function() { loadFixtures('merge_requests/merge_request_with_task_list.html.raw'); @@ -27,5 +28,42 @@ import '~/merge_request'; return $('.js-task-list-field').trigger('tasklist:changed'); }); }); + + describe('class constructor', () => { + it('calls .initCloseReopenReport', () => { + spyOn(MergeRequest, 'initCloseReopenReport'); + + new MergeRequest(); // eslint-disable-line no-new + + expect(MergeRequest.initCloseReopenReport).toHaveBeenCalled(); + }); + }); + + describe('initCloseReopenReport', () => { + it('inits a new CloseReopenReportToggle instance and calls .initDroplab', () => { + const container = jasmine.createSpyObj('container', ['querySelector']); + const closeReopenReportToggle = jasmine.createSpyObj('closeReopenReportToggle', ['initDroplab']); + const dropdownTrigger = {}; + const dropdownList = {}; + const button = {}; + + spyOn(document, 'querySelector').and.returnValue(container); + spyOn(CloseReopenReportToggle, 'default').and.returnValue(closeReopenReportToggle); + container.querySelector.and.returnValues(dropdownTrigger, dropdownList, button); + + MergeRequest.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.default).toHaveBeenCalledWith({ + dropdownTrigger, + dropdownList, + button, + }); + expect(closeReopenReportToggle.initDroplab).toHaveBeenCalled(); + }); + }); }); }).call(window); |