diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-11-21 09:06:16 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-11-21 09:06:16 +0000 |
commit | a048261403ea7e12992ccffe704f0779235712d7 (patch) | |
tree | 59254549db6d39a4da824379a7bf354e7c8e7e67 | |
parent | 80e5134020483299c039114e76b734436f006c66 (diff) | |
download | gitlab-ce-a048261403ea7e12992ccffe704f0779235712d7.tar.gz |
Add latest changes from gitlab-org/gitlab@master
64 files changed, 430 insertions, 158 deletions
diff --git a/app/assets/javascripts/autosave.js b/app/assets/javascripts/autosave.js index 7652b67ae1e..07d79ea1c70 100644 --- a/app/assets/javascripts/autosave.js +++ b/app/assets/javascripts/autosave.js @@ -1,9 +1,9 @@ -/* eslint-disable no-param-reassign, no-void, consistent-return */ +/* eslint-disable no-param-reassign, consistent-return */ import AccessorUtilities from './lib/utils/accessor'; export default class Autosave { - constructor(field, key) { + constructor(field, key, fallbackKey) { this.field = field; this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe(); @@ -11,6 +11,7 @@ export default class Autosave { key = key.join('/'); } this.key = `autosave/${key}`; + this.fallbackKey = fallbackKey; this.field.data('autosave', this); this.restore(); this.field.on('input', () => this.save()); @@ -21,9 +22,12 @@ export default class Autosave { if (!this.field.length) return; const text = window.localStorage.getItem(this.key); + const fallbackText = window.localStorage.getItem(this.fallbackKey); - if ((text != null ? text.length : void 0) > 0) { + if (text) { this.field.val(text); + } else if (fallbackText) { + this.field.val(fallbackText); } this.field.trigger('input'); @@ -41,7 +45,10 @@ export default class Autosave { const text = this.field.val(); - if (this.isLocalStorageAvailable && (text != null ? text.length : void 0) > 0) { + if (this.isLocalStorageAvailable && text) { + if (this.fallbackKey) { + window.localStorage.setItem(this.fallbackKey, text); + } return window.localStorage.setItem(this.key, text); } @@ -51,6 +58,7 @@ export default class Autosave { reset() { if (!this.isLocalStorageAvailable) return; + window.localStorage.removeItem(this.fallbackKey); return window.localStorage.removeItem(this.key); } diff --git a/app/assets/javascripts/clusters/components/crossplane_provider_stack.vue b/app/assets/javascripts/clusters/components/crossplane_provider_stack.vue index 966918ae636..6b99bb09504 100644 --- a/app/assets/javascripts/clusters/components/crossplane_provider_stack.vue +++ b/app/assets/javascripts/clusters/components/crossplane_provider_stack.vue @@ -1,6 +1,5 @@ <script> -import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; -import Icon from '~/vue_shared/components/icon.vue'; +import { GlDropdown, GlDropdownItem, GlIcon } from '@gitlab/ui'; import { s__ } from '../../locale'; export default { @@ -8,7 +7,7 @@ export default { components: { GlDropdown, GlDropdownItem, - Icon, + GlIcon, }, props: { stacks: { @@ -86,8 +85,9 @@ export default { href="https://crossplane.io/docs/master/stacks-guide.html" target="_blank" rel="noopener noreferrer" - >{{ __('Crossplane') }}</a - > + >{{ __('Crossplane') }} + <gl-icon name="external-link" class="vertical-align-middle" /> + </a> </p> </div> </template> diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index 7576d36f27d..1d0807dc15d 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -6,6 +6,36 @@ import UsersSelect from './users_select'; import ZenMode from './zen_mode'; import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select'; import { parsePikadayDate, pikadayToString } from './lib/utils/datetime_utility'; +import { queryToObject, objectToQuery } from './lib/utils/url_utility'; + +function organizeQuery(obj, isFallbackKey = false) { + const sourceBranch = 'merge_request[source_branch]'; + const targetBranch = 'merge_request[target_branch]'; + + if (isFallbackKey) { + return { + [sourceBranch]: obj[sourceBranch], + }; + } + + return { + [sourceBranch]: obj[sourceBranch], + [targetBranch]: obj[targetBranch], + }; +} + +function format(searchTerm, isFallbackKey = false) { + const queryObject = queryToObject(searchTerm); + const organizeQueryObject = organizeQuery(queryObject, isFallbackKey); + const formattedQuery = objectToQuery(organizeQueryObject); + + return formattedQuery; +} + +function getFallbackKey() { + const searchTerm = format(document.location.search, true); + return ['autosave', document.location.pathname, searchTerm].join('/'); +} export default class IssuableForm { constructor(form) { @@ -57,16 +87,20 @@ export default class IssuableForm { } initAutosave() { - this.autosave = new Autosave(this.titleField, [ - document.location.pathname, - document.location.search, - 'title', - ]); - return new Autosave(this.descriptionField, [ - document.location.pathname, - document.location.search, - 'description', - ]); + const searchTerm = format(document.location.search); + const fallbackKey = getFallbackKey(); + + this.autosave = new Autosave( + this.titleField, + [document.location.pathname, searchTerm, 'title'], + `${fallbackKey}=title`, + ); + + return new Autosave( + this.descriptionField, + [document.location.pathname, searchTerm, 'description'], + `${fallbackKey}=description`, + ); } handleSubmit() { diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 4be0d05a9b7..202a44d5694 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -181,4 +181,36 @@ export function getWebSocketUrl(path) { return `${getWebSocketProtocol()}//${joinPaths(window.location.host, path)}`; } +/** + * Convert search query into an object + * + * @param {String} query from "document.location.search" + * @returns {Object} + * + * ex: "?one=1&two=2" into {one: 1, two: 2} + */ +export function queryToObject(query) { + const removeQuestionMarkFromQuery = String(query).startsWith('?') ? query.slice(1) : query; + return removeQuestionMarkFromQuery.split('&').reduce((accumulator, curr) => { + const p = curr.split('='); + accumulator[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + return accumulator; + }, {}); +} + +/** + * Convert search query object back into a search query + * + * @param {Object} obj that needs to be converted + * @returns {String} + * + * ex: {one: 1, two: 2} into "one=1&two=2" + * + */ +export function objectToQuery(obj) { + return Object.keys(obj) + .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(obj[k])}`) + .join('&'); +} + export { joinPaths }; diff --git a/app/assets/javascripts/monitoring/components/graph_group.vue b/app/assets/javascripts/monitoring/components/graph_group.vue index 3cb6ccb64b1..e01324372a7 100644 --- a/app/assets/javascripts/monitoring/components/graph_group.vue +++ b/app/assets/javascripts/monitoring/components/graph_group.vue @@ -30,9 +30,6 @@ export default { return this.collapseGroup && this.showGroup ? 'angle-down' : 'angle-right'; }, }, - created() { - this.showGroup = this.collapseGroup; - }, methods: { collapse() { this.showGroup = !this.showGroup; diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js index 01acfca158f..739ae1cea16 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, no-var, no-return-assign */ +/* eslint-disable func-names, no-return-assign */ import $ from 'jquery'; import Cookies from 'js-cookie'; @@ -90,19 +90,19 @@ export default class Project { } static initRefSwitcher() { - var refListItem = document.createElement('li'); - var refLink = document.createElement('a'); + const refListItem = document.createElement('li'); + const refLink = document.createElement('a'); refLink.href = '#'; return $('.js-project-refs-dropdown').each(function() { - var $dropdown = $(this); - var selected = $dropdown.data('selected'); - var fieldName = $dropdown.data('fieldName'); - var shouldVisit = Boolean($dropdown.data('visit')); - var $form = $dropdown.closest('form'); - var action = $form.attr('action'); - var linkTarget = mergeUrlParams(serializeForm($form[0]), action); + const $dropdown = $(this); + const selected = $dropdown.data('selected'); + const fieldName = $dropdown.data('fieldName'); + const shouldVisit = Boolean($dropdown.data('visit')); + const $form = $dropdown.closest('form'); + const action = $form.attr('action'); + const linkTarget = mergeUrlParams(serializeForm($form[0]), action); return $dropdown.glDropdown({ data(term, callback) { @@ -123,9 +123,9 @@ export default class Project { inputFieldName: $dropdown.data('inputFieldName'), fieldName, renderRow(ref) { - var li = refListItem.cloneNode(false); + const li = refListItem.cloneNode(false); - var link = refLink.cloneNode(false); + const link = refLink.cloneNode(false); if (ref === selected) { link.className = 'is-active'; diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 65e87bb08a7..0623ba746c2 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -88,10 +88,6 @@ class DiffNote < Note line&.suggestible? end - def discussion_first_note? - self == discussion.first_note - end - def banzai_render_context(field) super.merge(suggestions_filter_enabled: true) end @@ -108,7 +104,7 @@ class DiffNote < Note end def should_create_diff_file? - on_text? && note_diff_file.nil? && discussion_first_note? + on_text? && note_diff_file.nil? && start_of_discussion? end def fetch_diff_file diff --git a/app/models/discussion.rb b/app/models/discussion.rb index b8525f7b135..d0a7db39a30 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -139,10 +139,6 @@ class Discussion false end - def new_discussion? - notes.length == 1 - end - def last_note @last_note ||= notes.last end diff --git a/app/models/note.rb b/app/models/note.rb index ce60413b8a0..1996fd4bff5 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -409,6 +409,10 @@ class Note < ApplicationRecord full_discussion || to_discussion end + def start_of_discussion? + discussion.first_note == self + end + def part_of_discussion? !to_discussion.individual_note? end diff --git a/app/services/issuable/common_system_notes_service.rb b/app/services/issuable/common_system_notes_service.rb index a170a4dcae2..846b881e819 100644 --- a/app/services/issuable/common_system_notes_service.rb +++ b/app/services/issuable/common_system_notes_service.rb @@ -7,20 +7,24 @@ module Issuable def execute(issuable, old_labels: [], is_update: true) @issuable = issuable - if is_update - if issuable.previous_changes.include?('title') - create_title_change_note(issuable.previous_changes['title'].first) + # We disable touch so that created system notes do not update + # the noteable's updated_at field + ActiveRecord::Base.no_touching do + if is_update + if issuable.previous_changes.include?('title') + create_title_change_note(issuable.previous_changes['title'].first) + end + + handle_description_change_note + + handle_time_tracking_note if issuable.is_a?(TimeTrackable) + create_discussion_lock_note if issuable.previous_changes.include?('discussion_locked') end - handle_description_change_note - - handle_time_tracking_note if issuable.is_a?(TimeTrackable) - create_discussion_lock_note if issuable.previous_changes.include?('discussion_locked') + create_due_date_note if issuable.previous_changes.include?('due_date') + create_milestone_note if issuable.previous_changes.include?('milestone_id') + create_labels_note(old_labels) if old_labels && issuable.labels != old_labels end - - create_due_date_note if issuable.previous_changes.include?('due_date') - create_milestone_note if issuable.previous_changes.include?('milestone_id') - create_labels_note(old_labels) if old_labels && issuable.labels != old_labels end private diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 200eca0e43c..bb65a8f402d 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -164,9 +164,7 @@ class IssuableBaseService < BaseService before_create(issuable) if issuable.save - ActiveRecord::Base.no_touching do - Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, is_update: false) - end + Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, is_update: false) after_create(issuable) execute_hooks(issuable) @@ -227,10 +225,7 @@ class IssuableBaseService < BaseService ensure_milestone_available(issuable) if issuable.with_transaction_returning_status { issuable.save(touch: should_touch) } - # We do not touch as it will affect a update on updated_at field - ActiveRecord::Base.no_touching do - Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: old_associations[:labels]) - end + Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: old_associations[:labels]) handle_changes(issuable, old_associations: old_associations) @@ -264,10 +259,7 @@ class IssuableBaseService < BaseService before_update(issuable, skip_spam_check: true) if issuable.with_transaction_returning_status { issuable.save } - # We do not touch as it will affect a update on updated_at field - ActiveRecord::Base.no_touching do - Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: nil) - end + Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: nil) handle_task_changes(issuable) invalidate_cache_counts(issuable, users: issuable.assignees.to_a) diff --git a/app/services/notes/base_service.rb b/app/services/notes/base_service.rb index b4d04c47cc0..87f7cb0e8ac 100644 --- a/app/services/notes/base_service.rb +++ b/app/services/notes/base_service.rb @@ -4,7 +4,7 @@ module Notes class BaseService < ::BaseService def clear_noteable_diffs_cache(note) if note.is_a?(DiffNote) && - note.discussion_first_note? && + note.start_of_discussion? && note.position.unfolded_diff?(project.repository) note.noteable.diffs.clear_cache end diff --git a/app/views/notify/_note_email.html.haml b/app/views/notify/_note_email.html.haml index dc5529b489b..c558358725c 100644 --- a/app/views/notify/_note_email.html.haml +++ b/app/views/notify/_note_email.html.haml @@ -11,7 +11,7 @@ - if discussion.nil? commented - else - - if discussion.new_discussion? + - if note.start_of_discussion? started a new - else commented on a diff --git a/app/views/notify/_note_email.text.erb b/app/views/notify/_note_email.text.erb index a25daad8458..8e2f7e6f76e 100644 --- a/app/views/notify/_note_email.text.erb +++ b/app/views/notify/_note_email.text.erb @@ -7,7 +7,7 @@ <% if discussion.nil? -%> <%= 'commented' -%>: <% else -%> -<% if discussion.new_discussion? -%> +<% if note.start_of_discussion? -%> <%= 'started a new discussion' -%> <% else -%> <%= 'commented on a discussion' -%> diff --git a/changelogs/unreleased/30016-changing-branch-keep-details.yml b/changelogs/unreleased/30016-changing-branch-keep-details.yml new file mode 100644 index 00000000000..05a280575e5 --- /dev/null +++ b/changelogs/unreleased/30016-changing-branch-keep-details.yml @@ -0,0 +1,5 @@ +--- +title: Keep details in MR when changing target branch +merge_request: 19138 +author: +type: changed diff --git a/changelogs/unreleased/34734-monitor-dashboard-does-not-expand-sections-that-have-data-on-load.yml b/changelogs/unreleased/34734-monitor-dashboard-does-not-expand-sections-that-have-data-on-load.yml new file mode 100644 index 00000000000..c6c5df2e698 --- /dev/null +++ b/changelogs/unreleased/34734-monitor-dashboard-does-not-expand-sections-that-have-data-on-load.yml @@ -0,0 +1,5 @@ +--- +title: Fix graph groups in monitor dashboard that are hidden on load +merge_request: 20312 +author: +type: fixed diff --git a/changelogs/unreleased/36450-update-the-saas-trial-copy-to-be-dynamic-depending-on-the-trial-typ.yml b/changelogs/unreleased/36450-update-the-saas-trial-copy-to-be-dynamic-depending-on-the-trial-typ.yml new file mode 100644 index 00000000000..e2996ed45b0 --- /dev/null +++ b/changelogs/unreleased/36450-update-the-saas-trial-copy-to-be-dynamic-depending-on-the-trial-typ.yml @@ -0,0 +1,5 @@ +--- +title: SaaS trial copy shows plan +merge_request: 20207 +author: +type: changed diff --git a/changelogs/unreleased/36455-add-external-link-icon.yml b/changelogs/unreleased/36455-add-external-link-icon.yml new file mode 100644 index 00000000000..7bbbb772ce9 --- /dev/null +++ b/changelogs/unreleased/36455-add-external-link-icon.yml @@ -0,0 +1,5 @@ +--- +title: Add missing external-link icon for Crossplane managed app +merge_request: 20283 +author: +type: fixed diff --git a/changelogs/unreleased/3695-view-closed-issues-in-epic.yml b/changelogs/unreleased/3695-view-closed-issues-in-epic.yml new file mode 100644 index 00000000000..a7cddcbc6dc --- /dev/null +++ b/changelogs/unreleased/3695-view-closed-issues-in-epic.yml @@ -0,0 +1,5 @@ +--- +title: View closed issues in epic +merge_request: 19741 +author: +type: added diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4f0e112a49d..ce9bc053b98 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2515,10 +2515,10 @@ msgstr "" msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}." msgstr "" -msgid "BillingPlans|Your GitLab.com Gold trial expired on %{expiration_date}. You can restore access to the Gold features at any time by upgrading below." +msgid "BillingPlans|Your GitLab.com %{plan} trial will <strong>expire after %{expiration_date}</strong>. You can retain access to the %{plan} features by upgrading below." msgstr "" -msgid "BillingPlans|Your GitLab.com Gold trial will <strong>expire after %{expiration_date}</strong>. You can retain access to the Gold features by upgrading below." +msgid "BillingPlans|Your GitLab.com trial expired on %{expiration_date}. You can restore access to the features at any time by upgrading below." msgstr "" msgid "BillingPlans|billed annually at %{price_per_year}" @@ -5770,7 +5770,7 @@ msgstr "" msgid "DesignManagement|Could not add a new comment. Please try again" msgstr "" -msgid "DesignManagement|Could not create new discussion, please try again." +msgid "DesignManagement|Could not create new discussion. Please try again." msgstr "" msgid "DesignManagement|Could not find design, please try again." @@ -5821,9 +5821,6 @@ msgstr "" msgid "DesignManagement|We could not delete %{design}. Please try again." msgstr "" -msgid "DesignManagement|We could not delete design(s). Please try again." -msgstr "" - msgid "Designs" msgstr "" diff --git a/scripts/review_apps/automated_cleanup.rb b/scripts/review_apps/automated_cleanup.rb index c7ab8829088..01bf149e76e 100755 --- a/scripts/review_apps/automated_cleanup.rb +++ b/scripts/review_apps/automated_cleanup.rb @@ -75,9 +75,11 @@ class AutomatedCleanup deployed_at = Time.parse(last_deploy) if deployed_at < delete_threshold - delete_environment(environment, deployment) - release = Quality::HelmClient::Release.new(environment.slug, 1, deployed_at.to_s, nil, nil, review_apps_namespace) - releases_to_delete << release + environment = delete_environment(environment, deployment) + if environment + release = Quality::HelmClient::Release.new(environment.slug, 1, deployed_at.to_s, nil, nil, review_apps_namespace) + releases_to_delete << release + end elsif deployed_at < stop_threshold stop_environment(environment, deployment) else @@ -116,11 +118,17 @@ class AutomatedCleanup def delete_environment(environment, deployment) print_release_state(subject: 'Review app', release_name: environment.slug, release_date: deployment.created_at, action: 'deleting') gitlab.delete_environment(project_path, environment.id) + + rescue Gitlab::Error::Forbidden + puts "Review app '#{environment.slug}' is forbidden: skipping it" end def stop_environment(environment, deployment) print_release_state(subject: 'Review app', release_name: environment.slug, release_date: deployment.created_at, action: 'stopping') gitlab.stop_environment(project_path, environment.id) + + rescue Gitlab::Error::Forbidden + puts "Review app '#{environment.slug}' is forbidden: skipping it" end def helm_releases diff --git a/spec/frontend/autosave_spec.js b/spec/frontend/autosave_spec.js index 33d402388c9..1d73e452eb4 100644 --- a/spec/frontend/autosave_spec.js +++ b/spec/frontend/autosave_spec.js @@ -9,6 +9,7 @@ describe('Autosave', () => { let autosave; const field = $('<textarea></textarea>'); const key = 'key'; + const fallbackKey = 'fallbackKey'; describe('class constructor', () => { beforeEach(() => { @@ -22,6 +23,13 @@ describe('Autosave', () => { expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled(); expect(autosave.isLocalStorageAvailable).toBe(true); }); + + it('should set .isLocalStorageAvailable if fallbackKey is passed', () => { + autosave = new Autosave(field, key, fallbackKey); + + expect(AccessorUtilities.isLocalStorageAccessSafe).toHaveBeenCalled(); + expect(autosave.isLocalStorageAvailable).toBe(true); + }); }); describe('restore', () => { @@ -151,4 +159,33 @@ describe('Autosave', () => { }); }); }); + + describe('restore with fallbackKey', () => { + beforeEach(() => { + autosave = { + field, + key, + fallbackKey, + }; + autosave.isLocalStorageAvailable = true; + }); + + it('should call .getItem', () => { + Autosave.prototype.restore.call(autosave); + + expect(window.localStorage.getItem).toHaveBeenCalledWith(fallbackKey); + }); + + it('should call .setItem for key and fallbackKey', () => { + Autosave.prototype.save.call(autosave); + + expect(window.localStorage.setItem).toHaveBeenCalledTimes(2); + }); + + it('should call .removeItem for key and fallbackKey', () => { + Autosave.prototype.reset.call(autosave); + + expect(window.localStorage.removeItem).toHaveBeenCalledTimes(2); + }); + }); }); diff --git a/spec/frontend/clusters/services/crossplane_provider_stack_spec.js b/spec/frontend/clusters/services/crossplane_provider_stack_spec.js index 0d234822d7b..d43dc9333b4 100644 --- a/spec/frontend/clusters/services/crossplane_provider_stack_spec.js +++ b/spec/frontend/clusters/services/crossplane_provider_stack_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { GlDropdownItem } from '@gitlab/ui'; +import { GlDropdownItem, GlIcon } from '@gitlab/ui'; import CrossplaneProviderStack from '~/clusters/components/crossplane_provider_stack.vue'; describe('CrossplaneProviderStack component', () => { @@ -72,7 +72,12 @@ describe('CrossplaneProviderStack component', () => { findFirstDropdownElement().vm.$emit('click'); expect(wrapper.emitted().set[0][0].code).toEqual('gcp'); }); - it('it renders the correct dropdown text when no stack is selected', () => { + + it('renders the correct dropdown text when no stack is selected', () => { expect(wrapper.vm.dropdownText).toBe('Select Stack'); }); + + it('renders an external link', () => { + expect(wrapper.find(GlIcon).props('name')).toBe('external-link'); + }); }); diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js index 6edb2e2dce2..8244acbceea 100644 --- a/spec/frontend/lib/utils/url_utility_spec.js +++ b/spec/frontend/lib/utils/url_utility_spec.js @@ -282,4 +282,20 @@ describe('URL utility', () => { expect(urlUtils.getWebSocketUrl(path)).toEqual('ws://example.com/lorem/ipsum?a=bc'); }); }); + + describe('queryToObject', () => { + it('converts search query into an object', () => { + const searchQuery = '?one=1&two=2'; + + expect(urlUtils.queryToObject(searchQuery)).toEqual({ one: '1', two: '2' }); + }); + }); + + describe('objectToQuery', () => { + it('converts search query object back into a search query', () => { + const searchQueryObject = { one: '1', two: '2' }; + + expect(urlUtils.objectToQuery(searchQueryObject)).toEqual('one=1&two=2'); + }); + }); }); diff --git a/spec/frontend/notes/components/note_edited_text_spec.js b/spec/frontend/notes/components/note_edited_text_spec.js index e4c8d954d50..e8d5a24e86a 100644 --- a/spec/frontend/notes/components/note_edited_text_spec.js +++ b/spec/frontend/notes/components/note_edited_text_spec.js @@ -1,47 +1,49 @@ -import Vue from 'vue'; -import noteEditedText from '~/notes/components/note_edited_text.vue'; - -describe('note_edited_text', () => { - let vm; - let props; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import NoteEditedText from '~/notes/components/note_edited_text.vue'; + +const localVue = createLocalVue(); +const propsData = { + actionText: 'Edited', + className: 'foo-bar', + editedAt: '2017-08-04T09:52:31.062Z', + editedBy: { + avatar_url: 'path', + id: 1, + name: 'Root', + path: '/root', + state: 'active', + username: 'root', + }, +}; + +describe('NoteEditedText', () => { + let wrapper; beforeEach(() => { - const Component = Vue.extend(noteEditedText); - props = { - actionText: 'Edited', - className: 'foo-bar', - editedAt: '2017-08-04T09:52:31.062Z', - editedBy: { - avatar_url: 'path', - id: 1, - name: 'Root', - path: '/root', - state: 'active', - username: 'root', - }, - }; - - vm = new Component({ - propsData: props, - }).$mount(); + wrapper = shallowMount(NoteEditedText, { + localVue, + propsData, + sync: false, + attachToDocument: true, + }); }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); it('should render block with provided className', () => { - expect(vm.$el.className).toEqual(props.className); + expect(wrapper.classes()).toContain(propsData.className); }); it('should render provided actionText', () => { - expect(vm.$el.textContent).toContain(props.actionText); + expect(wrapper.text().trim()).toContain(propsData.actionText); }); it('should render provided user information', () => { - const authorLink = vm.$el.querySelector('.js-user-link'); + const authorLink = wrapper.find('.js-user-link'); - expect(authorLink.getAttribute('href')).toEqual(props.editedBy.path); - expect(authorLink.textContent.trim()).toEqual(props.editedBy.name); + expect(authorLink.attributes('href')).toEqual(propsData.editedBy.path); + expect(authorLink.text().trim()).toEqual(propsData.editedBy.name); }); }); diff --git a/spec/frontend/registry/components/app_spec.js b/spec/frontend/registry/components/app_spec.js index a69c33c246d..2bb21c12fc9 100644 --- a/spec/frontend/registry/components/app_spec.js +++ b/spec/frontend/registry/components/app_spec.js @@ -39,6 +39,8 @@ describe('Registry List', () => { // See https://github.com/vuejs/vue-test-utils/issues/532. Vue.config.silent = true; wrapper = mount(registry, { + attachToDocument: true, + sync: false, propsData, computed: { repos() { @@ -67,6 +69,8 @@ describe('Registry List', () => { let localWrapper; beforeEach(() => { localWrapper = mount(registry, { + attachToDocument: true, + sync: false, propsData, computed: { repos() { diff --git a/spec/frontend/registry/components/collapsible_container_spec.js b/spec/frontend/registry/components/collapsible_container_spec.js index d035055afd3..3d07ab63776 100644 --- a/spec/frontend/registry/components/collapsible_container_spec.js +++ b/spec/frontend/registry/components/collapsible_container_spec.js @@ -22,7 +22,14 @@ describe('collapsible registry container', () => { const findToggleRepos = (w = wrapper) => w.findAll('.js-toggle-repo'); const findDeleteModal = (w = wrapper) => w.find({ ref: 'deleteModal' }); - const mountWithStore = config => mount(collapsibleComponent, { ...config, store, localVue }); + const mountWithStore = config => + mount(collapsibleComponent, { + ...config, + store, + localVue, + attachToDocument: true, + sync: false, + }); beforeEach(() => { createFlash.mockClear(); @@ -63,12 +70,15 @@ describe('collapsible registry container', () => { it('should be closed by default', () => { expectIsClosed(); }); - it('should be open when user clicks on closed repo', () => { + it('should be open when user clicks on closed repo', done => { const toggleRepos = findToggleRepos(wrapper); toggleRepos.at(0).trigger('click'); - const container = findContainerImageTags(wrapper); - expect(container.exists()).toBe(true); - expect(wrapper.vm.fetchList).toHaveBeenCalled(); + Vue.nextTick(() => { + const container = findContainerImageTags(wrapper); + expect(container.exists()).toBe(true); + expect(wrapper.vm.fetchList).toHaveBeenCalled(); + done(); + }); }); it('should be closed when the user clicks on an opened repo', done => { const toggleRepos = findToggleRepos(wrapper); diff --git a/spec/frontend/registry/components/project_empty_state_spec.js b/spec/frontend/registry/components/project_empty_state_spec.js index 913524db3aa..dd0fe32b68c 100644 --- a/spec/frontend/registry/components/project_empty_state_spec.js +++ b/spec/frontend/registry/components/project_empty_state_spec.js @@ -6,6 +6,8 @@ describe('Registry Project Empty state', () => { beforeEach(() => { wrapper = mount(projectEmptyState, { + attachToDocument: true, + sync: false, propsData: { noContainersImage: 'imageUrl', helpPagePath: 'help', diff --git a/spec/frontend/registry/components/table_registry_spec.js b/spec/frontend/registry/components/table_registry_spec.js index b9075f565e9..4566e6ed705 100644 --- a/spec/frontend/registry/components/table_registry_spec.js +++ b/spec/frontend/registry/components/table_registry_spec.js @@ -28,7 +28,8 @@ describe('table registry', () => { const findImageId = (w = wrapper) => w.find({ ref: 'imageId' }); const bulkDeletePath = 'path'; - const mountWithStore = config => mount(tableRegistry, { ...config, store, localVue }); + const mountWithStore = config => + mount(tableRegistry, { ...config, store, localVue, attachToDocument: true, sync: false }); beforeEach(() => { // This is needed due to console.error called by vue to emit a warning that stop the tests @@ -196,7 +197,7 @@ describe('table registry', () => { expect(wrapper.vm.handleSingleDelete).toHaveBeenCalledWith(repoPropsData.list[0]); expect(wrapper.vm.handleMultipleDelete).not.toHaveBeenCalled(); }); - it('on ok when multiple items are selected should call muultiDelete', () => { + it('on ok when multiple items are selected should call multiDelete', () => { wrapper.setData({ itemsToBeDeleted: [0, 1, 2] }); wrapper.vm.onDeletionConfirmed(); diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js index f8f835ffdef..45b10fc3bd8 100644 --- a/spec/javascripts/line_highlighter_spec.js +++ b/spec/javascripts/line_highlighter_spec.js @@ -1,12 +1,11 @@ -/* eslint-disable no-var, no-else-return, dot-notation, no-return-assign, no-new, no-underscore-dangle */ +/* eslint-disable no-else-return, dot-notation, no-return-assign, no-new, no-underscore-dangle */ import $ from 'jquery'; import LineHighlighter from '~/line_highlighter'; describe('LineHighlighter', function() { - var clickLine; preloadFixtures('static/line_highlighter.html'); - clickLine = function(number, eventData = {}) { + const clickLine = function(number, eventData = {}) { if ($.isEmptyObject(eventData)) { return $(`#L${number}`).click(); } else { @@ -39,34 +38,30 @@ describe('LineHighlighter', function() { }); it('highlights a range of lines given in the URL hash', function() { - var line; new LineHighlighter({ hash: '#L5-25' }); expect($(`.${this.css}`).length).toBe(21); - for (line = 5; line <= 25; line += 1) { + for (let line = 5; line <= 25; line += 1) { expect($(`#LC${line}`)).toHaveClass(this.css); } }); it('scrolls to the first highlighted line on initial load', function() { - var spy; - spy = spyOn($, 'scrollTo'); + const spy = spyOn($, 'scrollTo'); new LineHighlighter({ hash: '#L5-25' }); expect(spy).toHaveBeenCalledWith('#L5', jasmine.anything()); }); it('discards click events', function() { - var spy; - spy = spyOnEvent('a[data-line-number]', 'click'); + const spy = spyOnEvent('a[data-line-number]', 'click'); clickLine(13); expect(spy).toHaveBeenPrevented(); }); it('handles garbage input from the hash', function() { - var func; - func = function() { + const func = function() { return new LineHighlighter({ fileHolderSelector: '#blob-content-holder' }); }; @@ -76,8 +71,7 @@ describe('LineHighlighter', function() { describe('clickHandler', function() { it('handles clicking on a child icon element', function() { - var spy; - spy = spyOn(this['class'], 'setHash').and.callThrough(); + const spy = spyOn(this['class'], 'setHash').and.callThrough(); $('#L13 i') .mousedown() .click(); @@ -102,8 +96,7 @@ describe('LineHighlighter', function() { }); it('sets the hash', function() { - var spy; - spy = spyOn(this['class'], 'setHash').and.callThrough(); + const spy = spyOn(this['class'], 'setHash').and.callThrough(); clickLine(13); expect(spy).toHaveBeenCalledWith(13); @@ -112,8 +105,7 @@ describe('LineHighlighter', function() { describe('with shiftKey', function() { it('sets the hash', function() { - var spy; - spy = spyOn(this['class'], 'setHash').and.callThrough(); + const spy = spyOn(this['class'], 'setHash').and.callThrough(); clickLine(13); clickLine(20, { shiftKey: true, @@ -134,8 +126,7 @@ describe('LineHighlighter', function() { }); it('sets the hash', function() { - var spy; - spy = spyOn(this['class'], 'setHash'); + const spy = spyOn(this['class'], 'setHash'); clickLine(13, { shiftKey: true, }); @@ -146,27 +137,25 @@ describe('LineHighlighter', function() { describe('with existing single-line highlight', function() { it('uses existing line as last line when target is lesser', function() { - var line; clickLine(20); clickLine(15, { shiftKey: true, }); expect($(`.${this.css}`).length).toBe(6); - for (line = 15; line <= 20; line += 1) { + for (let line = 15; line <= 20; line += 1) { expect($(`#LC${line}`)).toHaveClass(this.css); } }); it('uses existing line as first line when target is greater', function() { - var line; clickLine(5); clickLine(10, { shiftKey: true, }); expect($(`.${this.css}`).length).toBe(6); - for (line = 5; line <= 10; line += 1) { + for (let line = 5; line <= 10; line += 1) { expect($(`#LC${line}`)).toHaveClass(this.css); } }); @@ -183,25 +172,23 @@ describe('LineHighlighter', function() { }); it('uses target as first line when it is less than existing first line', function() { - var line; clickLine(5, { shiftKey: true, }); expect($(`.${this.css}`).length).toBe(6); - for (line = 5; line <= 10; line += 1) { + for (let line = 5; line <= 10; line += 1) { expect($(`#LC${line}`)).toHaveClass(this.css); } }); it('uses target as last line when it is greater than existing first line', function() { - var line; clickLine(15, { shiftKey: true, }); expect($(`.${this.css}`).length).toBe(6); - for (line = 10; line <= 15; line += 1) { + for (let line = 10; line <= 15; line += 1) { expect($(`#LC${line}`)).toHaveClass(this.css); } }); diff --git a/spec/javascripts/monitoring/components/graph_group_spec.js b/spec/javascripts/monitoring/components/graph_group_spec.js index 068c4b5302c..7bcab9116e9 100644 --- a/spec/javascripts/monitoring/components/graph_group_spec.js +++ b/spec/javascripts/monitoring/components/graph_group_spec.js @@ -3,6 +3,8 @@ import GraphGroup from '~/monitoring/components/graph_group.vue'; describe('Graph group component', () => { let graphGroup; + const findPrometheusGroup = () => graphGroup.find('.prometheus-graph-group'); + const findPrometheusPanel = () => graphGroup.find('.prometheus-panel'); afterEach(() => { graphGroup.destroy(); @@ -40,8 +42,32 @@ describe('Graph group component', () => { }); }); - it('should not contain a prometheus-graph-group container when showPanels is false', () => { - expect(graphGroup.vm.$el.querySelector('.prometheus-graph-group')).toBe(null); + it('should not contain a prometheus-panel container when showPanels is false', () => { + expect(findPrometheusPanel().exists()).toBe(false); + }); + }); + + describe('When collapseGroup prop is updated', () => { + beforeEach(() => { + graphGroup = shallowMount(GraphGroup, { + propsData: { + name: 'panel', + collapseGroup: false, + }, + }); + }); + + it('previously collapsed group should respond to the prop change', done => { + expect(findPrometheusGroup().exists()).toBe(false); + + graphGroup.setProps({ + collapseGroup: true, + }); + + graphGroup.vm.$nextTick(() => { + expect(findPrometheusGroup().exists()).toBe(true); + done(); + }); }); }); }); diff --git a/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb b/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb index 134bd5657e7..6c4f650fa83 100644 --- a/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb +++ b/spec/lib/gitlab/git/attributes_at_ref_parser_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::AttributesAtRefParser, :seed_helper do diff --git a/spec/lib/gitlab/git/attributes_parser_spec.rb b/spec/lib/gitlab/git/attributes_parser_spec.rb index f431d4e2a53..94b7a086e59 100644 --- a/spec/lib/gitlab/git/attributes_parser_spec.rb +++ b/spec/lib/gitlab/git/attributes_parser_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::AttributesParser, :seed_helper do diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb index ac085e2c266..9b2d6fa3bcb 100644 --- a/spec/lib/gitlab/git/blame_spec.rb +++ b/spec/lib/gitlab/git/blame_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Blame, :seed_helper do diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index 7f680071969..15d03a481d5 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Blob, :seed_helper do diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb index 02ef7b92538..cc26b7e7fcd 100644 --- a/spec/lib/gitlab/git/branch_spec.rb +++ b/spec/lib/gitlab/git/branch_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Branch, :seed_helper do @@ -77,7 +79,7 @@ describe Gitlab::Git::Branch, :seed_helper do tree = parents.first.tree { - message: 'commit message', + message: +'commit message', author: committer, committer: committer, tree: tree, @@ -126,7 +128,7 @@ describe Gitlab::Git::Branch, :seed_helper do it { expect(repository.branches.size).to eq(SeedRepo::Repo::BRANCHES.size) } def create_commit - params[:message].delete!("\r") + params[:message].delete!(+"\r") Rugged::Commit.create(rugged, params.merge(committer: committer.merge(time: Time.now))) end end diff --git a/spec/lib/gitlab/git/bundle_file_spec.rb b/spec/lib/gitlab/git/bundle_file_spec.rb index ff7c981dadd..e88e163a03f 100644 --- a/spec/lib/gitlab/git/bundle_file_spec.rb +++ b/spec/lib/gitlab/git/bundle_file_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::BundleFile do diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index cdab7127748..6c9467916de 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Commit, :seed_helper do @@ -64,8 +66,8 @@ describe Gitlab::Git::Commit, :seed_helper do end describe "Commit info from gitaly commit" do - let(:subject) { "My commit".force_encoding('ASCII-8BIT') } - let(:body) { subject + "My body".force_encoding('ASCII-8BIT') } + let(:subject) { (+"My commit").force_encoding('ASCII-8BIT') } + let(:body) { subject + (+"My body").force_encoding('ASCII-8BIT') } let(:body_size) { body.length } let(:gitaly_commit) { build(:gitaly_commit, subject: subject, body: body, body_size: body_size) } let(:id) { gitaly_commit.id } @@ -85,7 +87,7 @@ describe Gitlab::Git::Commit, :seed_helper do it { expect(commit.parent_ids).to eq(gitaly_commit.parent_ids) } context 'body_size != body.size' do - let(:body) { "".force_encoding('ASCII-8BIT') } + let(:body) { (+"").force_encoding('ASCII-8BIT') } context 'zero body_size' do it { expect(commit.safe_message).to eq(subject) } diff --git a/spec/lib/gitlab/git/compare_spec.rb b/spec/lib/gitlab/git/compare_spec.rb index 65dfb93d0db..6136df57acb 100644 --- a/spec/lib/gitlab/git/compare_spec.rb +++ b/spec/lib/gitlab/git/compare_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Compare, :seed_helper do diff --git a/spec/lib/gitlab/git/conflict/file_spec.rb b/spec/lib/gitlab/git/conflict/file_spec.rb index a6cabd4966a..0ee9ff93e87 100644 --- a/spec/lib/gitlab/git/conflict/file_spec.rb +++ b/spec/lib/gitlab/git/conflict/file_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::Conflict::File do let(:conflict) { { theirs: { path: 'foo', mode: 33188 }, ours: { path: 'foo', mode: 33188 } } } - let(:invalid_content) { described_class.new(nil, nil, conflict, "a\xC4\xFC".force_encoding(Encoding::ASCII_8BIT)) } - let(:valid_content) { described_class.new(nil, nil, conflict, "Espa\xC3\xB1a".force_encoding(Encoding::ASCII_8BIT)) } + let(:invalid_content) { described_class.new(nil, nil, conflict, (+"a\xC4\xFC").force_encoding(Encoding::ASCII_8BIT)) } + let(:valid_content) { described_class.new(nil, nil, conflict, (+"Espa\xC3\xB1a").force_encoding(Encoding::ASCII_8BIT)) } describe '#lines' do context 'when the content contains non-UTF-8 characters' do diff --git a/spec/lib/gitlab/git/conflict/parser_spec.rb b/spec/lib/gitlab/git/conflict/parser_spec.rb index 29a1702a1c6..600c870acd4 100644 --- a/spec/lib/gitlab/git/conflict/parser_spec.rb +++ b/spec/lib/gitlab/git/conflict/parser_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::Conflict::Parser do @@ -208,7 +210,7 @@ CONFLICT # these strings. context 'when the file contains UTF-8 characters' do it 'does not raise' do - expect { parse_text("Espa\xC3\xB1a".force_encoding(Encoding::ASCII_8BIT)) } + expect { parse_text((+"Espa\xC3\xB1a").force_encoding(Encoding::ASCII_8BIT)) } .not_to raise_error end end diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb index ce45d6e24ba..0d19d35bc52 100644 --- a/spec/lib/gitlab/git/diff_collection_spec.rb +++ b/spec/lib/gitlab/git/diff_collection_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::DiffCollection, :seed_helper do diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb index 9ab669ad488..456d6af7bd8 100644 --- a/spec/lib/gitlab/git/diff_spec.rb +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Diff, :seed_helper do diff --git a/spec/lib/gitlab/git/gitmodules_parser_spec.rb b/spec/lib/gitlab/git/gitmodules_parser_spec.rb index de81dcd227d..58d1d2c71da 100644 --- a/spec/lib/gitlab/git/gitmodules_parser_spec.rb +++ b/spec/lib/gitlab/git/gitmodules_parser_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::GitmodulesParser do diff --git a/spec/lib/gitlab/git/hook_env_spec.rb b/spec/lib/gitlab/git/hook_env_spec.rb index 5e49ea6da7a..ca6a4ad42a3 100644 --- a/spec/lib/gitlab/git/hook_env_spec.rb +++ b/spec/lib/gitlab/git/hook_env_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::HookEnv do diff --git a/spec/lib/gitlab/git/lfs_changes_spec.rb b/spec/lib/gitlab/git/lfs_changes_spec.rb index d035df7e0c2..a99e8c4f60c 100644 --- a/spec/lib/gitlab/git/lfs_changes_spec.rb +++ b/spec/lib/gitlab/git/lfs_changes_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::LfsChanges do diff --git a/spec/lib/gitlab/git/lfs_pointer_file_spec.rb b/spec/lib/gitlab/git/lfs_pointer_file_spec.rb index d7f76737f3f..8bb26ed4854 100644 --- a/spec/lib/gitlab/git/lfs_pointer_file_spec.rb +++ b/spec/lib/gitlab/git/lfs_pointer_file_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::LfsPointerFile do diff --git a/spec/lib/gitlab/git/pre_receive_error_spec.rb b/spec/lib/gitlab/git/pre_receive_error_spec.rb index cb030e38032..cb539261671 100644 --- a/spec/lib/gitlab/git/pre_receive_error_spec.rb +++ b/spec/lib/gitlab/git/pre_receive_error_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::PreReceiveError do diff --git a/spec/lib/gitlab/git/push_spec.rb b/spec/lib/gitlab/git/push_spec.rb index 566c8209504..32c4c1c82d4 100644 --- a/spec/lib/gitlab/git/push_spec.rb +++ b/spec/lib/gitlab/git/push_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::Push do diff --git a/spec/lib/gitlab/git/raw_diff_change_spec.rb b/spec/lib/gitlab/git/raw_diff_change_spec.rb index a0bb37fd84a..79b2fc21011 100644 --- a/spec/lib/gitlab/git/raw_diff_change_spec.rb +++ b/spec/lib/gitlab/git/raw_diff_change_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::RawDiffChange do diff --git a/spec/lib/gitlab/git/remote_mirror_spec.rb b/spec/lib/gitlab/git/remote_mirror_spec.rb index dc63eef7814..9744562b51b 100644 --- a/spec/lib/gitlab/git/remote_mirror_spec.rb +++ b/spec/lib/gitlab/git/remote_mirror_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::RemoteMirror do diff --git a/spec/lib/gitlab/git/remote_repository_spec.rb b/spec/lib/gitlab/git/remote_repository_spec.rb index e166628d4ca..556cc692231 100644 --- a/spec/lib/gitlab/git/remote_repository_spec.rb +++ b/spec/lib/gitlab/git/remote_repository_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::RemoteRepository, :seed_helper do diff --git a/spec/lib/gitlab/git/repository_cleaner_spec.rb b/spec/lib/gitlab/git/repository_cleaner_spec.rb index 7bba0107e58..b387d1033d3 100644 --- a/spec/lib/gitlab/git/repository_cleaner_spec.rb +++ b/spec/lib/gitlab/git/repository_cleaner_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::RepositoryCleaner do diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 44c41da7560..20a74af7a45 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Repository, :seed_helper do diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb index 4c0291f64f0..87db3f588ad 100644 --- a/spec/lib/gitlab/git/tag_spec.rb +++ b/spec/lib/gitlab/git/tag_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Tag, :seed_helper do diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb index 7e169cfe270..98bb4ffd0bd 100644 --- a/spec/lib/gitlab/git/tree_spec.rb +++ b/spec/lib/gitlab/git/tree_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "spec_helper" describe Gitlab::Git::Tree, :seed_helper do diff --git a/spec/lib/gitlab/git/user_spec.rb b/spec/lib/gitlab/git/user_spec.rb index d9d338206f8..277b1c48355 100644 --- a/spec/lib/gitlab/git/user_spec.rb +++ b/spec/lib/gitlab/git/user_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::User do diff --git a/spec/lib/gitlab/git/util_spec.rb b/spec/lib/gitlab/git/util_spec.rb index 88c871855df..81918f036f9 100644 --- a/spec/lib/gitlab/git/util_spec.rb +++ b/spec/lib/gitlab/git/util_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::Util do diff --git a/spec/lib/gitlab/git/wiki_spec.rb b/spec/lib/gitlab/git/wiki_spec.rb index 1e577392949..8bae2e8125e 100644 --- a/spec/lib/gitlab/git/wiki_spec.rb +++ b/spec/lib/gitlab/git/wiki_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::Wiki do diff --git a/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb b/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb index bcf4814edb6..a4489cca443 100644 --- a/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb +++ b/spec/lib/gitlab/git/wraps_gitaly_errors_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Git::WrapsGitalyErrors do diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb index 8401b683fd5..ea0a6e1b967 100644 --- a/spec/lib/gitlab/gpg/commit_spec.rb +++ b/spec/lib/gitlab/gpg/commit_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Gitlab::Gpg::Commit do diff --git a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb index da307754243..c1516a48b80 100644 --- a/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb +++ b/spec/lib/gitlab/gpg/invalid_gpg_signature_updater_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater do diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index df82ac38fc6..ad256c10964 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -990,7 +990,8 @@ describe Notify do end context 'when a comment on an existing discussion' do - let!(:second_note) { create(model, author: note_author, noteable: nil, in_reply_to: note) } + let(:first_note) { create_note } + let(:note) { create(model, author: note_author, noteable: nil, in_reply_to: first_note) } it 'contains an introduction' do is_expected.to have_body_text 'commented on a' @@ -1000,7 +1001,11 @@ describe Notify do describe 'on a commit' do let(:commit) { project.commit } - let(:note) { create(:discussion_note_on_commit, commit_id: commit.id, project: project, author: note_author) } + let(:note) { create_note } + + def create_note + create(:discussion_note_on_commit, commit_id: commit.id, project: project, author: note_author) + end before do allow(note).to receive(:noteable).and_return(commit) @@ -1027,9 +1032,13 @@ describe Notify do end describe 'on a merge request' do - let(:note) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project, author: note_author) } + let(:note) { create_note } let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") } + def create_note + create(:discussion_note_on_merge_request, noteable: merge_request, project: project, author: note_author) + end + before do allow(note).to receive(:noteable).and_return(merge_request) end @@ -1055,9 +1064,13 @@ describe Notify do end describe 'on an issue' do - let(:note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: note_author) } + let(:note) { create_note } let(:note_on_issue_path) { project_issue_path(project, issue, anchor: "note_#{note.id}") } + def create_note + create(:discussion_note_on_issue, noteable: issue, project: project, author: note_author) + end + before do allow(note).to receive(:noteable).and_return(issue) end @@ -1134,7 +1147,8 @@ describe Notify do end context 'when a comment on an existing discussion' do - let!(:second_note) { create(model, author: note_author, noteable: nil, in_reply_to: note) } + let(:first_note) { create(model) } + let(:note) { create(model, author: note_author, noteable: nil, in_reply_to: first_note) } it 'contains an introduction' do is_expected.to have_body_text 'commented on a discussion on' diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 3ab88b52568..b09e699a8a7 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -456,6 +456,19 @@ describe Note do end end + describe '#start_of_discussion?' do + let_it_be(:note) { create(:discussion_note_on_merge_request) } + let_it_be(:reply) { create(:discussion_note_on_merge_request, in_reply_to: note) } + + it 'returns true when note is the start of a discussion' do + expect(note).to be_start_of_discussion + end + + it 'returns false when note is a reply' do + expect(reply).not_to be_start_of_discussion + end + end + describe '.find_discussion' do let!(:note) { create(:discussion_note_on_merge_request) } let!(:note2) { create(:discussion_note_on_merge_request, in_reply_to: note) } |