diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-09-23 21:06:29 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-09-23 21:06:29 +0000 |
commit | b9254657872c4db441ab268154686f5476fb4bc6 (patch) | |
tree | ab045c623296a049d1246ba2d66800456a1077aa | |
parent | c792263edfaf826c58f4aa41d26904464a17a3e7 (diff) | |
download | gitlab-ce-b9254657872c4db441ab268154686f5476fb4bc6.tar.gz |
Add latest changes from gitlab-org/gitlab@master
29 files changed, 371 insertions, 206 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 992c5e5e330..d57be10f472 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -74,6 +74,11 @@ const Api = { }); }, + groupLabels(namespace) { + const url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespace); + return axios.get(url).then(({ data }) => data); + }, + // Return namespaces list. Filtered by query namespaces(query, callback) { const url = Api.buildUrl(Api.namespacesPath); diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js index 7744984edfc..ac903d60089 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js @@ -48,6 +48,8 @@ export default () => { import('ee_component/analytics/cycle_analytics/components/custom_stage_form.vue'), AddStageButton: () => import('ee_component/analytics/cycle_analytics/components/add_stage_button.vue'), + CustomStageFormContainer: () => + import('ee_component/analytics/cycle_analytics/components/custom_stage_form_container.vue'), }, mixins: [filterMixins, addStageMixin], data() { diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index a4715789337..e0832ab535d 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -537,13 +537,6 @@ export const stringifyTime = (timeObject, fullNameFormat = false) => { }; /** - * Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns - * the first non-zero unit/value pair. - */ -export const abbreviateTime = timeStr => - timeStr.split(' ').filter(unitStr => unitStr.charAt(0) !== '0')[0]; - -/** * Calculates the milliseconds between now and a given date string. * The result cannot become negative. * diff --git a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue index 24d5b14ded9..65ecd5be05d 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue @@ -1,6 +1,5 @@ <script> import { __, sprintf } from '~/locale'; -import { abbreviateTime } from '~/lib/utils/datetime_utility'; import icon from '~/vue_shared/components/icon.vue'; import tooltip from '~/vue_shared/directives/tooltip'; @@ -41,12 +40,6 @@ export default { }, }, computed: { - timeSpent() { - return this.abbreviateTime(this.timeSpentHumanReadable); - }, - timeEstimate() { - return this.abbreviateTime(this.timeEstimateHumanReadable); - }, divClass() { if (this.showComparisonState) { return 'compare'; @@ -73,11 +66,11 @@ export default { }, text() { if (this.showComparisonState) { - return `${this.timeSpent} / ${this.timeEstimate}`; + return `${this.timeSpentHumanReadable} / ${this.timeEstimateHumanReadable}`; } else if (this.showEstimateOnlyState) { - return `-- / ${this.timeEstimate}`; + return `-- / ${this.timeEstimateHumanReadable}`; } else if (this.showSpentOnlyState) { - return `${this.timeSpent} / --`; + return `${this.timeSpentHumanReadable} / --`; } else if (this.showNoTimeTrackingState) { return __('None'); } @@ -100,11 +93,6 @@ export default { return this.showNoTimeTrackingState ? __('Time tracking') : this.timeTrackedTooltipText; }, }, - methods: { - abbreviateTime(timeStr) { - return abbreviateTime(timeStr); - }, - }, }; </script> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue index 52acd1de666..ebedd4842c9 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue @@ -110,7 +110,10 @@ export default { <div class="ci-widget-container d-flex"> <div class="ci-widget-content"> <div class="media-body"> - <div class="font-weight-bold js-pipeline-info-container"> + <div + class="font-weight-bold js-pipeline-info-container" + data-qa-selector="merge_request_pipeline_info_content" + > {{ pipeline.details.name }} <gl-link :href="pipeline.path" class="pipeline-id font-weight-normal pipeline-number" >#{{ pipeline.id }}</gl-link diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_eventhub.js b/app/assets/javascripts/vue_shared/components/recaptcha_eventhub.js new file mode 100644 index 00000000000..a4e004c3341 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/recaptcha_eventhub.js @@ -0,0 +1,21 @@ +import Vue from 'vue'; + +// see recaptcha_tags in app/views/shared/_recaptcha_form.html.haml +export const callbackName = 'recaptchaDialogCallback'; + +export const eventHub = new Vue(); + +const throwDuplicateCallbackError = () => { + throw new Error(`${callbackName} is already defined!`); +}; + +if (window[callbackName]) { + throwDuplicateCallbackError(); +} + +const callback = () => eventHub.$emit('submit'); + +Object.defineProperty(window, callbackName, { + get: () => callback, + set: throwDuplicateCallbackError, +}); diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue index f0aae20477b..55172649813 100644 --- a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue +++ b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue @@ -1,5 +1,6 @@ <script> import DeprecatedModal from './deprecated_modal.vue'; +import { eventHub } from './recaptcha_eventhub'; export default { name: 'RecaptchaModal', @@ -30,14 +31,11 @@ export default { }, mounted() { - if (window.recaptchaDialogCallback) { - throw new Error('recaptchaDialogCallback is already defined!'); - } - window.recaptchaDialogCallback = this.submit.bind(this); + eventHub.$on('submit', this.submit); }, beforeDestroy() { - window.recaptchaDialogCallback = null; + eventHub.$off('submit', this.submit); }, methods: { diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb index 3f0679f9bf9..d3950219f3f 100644 --- a/app/helpers/boards_helper.rb +++ b/app/helpers/boards_helper.rb @@ -88,7 +88,7 @@ module BoardsHelper end def boards_link_text - if multiple_boards_available? + if current_board_parent.multiple_issue_boards_available? s_("IssueBoards|Boards") else s_("IssueBoards|Board") diff --git a/app/services/boards/lists/update_service.rb b/app/services/boards/lists/update_service.rb index 2ddeb6f0bd8..ad96e42f756 100644 --- a/app/services/boards/lists/update_service.rb +++ b/app/services/boards/lists/update_service.rb @@ -4,10 +4,10 @@ module Boards module Lists class UpdateService < Boards::BaseService def execute(list) - return not_authorized if preferences? && !can_read?(list) - return not_authorized if position? && !can_admin?(list) + update_preferences_result = update_preferences(list) if can_read?(list) + update_position_result = update_position(list) if can_admin?(list) - if update_preferences(list) || update_position(list) + if update_preferences_result || update_position_result success(list: list) else error(list.errors.messages, 422) @@ -32,10 +32,6 @@ module Boards { collapsed: Gitlab::Utils.to_boolean(params[:collapsed]) } end - def not_authorized - error("Not authorized", 403) - end - def preferences? params.has_key?(:collapsed) end diff --git a/app/views/admin/application_settings/_snowplow.html.haml b/app/views/admin/application_settings/_snowplow.html.haml index b60b5d55a1b..31fd12d191e 100644 --- a/app/views/admin/application_settings/_snowplow.html.haml +++ b/app/views/admin/application_settings/_snowplow.html.haml @@ -9,7 +9,7 @@ = _('Configure the %{link} integration.').html_safe % { link: link_to('Snowplow', 'https://snowplowanalytics.com/', target: '_blank') } .settings-content - = form_for @application_setting, url: integrations_admin_application_settings_path, html: { class: 'fieldset-form' } do |f| + = form_for @application_setting, url: integrations_admin_application_settings_path(anchor: 'js-snowplow-settings'), html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index de994250649..c9458475aa5 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -6,7 +6,7 @@ .issues-filters{ class: ("w-100" if type == :boards_modal) } .issues-details-filters.filtered-search-block.d-flex.flex-column.flex-md-row{ class: block_css_class, "v-pre" => type == :boards_modal } - - if type == :boards && (multiple_boards_available? || current_board_parent.boards.size > 1) + - if type == :boards = render "shared/boards/switcher", board: board = form_tag page_filter_path, method: :get, class: 'filter-form js-filter-form w-100' do - if params[:search].present? diff --git a/changelogs/unreleased/32427-cannot-reorder-issue-boards.yml b/changelogs/unreleased/32427-cannot-reorder-issue-boards.yml new file mode 100644 index 00000000000..0ad9279876e --- /dev/null +++ b/changelogs/unreleased/32427-cannot-reorder-issue-boards.yml @@ -0,0 +1,5 @@ +--- +title: Fix ordering of issue board lists not being persisted +merge_request: 17356 +author: +type: fixed diff --git a/changelogs/unreleased/32595-blame-or-history-newline-in-filename.yml b/changelogs/unreleased/32595-blame-or-history-newline-in-filename.yml new file mode 100644 index 00000000000..b67b823704f --- /dev/null +++ b/changelogs/unreleased/32595-blame-or-history-newline-in-filename.yml @@ -0,0 +1,5 @@ +--- +title: Users can view the blame or history of a file with newlines in its filename. +merge_request: 17543 +author: Jesse Hall @jessehall3 +type: fixed diff --git a/config/routes/repository.rb b/config/routes/repository.rb index 093b86f3259..4815575ba9f 100644 --- a/config/routes/repository.rb +++ b/config/routes/repository.rb @@ -34,7 +34,7 @@ scope format: false do # ref regex used in constraints. Regex verification now done in controller. get 'logs_tree/*path', action: :logs_tree, as: :logs_file, format: false, constraints: { id: /.*/, - path: /.*/ + path: /[^\0]*/ } end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 6658d2b67e5..dd3c2d96d7f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4547,6 +4547,42 @@ msgstr "" msgid "CustomCycleAnalytics|Add a stage" msgstr "" +msgid "CustomCycleAnalytics|Add stage" +msgstr "" + +msgid "CustomCycleAnalytics|Enter a name for the stage" +msgstr "" + +msgid "CustomCycleAnalytics|Name" +msgstr "" + +msgid "CustomCycleAnalytics|New stage" +msgstr "" + +msgid "CustomCycleAnalytics|Please select a start event first" +msgstr "" + +msgid "CustomCycleAnalytics|Select start event" +msgstr "" + +msgid "CustomCycleAnalytics|Select stop event" +msgstr "" + +msgid "CustomCycleAnalytics|Start event" +msgstr "" + +msgid "CustomCycleAnalytics|Start event changed, please select a valid stop event" +msgstr "" + +msgid "CustomCycleAnalytics|Start event label" +msgstr "" + +msgid "CustomCycleAnalytics|Stop event" +msgstr "" + +msgid "CustomCycleAnalytics|Stop event label" +msgstr "" + msgid "Customize colors" msgstr "" @@ -10208,9 +10244,6 @@ msgstr "" msgid "New snippet" msgstr "" -msgid "New stage" -msgstr "" - msgid "New subgroup" msgstr "" @@ -15698,6 +15731,9 @@ msgstr "" msgid "There was an error fetching configuration for charts" msgstr "" +msgid "There was an error fetching the form data" +msgstr "" + msgid "There was an error gathering the chart data" msgstr "" diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 6e550805f9f..ea0cbfe2ab0 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -6,6 +6,16 @@ module QA class Show < Page::Base include Page::Component::Note + view 'app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue' do + element :dropdown_toggle + element :download_email_patches + element :download_plain_diff + end + + view 'app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue' do + element :merge_request_pipeline_info_content + end + view 'app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue' do element :merge_button element :fast_forward_message, 'Fast-forward merge without a merge commit' # rubocop:disable QA/ElementWithPattern @@ -27,12 +37,6 @@ module QA element :squash_checkbox end - view 'app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue' do - element :dropdown_toggle - element :download_email_patches - element :download_plain_diff - end - view 'app/views/projects/merge_requests/show.html.haml' do element :notes_tab element :diffs_tab @@ -111,6 +115,11 @@ module QA end end + def has_pipeline_status?(text) + # Pipelines can be slow, so we wait a bit longer than the usual 10 seconds + has_element?(:merge_request_pipeline_info_content, text: text, wait: 30) + end + def has_title?(title) has_element?(:title, text: title) end diff --git a/qa/qa/page/project/settings/main.rb b/qa/qa/page/project/settings/main.rb index 6b26c82a46f..3a60330217b 100644 --- a/qa/qa/page/project/settings/main.rb +++ b/qa/qa/page/project/settings/main.rb @@ -11,6 +11,7 @@ module QA view 'app/views/projects/edit.html.haml' do element :advanced_settings + element :merge_request_settings end view 'app/views/projects/settings/_general.html.haml' do @@ -41,6 +42,12 @@ module QA end end + def expand_merge_requests_settings(&block) + expand_section(:merge_request_settings) do + MergeRequest.perform(&block) + end + end + def expand_visibility_project_features_permissions(&block) expand_section(:visibility_features_permissions_content) do VisibilityFeaturesPermissions.perform(&block) diff --git a/qa/qa/page/project/settings/merge_request.rb b/qa/qa/page/project/settings/merge_request.rb index f92528c4262..7da2c9d168c 100644 --- a/qa/qa/page/project/settings/merge_request.rb +++ b/qa/qa/page/project/settings/merge_request.rb @@ -8,7 +8,6 @@ module QA include Common view 'app/views/projects/edit.html.haml' do - element :merge_request_settings element :save_merge_request_changes end @@ -16,14 +15,18 @@ module QA element :radio_button_merge_ff end + def click_save_changes + click_element :save_merge_request_changes + end + def enable_ff_only - expand_section(:merge_request_settings) do - click_element :radio_button_merge_ff - click_element :save_merge_request_changes - end + click_element :radio_button_merge_ff + click_save_changes end end end end end end + +QA::Page::Project::Settings::MergeRequest.prepend_if_ee("QA::EE::Page::Project::Settings::MergeRequest") diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb index 3ce291bf8bc..c7b5e40d0be 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb @@ -13,8 +13,12 @@ module QA end project.visit! - Page::Project::Menu.perform(&:click_settings) - Page::Project::Settings::MergeRequest.perform(&:enable_ff_only) + Page::Project::Menu.perform(&:go_to_general_settings) + Page::Project::Settings::Main.perform do |main| + main.expand_merge_requests_settings do |settings| + settings.enable_ff_only + end + end merge_request = Resource::MergeRequest.fabricate! do |merge_request| merge_request.project = project diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb index 802fc1770a4..77b5872fcb3 100644 --- a/spec/controllers/boards/lists_controller_spec.rb +++ b/spec/controllers/boards/lists_controller_spec.rb @@ -162,10 +162,10 @@ describe Boards::ListsController do end context 'with unauthorized user' do - it 'returns a forbidden 403 response' do + it 'returns a 422 unprocessable entity response' do move user: guest, board: board, list: planning, position: 6 - expect(response).to have_gitlab_http_status(403) + expect(response).to have_gitlab_http_status(422) end end diff --git a/spec/frontend/ide/components/error_message_spec.js b/spec/frontend/ide/components/error_message_spec.js new file mode 100644 index 00000000000..fd6d1a494d4 --- /dev/null +++ b/spec/frontend/ide/components/error_message_spec.js @@ -0,0 +1,122 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { GlLoadingIcon } from '@gitlab/ui'; +import ErrorMessage from '~/ide/components/error_message.vue'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('IDE error message component', () => { + let wrapper; + + const setErrorMessageMock = jest.fn(); + const createComponent = messageProps => { + const fakeStore = new Vuex.Store({ + actions: { setErrorMessage: setErrorMessageMock }, + }); + + wrapper = shallowMount(ErrorMessage, { + propsData: { + message: { + text: 'some text', + actionText: 'test action', + actionPayload: 'testActionPayload', + ...messageProps, + }, + }, + store: fakeStore, + localVue, + sync: false, + }); + }; + + beforeEach(() => { + setErrorMessageMock.mockReset(); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('renders error message', () => { + const text = 'error message'; + createComponent({ text }); + expect(wrapper.text()).toContain(text); + }); + + it('clears error message on click', () => { + createComponent(); + wrapper.trigger('click'); + + expect(setErrorMessageMock).toHaveBeenCalledWith(expect.any(Object), null, undefined); + }); + + describe('with action', () => { + let actionMock; + + const message = { + actionText: 'test action', + actionPayload: 'testActionPayload', + }; + + beforeEach(() => { + actionMock = jest.fn().mockResolvedValue(); + createComponent({ + ...message, + action: actionMock, + }); + }); + + it('renders action button', () => { + const button = wrapper.find('button'); + + expect(button.exists()).toBe(true); + expect(button.text()).toContain(message.actionText); + }); + + it('does not clear error message on click', () => { + wrapper.trigger('click'); + + expect(setErrorMessageMock).not.toHaveBeenCalled(); + }); + + it('dispatches action', () => { + wrapper.find('button').trigger('click'); + + expect(actionMock).toHaveBeenCalledWith(message.actionPayload); + }); + + it('does not dispatch action when already loading', () => { + wrapper.find('button').trigger('click'); + actionMock.mockReset(); + wrapper.find('button').trigger('click'); + expect(actionMock).not.toHaveBeenCalled(); + }); + + it('shows loading icon when loading', () => { + let resolve; + actionMock.mockImplementation( + () => + new Promise(ok => { + resolve = ok; + }), + ); + wrapper.find('button').trigger('click'); + + return wrapper.vm.$nextTick(() => { + expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(true); + resolve(); + }); + }); + + it('hides loading icon when operation finishes', () => { + wrapper.find('button').trigger('click'); + return actionMock() + .then(() => wrapper.vm.$nextTick()) + .then(() => { + expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(false); + }); + }); + }); +}); diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js index 9f1700bb243..8cbf170b3e6 100644 --- a/spec/frontend/lib/utils/datetime_utility_spec.js +++ b/spec/frontend/lib/utils/datetime_utility_spec.js @@ -388,20 +388,6 @@ describe('prettyTime methods', () => { expect(datetimeUtility.stringifyTime(timeObject, true)).toEqual('1 week 1 hour'); }); }); - - describe('abbreviateTime', () => { - it('should abbreviate stringified times for weeks', () => { - const fullTimeString = '1w 3d 4h 5m'; - - expect(datetimeUtility.abbreviateTime(fullTimeString)).toBe('1w'); - }); - - it('should abbreviate stringified times for non-weeks', () => { - const fullTimeString = '0w 3d 4h 5m'; - - expect(datetimeUtility.abbreviateTime(fullTimeString)).toBe('3d'); - }); - }); }); describe('calculateRemainingMilliseconds', () => { diff --git a/spec/frontend/vue_shared/components/recaptcha_eventhub_spec.js b/spec/frontend/vue_shared/components/recaptcha_eventhub_spec.js new file mode 100644 index 00000000000..d86d627886f --- /dev/null +++ b/spec/frontend/vue_shared/components/recaptcha_eventhub_spec.js @@ -0,0 +1,21 @@ +import { eventHub, callbackName } from '~/vue_shared/components/recaptcha_eventhub'; + +describe('reCAPTCHA event hub', () => { + // the following test case currently crashes + // see https://gitlab.com/gitlab-org/gitlab/issues/29192#note_217840035 + // eslint-disable-next-line jest/no-disabled-tests + it.skip('throws an error for overriding the callback', () => { + expect(() => { + window[callbackName] = 'something'; + }).toThrow(); + }); + + it('triggering callback emits a submit event', () => { + const eventHandler = jest.fn(); + eventHub.$once('submit', eventHandler); + + window[callbackName](); + + expect(eventHandler).toHaveBeenCalled(); + }); +}); diff --git a/spec/frontend/vue_shared/components/recaptcha_modal_spec.js b/spec/frontend/vue_shared/components/recaptcha_modal_spec.js new file mode 100644 index 00000000000..e509fe09d94 --- /dev/null +++ b/spec/frontend/vue_shared/components/recaptcha_modal_spec.js @@ -0,0 +1,36 @@ +import { shallowMount } from '@vue/test-utils'; + +import { eventHub } from '~/vue_shared/components/recaptcha_eventhub'; + +import RecaptchaModal from '~/vue_shared/components/recaptcha_modal'; + +describe('RecaptchaModal', () => { + const recaptchaFormId = 'recaptcha-form'; + const recaptchaHtml = `<form id="${recaptchaFormId}"></form>`; + + let wrapper; + + const findRecaptchaForm = () => wrapper.find(`#${recaptchaFormId}`).element; + + beforeEach(() => { + wrapper = shallowMount(RecaptchaModal, { + sync: false, + propsData: { + html: recaptchaHtml, + }, + }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('submits the form if event hub emits submit event', () => { + const form = findRecaptchaForm(); + jest.spyOn(form, 'submit').mockImplementation(); + + eventHub.$emit('submit'); + + expect(form.submit).toHaveBeenCalled(); + }); +}); diff --git a/spec/javascripts/ide/components/error_message_spec.js b/spec/javascripts/ide/components/error_message_spec.js deleted file mode 100644 index 80d6c7fd564..00000000000 --- a/spec/javascripts/ide/components/error_message_spec.js +++ /dev/null @@ -1,106 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import ErrorMessage from '~/ide/components/error_message.vue'; -import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; -import { resetStore } from '../helpers'; - -describe('IDE error message component', () => { - const Component = Vue.extend(ErrorMessage); - let vm; - - beforeEach(() => { - vm = createComponentWithStore(Component, store, { - message: { - text: 'error message', - action: null, - actionText: null, - }, - }).$mount(); - }); - - afterEach(() => { - vm.$destroy(); - resetStore(vm.$store); - }); - - it('renders error message', () => { - expect(vm.$el.textContent).toContain('error message'); - }); - - it('clears error message on click', () => { - spyOn(vm, 'setErrorMessage'); - - vm.$el.click(); - - expect(vm.setErrorMessage).toHaveBeenCalledWith(null); - }); - - describe('with action', () => { - let actionSpy; - - beforeEach(done => { - actionSpy = jasmine.createSpy('action').and.returnValue(Promise.resolve()); - - vm.message.action = actionSpy; - vm.message.actionText = 'test action'; - vm.message.actionPayload = 'testActionPayload'; - - vm.$nextTick(done); - }); - - it('renders action button', () => { - expect(vm.$el.querySelector('.flash-action')).not.toBe(null); - expect(vm.$el.textContent).toContain('test action'); - }); - - it('does not clear error message on click', () => { - spyOn(vm, 'setErrorMessage'); - - vm.$el.click(); - - expect(vm.setErrorMessage).not.toHaveBeenCalled(); - }); - - it('dispatches action', done => { - vm.$el.querySelector('.flash-action').click(); - - vm.$nextTick(() => { - expect(actionSpy).toHaveBeenCalledWith('testActionPayload'); - - done(); - }); - }); - - it('does not dispatch action when already loading', () => { - vm.isLoading = true; - - vm.$el.querySelector('.flash-action').click(); - - expect(actionSpy).not.toHaveBeenCalledWith(); - }); - - it('resets isLoading after click', done => { - vm.$el.querySelector('.flash-action').click(); - - expect(vm.isLoading).toBe(true); - - setTimeout(() => { - expect(vm.isLoading).toBe(false); - - done(); - }); - }); - - it('shows loading icon when isLoading is true', done => { - expect(vm.$el.querySelector('.loading-container').style.display).not.toBe(''); - - vm.isLoading = true; - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.loading-container').style.display).toBe(''); - - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js b/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js index 2e1863cff86..ab28190ae64 100644 --- a/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js +++ b/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js @@ -83,8 +83,8 @@ describe('Issuable Time Tracker', () => { initTimeTrackingComponent({ timeEstimate: 100000, // 1d 3h timeSpent: 5000, // 1h 23m - timeEstimateHumanReadable: '', - timeSpentHumanReadable: '', + timeEstimateHumanReadable: '1d 3h', + timeSpentHumanReadable: '1h 23m', }); }); @@ -98,6 +98,16 @@ describe('Issuable Time Tracker', () => { }); }); + it('should show full times when the sidebar is collapsed', done => { + Vue.nextTick(() => { + const timeTrackingText = vm.$el.querySelector('.time-tracking-collapsed-summary span') + .innerText; + + expect(timeTrackingText).toBe('1h 23m / 1d 3h'); + done(); + }); + }); + describe('Remaining meter', () => { it('should display the remaining meter with the correct width', done => { Vue.nextTick(() => { diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 7e2d70d6eb5..acdbf064a73 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -276,6 +276,11 @@ describe 'project routing' do expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45', path: 'foo/bar/baz') expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45', path: 'foo/bar/baz') expect(get('/gitlab/gitlabhq/refs/stable/logs_tree/files.scss')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable', path: 'files.scss') + assert_routing({ path: "/gitlab/gitlabhq/refs/stable/logs_tree/new%0A%0Aline.txt", + method: :get }, + { controller: 'projects/refs', action: 'logs_tree', + namespace_id: 'gitlab', project_id: 'gitlabhq', + id: "stable", path: "new\n\nline.txt" }) end end diff --git a/spec/services/boards/lists/update_service_spec.rb b/spec/services/boards/lists/update_service_spec.rb index f28bbab941a..a5411a2fb3a 100644 --- a/spec/services/boards/lists/update_service_spec.rb +++ b/spec/services/boards/lists/update_service_spec.rb @@ -10,9 +10,8 @@ describe Boards::Lists::UpdateService do context 'when user can admin list' do it 'calls Lists::MoveService to update list position' do board.parent.add_developer(user) - service = described_class.new(board.parent, user, position: 1) - expect(Boards::Lists::MoveService).to receive(:new).with(board.parent, user, { position: 1 }).and_call_original + expect(Boards::Lists::MoveService).to receive(:new).with(board.parent, user, params).and_call_original expect_any_instance_of(Boards::Lists::MoveService).to receive(:execute).with(list) service.execute(list) @@ -21,8 +20,6 @@ describe Boards::Lists::UpdateService do context 'when user cannot admin list' do it 'does not call Lists::MoveService to update list position' do - service = described_class.new(board.parent, user, position: 1) - expect(Boards::Lists::MoveService).not_to receive(:new) service.execute(list) @@ -34,7 +31,6 @@ describe Boards::Lists::UpdateService do context 'when user can read list' do it 'updates list preference for user' do board.parent.add_guest(user) - service = described_class.new(board.parent, user, collapsed: true) service.execute(list) @@ -44,8 +40,6 @@ describe Boards::Lists::UpdateService do context 'when user cannot read list' do it 'does not update list preference for user' do - service = described_class.new(board.parent, user, collapsed: true) - service.execute(list) expect(list.preferences_for(user).collapsed).to be_nil @@ -54,35 +48,61 @@ describe Boards::Lists::UpdateService do end describe '#execute' do + let(:service) { described_class.new(board.parent, user, params) } + context 'when position parameter is present' do + let(:params) { { position: 1 } } + context 'for projects' do - it_behaves_like 'moving list' do - let(:project) { create(:project, :private) } - let(:board) { create(:board, project: project) } - end + let(:project) { create(:project, :private) } + let(:board) { create(:board, project: project) } + + it_behaves_like 'moving list' end context 'for groups' do - it_behaves_like 'moving list' do - let(:group) { create(:group, :private) } - let(:board) { create(:board, group: group) } - end + let(:group) { create(:group, :private) } + let(:board) { create(:board, group: group) } + + it_behaves_like 'moving list' end end context 'when collapsed parameter is present' do + let(:params) { { collapsed: true } } + context 'for projects' do - it_behaves_like 'updating list preferences' do - let(:project) { create(:project, :private) } - let(:board) { create(:board, project: project) } - end + let(:project) { create(:project, :private) } + let(:board) { create(:board, project: project) } + + it_behaves_like 'updating list preferences' end context 'for groups' do - it_behaves_like 'updating list preferences' do - let(:group) { create(:group, :private) } - let(:board) { create(:board, group: group) } - end + let(:project) { create(:project, :private) } + let(:board) { create(:board, project: project) } + + it_behaves_like 'updating list preferences' + end + end + + context 'when position and collapsed are both present' do + let(:params) { { collapsed: true, position: 1 } } + + context 'for projects' do + let(:project) { create(:project, :private) } + let(:board) { create(:board, project: project) } + + it_behaves_like 'moving list' + it_behaves_like 'updating list preferences' + end + + context 'for groups' do + let(:group) { create(:group, :private) } + let(:board) { create(:board, group: group) } + + it_behaves_like 'moving list' + it_behaves_like 'updating list preferences' end end end diff --git a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb index 07c541ec189..f2f31e1b7f2 100644 --- a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb +++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb @@ -11,10 +11,6 @@ shared_examples_for 'multiple issue boards' do wait_for_requests end - it 'shows board switcher' do - expect(page).to have_css('.boards-switcher') - end - it 'shows current board name' do page.within('.boards-switcher') do expect(page).to have_content(board.name) |