From 87543246d9d68066b216811f3de590689e218e7c Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 24 Jan 2022 15:11:33 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .rubocop_todo/graphql/ordered_arguments.yml | 4 - .../application_settings/payload_previewer.js | 21 +++-- .../projects/group_links_controller.rb | 2 +- .../resolvers/merge_requests_resolver.rb | 13 +-- .../resolvers/paginated_tree_resolver.rb | 8 +- app/graphql/resolvers/tree_resolver.rb | 8 +- .../resolvers/users/groups_resolver.rb | 6 +- app/models/member.rb | 7 ++ app/models/members/project_member.rb | 7 ++ app/models/state_note.rb | 4 +- app/services/issues/close_service.rb | 4 +- .../projects/import_export/export_service.rb | 7 +- app/services/system_note_service.rb | 4 - .../system_notes/alert_management_service.rb | 19 +--- app/services/system_notes/incident_service.rb | 6 -- .../application_settings/_usage.html.haml | 4 +- app/views/layouts/_page.html.haml | 2 +- .../14-8-iteration-started-field.yml | 15 +++ .../service_ping/metrics_lifecycle.md | 7 +- doc/update/deprecations.md | 12 +++ lib/gitlab/import_export/saver.rb | 2 + qa/qa/resource/project_access_token.rb | 4 +- .../api/1_manage/project_access_token_spec.rb | 9 +- .../merge_request/push_options_labels_spec.rb | 7 +- spec/commands/sidekiq_cluster/cli_spec.rb | 3 +- .../projects/group_links_controller_spec.rb | 2 +- spec/features/admin/admin_settings_spec.rb | 6 +- .../badges/components/badge_form_spec.js | 16 ++-- .../badges/components/badge_list_row_spec.js | 16 ++-- .../badges/components/badge_list_spec.js | 38 +++----- spec/frontend/badges/components/badge_spec.js | 75 ++++++--------- .../components/preview_dropdown_spec.js | 6 +- spec/frontend/confirm_modal_spec.js | 7 +- .../eks_cluster_configuration_form_spec.js | 93 ++++++++----------- .../groups/components/group_folder_spec.js | 6 +- .../components/commit_sidebar/list_spec.js | 7 +- .../commit_sidebar/radio_group_spec.js | 45 ++++----- .../commit_sidebar/success_message_spec.js | 11 +-- spec/frontend/ide/stores/actions/file_spec.js | 6 +- .../components/issue_milestone_spec.js | 24 ++--- .../jira_issues_import_status_app_spec.js | 9 +- .../issues/show/components/title_spec.js | 46 +++++---- .../components/jira_import_app_spec.js | 6 +- .../notes/components/note_actions_spec.js | 32 +++---- .../notes/components/note_body_spec.js | 6 +- .../notes/components/notes_app_spec.js | 20 ++-- .../components/delete_account_modal_spec.js | 74 ++++++--------- .../reports/components/report_section_spec.js | 90 ++++++------------ spec/frontend/sidebar/participants_spec.js | 40 ++++---- .../user_lists/components/user_lists_spec.js | 8 +- spec/frontend/vue_alerts_spec.js | 7 +- .../components/mr_widget_memory_usage_spec.js | 10 +- .../states/mr_widget_merged_spec.js | 8 +- .../states/mr_widget_nothing_to_merge_spec.js | 4 +- .../states/mr_widget_ready_to_merge_spec.js | 6 +- .../components/states/mr_widget_wip_spec.js | 4 +- .../components/expand_button_spec.js | 39 ++++---- .../components/gl_countdown_spec.js | 20 ++-- .../components/markdown/suggestions_spec.js | 6 +- .../resizable_chart_container_spec.js | 15 ++- spec/models/member_spec.rb | 11 +++ spec/models/members/project_member_spec.rb | 11 +++ spec/models/state_note_spec.rb | 4 +- spec/services/issues/close_service_spec.rb | 10 +- .../import_export/export_service_spec.rb | 15 ++- spec/services/system_note_service_spec.rb | 12 --- .../alert_management_service_spec.rb | 16 +--- .../system_notes/incident_service_spec.rb | 10 -- spec/spec_helper.rb | 8 -- spec/support/stub_settings_source.rb | 11 +++ 70 files changed, 490 insertions(+), 611 deletions(-) create mode 100644 data/deprecations/14-8-iteration-started-field.yml create mode 100644 spec/support/stub_settings_source.rb diff --git a/.rubocop_todo/graphql/ordered_arguments.yml b/.rubocop_todo/graphql/ordered_arguments.yml index 3a4e805948d..def1b0085e4 100644 --- a/.rubocop_todo/graphql/ordered_arguments.yml +++ b/.rubocop_todo/graphql/ordered_arguments.yml @@ -4,10 +4,6 @@ GraphQL/OrderedArguments: - app/graphql/resolvers/base_issues_resolver.rb - app/graphql/resolvers/design_management/designs_resolver.rb - app/graphql/resolvers/design_management/version/design_at_version_resolver.rb - - app/graphql/resolvers/merge_requests_resolver.rb - - app/graphql/resolvers/paginated_tree_resolver.rb - - app/graphql/resolvers/tree_resolver.rb - - app/graphql/resolvers/users/groups_resolver.rb - app/graphql/types/commit_action_type.rb - app/graphql/types/diff_paths_input_type.rb - app/graphql/types/issues/negated_issue_filter_input_type.rb diff --git a/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js b/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js index 08f6633f424..c017cf0afa2 100644 --- a/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js +++ b/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js @@ -5,7 +5,6 @@ import { __ } from '../../../locale'; export default class PayloadPreviewer { constructor(trigger) { this.trigger = trigger; - this.container = document.querySelector(trigger.dataset.payloadSelector); this.isVisible = false; this.isInserted = false; } @@ -23,21 +22,27 @@ export default class PayloadPreviewer { }); } + getContainer() { + return document.querySelector(this.trigger.dataset.payloadSelector); + } + requestPayload() { if (this.isInserted) return this.showPayload(); - this.spinner.classList.add('d-inline-flex'); + this.spinner.classList.add('gl-display-inline-flex'); + + const container = this.getContainer(); return axios - .get(this.container.dataset.endpoint, { + .get(container.dataset.endpoint, { responseType: 'text', }) .then(({ data }) => { - this.spinner.classList.remove('d-inline-flex'); + this.spinner.classList.remove('gl-display-inline-flex'); this.insertPayload(data); }) .catch(() => { - this.spinner.classList.remove('d-inline-flex'); + this.spinner.classList.remove('gl-display-inline-flex'); createFlash({ message: __('Error fetching payload data.'), }); @@ -46,19 +51,19 @@ export default class PayloadPreviewer { hidePayload() { this.isVisible = false; - this.container.classList.add('d-none'); + this.getContainer().classList.add('gl-display-none'); this.text.textContent = __('Preview payload'); } showPayload() { this.isVisible = true; - this.container.classList.remove('d-none'); + this.getContainer().classList.remove('gl-display-none'); this.text.textContent = __('Hide payload'); } insertPayload(data) { this.isInserted = true; - this.container.innerHTML = data; + this.getContainer().innerHTML = data; this.showPayload(); } } diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 27893fe510d..6bc81381d92 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -28,7 +28,7 @@ class Projects::GroupLinksController < Projects::ApplicationController if group_link.expires? render json: { - expires_in: helpers.distance_of_time_in_words_to_now(group_link.expires_at), + expires_in: helpers.time_ago_with_tooltip(group_link.expires_at), expires_soon: group_link.expires_soon? } else diff --git a/app/graphql/resolvers/merge_requests_resolver.rb b/app/graphql/resolvers/merge_requests_resolver.rb index 6dbcbe0e04d..ff722122933 100644 --- a/app/graphql/resolvers/merge_requests_resolver.rb +++ b/app/graphql/resolvers/merge_requests_resolver.rb @@ -55,6 +55,13 @@ module Resolvers required: false, description: 'Limit result to draft merge requests.' + argument :created_after, Types::TimeType, + required: false, + description: 'Merge requests created after this timestamp.' + argument :created_before, Types::TimeType, + required: false, + description: 'Merge requests created before this timestamp.' + argument :labels, [GraphQL::Types::String], required: false, as: :label_name, @@ -72,12 +79,6 @@ module Resolvers description: 'Sort merge requests by this criteria.', required: false, default_value: :created_desc - argument :created_after, Types::TimeType, - required: false, - description: 'Merge requests created after this timestamp.' - argument :created_before, Types::TimeType, - required: false, - description: 'Merge requests created before this timestamp.' negated do argument :labels, [GraphQL::Types::String], diff --git a/app/graphql/resolvers/paginated_tree_resolver.rb b/app/graphql/resolvers/paginated_tree_resolver.rb index 6c0545d26de..d29d87ca204 100644 --- a/app/graphql/resolvers/paginated_tree_resolver.rb +++ b/app/graphql/resolvers/paginated_tree_resolver.rb @@ -11,14 +11,14 @@ module Resolvers required: false, default_value: '', # root of the repository description: 'Path to get the tree for. Default value is the root of the repository.' - argument :ref, GraphQL::Types::String, - required: false, - default_value: :head, - description: 'Commit ref to get the tree for. Default value is HEAD.' argument :recursive, GraphQL::Types::Boolean, required: false, default_value: false, description: 'Used to get a recursive tree. Default is false.' + argument :ref, GraphQL::Types::String, + required: false, + default_value: :head, + description: 'Commit ref to get the tree for. Default value is HEAD.' alias_method :repository, :object diff --git a/app/graphql/resolvers/tree_resolver.rb b/app/graphql/resolvers/tree_resolver.rb index 8d6ece0956e..f02eb226810 100644 --- a/app/graphql/resolvers/tree_resolver.rb +++ b/app/graphql/resolvers/tree_resolver.rb @@ -10,14 +10,14 @@ module Resolvers required: false, default_value: '', description: 'Path to get the tree for. Default value is the root of the repository.' - argument :ref, GraphQL::Types::String, - required: false, - default_value: :head, - description: 'Commit ref to get the tree for. Default value is HEAD.' argument :recursive, GraphQL::Types::Boolean, required: false, default_value: false, description: 'Used to get a recursive tree. Default is false.' + argument :ref, GraphQL::Types::String, + required: false, + default_value: :head, + description: 'Commit ref to get the tree for. Default value is HEAD.' alias_method :repository, :object diff --git a/app/graphql/resolvers/users/groups_resolver.rb b/app/graphql/resolvers/users/groups_resolver.rb index d8492a8fcf9..09c6b51cc3d 100644 --- a/app/graphql/resolvers/users/groups_resolver.rb +++ b/app/graphql/resolvers/users/groups_resolver.rb @@ -11,13 +11,13 @@ module Resolvers authorize :read_user_groups authorizes_object! - argument :search, GraphQL::Types::String, - required: false, - description: 'Search by group name or path.' argument :permission_scope, ::Types::PermissionTypes::GroupEnum, required: false, description: 'Filter by permissions the user has on groups.' + argument :search, GraphQL::Types::String, + required: false, + description: 'Search by group name or path.' before_connection_authorization do |nodes, current_user| Preloaders::GroupPolicyPreloader.new(nodes, current_user).execute diff --git a/app/models/member.rb b/app/models/member.rb index 6c0503dca3f..1c1b603b4c7 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -180,6 +180,7 @@ class Member < ApplicationRecord scope :on_project_and_ancestors, ->(project) { where(source: [project] + project.ancestors) } + before_validation :set_member_namespace_id, on: :create before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? && !member.invite_accepted_at? } after_create :send_invite, if: :invite?, unless: :importing? @@ -380,6 +381,12 @@ class Member < ApplicationRecord private + # TODO: https://gitlab.com/groups/gitlab-org/-/epics/7054 + # temporary until we can we properly remove the source columns + def set_member_namespace_id + self.member_namespace_id = self.source_id + end + def access_level_inclusion return if access_level.in?(Gitlab::Access.all_values) diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 6fc665cb87a..3a449055bc1 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -118,6 +118,13 @@ class ProjectMember < Member # rubocop:enable CodeReuse/ServiceClass end + # TODO: https://gitlab.com/groups/gitlab-org/-/epics/7054 + # temporary until we can we properly remove the source columns + override :set_member_namespace_id + def set_member_namespace_id + self.member_namespace_id = project&.project_namespace_id + end + def send_invite run_after_commit_or_now { notification_service.invite_project_member(self, @raw_invite_token) } diff --git a/app/models/state_note.rb b/app/models/state_note.rb index 5e35f15aac4..93c025a9bf0 100644 --- a/app/models/state_note.rb +++ b/app/models/state_note.rb @@ -18,11 +18,11 @@ class StateNote < SyntheticNote def note_text(html: false) if event.state == 'closed' if event.close_after_error_tracking_resolve - return 'resolved the corresponding error and closed the issue.' + return 'resolved the corresponding error and closed the issue' end if event.close_auto_resolve_prometheus_alert - return 'automatically closed this issue because the alert resolved.' + return 'automatically closed this incident because the alert resolved' end end diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb index 65f143d0b21..ff45091c7e6 100644 --- a/app/services/issues/close_service.rb +++ b/app/services/issues/close_service.rb @@ -81,7 +81,7 @@ module Issues return if alert.resolved? if alert.resolve - SystemNotes::AlertManagementService.new(noteable: alert, project: alert.project, author: current_user).closed_alert_issue(issue) + SystemNoteService.change_alert_status(alert, current_user, " by closing incident #{issue.to_reference(project)}") else Gitlab::AppLogger.warn( message: 'Cannot resolve an associated Alert Management alert', @@ -97,7 +97,7 @@ module Issues status = issue.incident_management_issuable_escalation_status || issue.build_incident_management_issuable_escalation_status - SystemNoteService.resolve_incident_status(issue, current_user) if status.resolve + SystemNoteService.change_incident_status(issue, current_user, ' by closing the incident') if status.resolve end def store_first_mentioned_in_commit_at(issue, merge_request, max_commit_lookup: 100) diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb index b1a2182fbdc..b91b7f34d42 100644 --- a/app/services/projects/import_export/export_service.rb +++ b/app/services/projects/import_export/export_service.rb @@ -47,8 +47,7 @@ module Projects end def save_all! - if save_exporters - Gitlab::ImportExport::Saver.save(exportable: project, shared: shared) + if save_exporters && save_export_archive notify_success else notify_error! @@ -59,6 +58,10 @@ module Projects exporters.all?(&:save) end + def save_export_archive + Gitlab::ImportExport::Saver.save(exportable: project, shared: shared) + end + def exporters [ version_saver, avatar_saver, project_tree_saver, uploads_saver, diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 1c85955ce90..7a7aea3fa4c 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -335,10 +335,6 @@ module SystemNoteService ::SystemNotes::IncidentService.new(noteable: incident, project: incident.project, author: author).change_incident_severity end - def resolve_incident_status(incident, author) - ::SystemNotes::IncidentService.new(noteable: incident, project: incident.project, author: author).resolve_incident_status - end - def change_incident_status(incident, author, reason = nil) ::SystemNotes::IncidentService.new(noteable: incident, project: incident.project, author: author).change_incident_status(reason) end diff --git a/app/services/system_notes/alert_management_service.rb b/app/services/system_notes/alert_management_service.rb index 5e20b4be9a5..994e3174668 100644 --- a/app/services/system_notes/alert_management_service.rb +++ b/app/services/system_notes/alert_management_service.rb @@ -40,30 +40,15 @@ module SystemNotes # # Example Note text: # - # "created issue #17 for this alert" + # "created incident #17 for this alert" # # Returns the created Note object def new_alert_issue(issue) - body = "created issue #{issue.to_reference(project)} for this alert" + body = "created incident #{issue.to_reference(project)} for this alert" create_note(NoteSummary.new(noteable, project, author, body, action: 'alert_issue_added')) end - # Called when an AlertManagement::Alert is resolved due to the associated issue being closed - # - # issue - Issue object. - # - # Example Note text: - # - # "changed the status to Resolved by closing issue #17" - # - # Returns the created Note object - def closed_alert_issue(issue) - body = "changed the status to **Resolved** by closing issue #{issue.to_reference(project)}" - - create_note(NoteSummary.new(noteable, project, author, body, action: 'status')) - end - # Called when an alert is resolved due to received resolving alert payload # # alert - AlertManagement::Alert object. diff --git a/app/services/system_notes/incident_service.rb b/app/services/system_notes/incident_service.rb index 6d1c10964b3..f3f9dfbec96 100644 --- a/app/services/system_notes/incident_service.rb +++ b/app/services/system_notes/incident_service.rb @@ -26,12 +26,6 @@ module SystemNotes end end - def resolve_incident_status - body = 'changed the status to **Resolved** by closing the incident' - - create_note(NoteSummary.new(noteable, project, author, body, action: 'status')) - end - # Called when the status of an IncidentManagement::IssuableEscalationStatus has changed # # reason - String. diff --git a/app/views/admin/application_settings/_usage.html.haml b/app/views/admin/application_settings/_usage.html.haml index 4fba1aee12d..326aae26d5e 100644 --- a/app/views/admin/application_settings/_usage.html.haml +++ b/app/views/admin/application_settings/_usage.html.haml @@ -28,8 +28,8 @@ %button.gl-button.btn.btn-default.js-payload-preview-trigger{ type: 'button', data: { payload_selector: ".#{payload_class}" } } .gl-spinner.js-spinner.gl-display-none.gl-mr-2 - .js-text.d-inline= _('Preview payload') - %pre.service-data-payload-container.js-syntax-highlight.code.highlight.mt-2.d-none{ class: payload_class, data: { endpoint: usage_data_admin_application_settings_path(format: :html) } } + .js-text.gl-display-inline= _('Preview payload') + %pre.service-data-payload-container.js-syntax-highlight.code.highlight.gl-mt-2.gl-display-none{ class: payload_class, data: { endpoint: usage_data_admin_application_settings_path(format: :html) } } - else = _('Service ping is disabled in your configuration file, and cannot be enabled through this form.') - deactivating_service_ping_path = help_page_path('development/service_ping/index.md', anchor: 'disable-service-ping-using-the-configuration-file') diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 3e875a0eb24..d6557772241 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -4,7 +4,6 @@ .content-wrapper.content-wrapper-margin{ class: "#{@content_wrapper_class}" } .mobile-overlay = render_if_exists 'layouts/header/verification_reminder' - = yield :group_invite_members_banner .alert-wrapper.gl-force-block-formatting-context = render 'shared/outdated_browser' = render_if_exists "layouts/header/licensed_user_count_threshold" @@ -21,6 +20,7 @@ = render_if_exists "shared/namespace_user_cap_reached_alert" = render_if_exists "shared/new_user_signups_cap_reached_alert" = yield :page_level_alert + = yield :group_invite_members_banner - unless @hide_breadcrumbs = render "layouts/nav/breadcrumbs" %div{ class: "#{(container_class unless @no_container)} #{@content_class}" } diff --git a/data/deprecations/14-8-iteration-started-field.yml b/data/deprecations/14-8-iteration-started-field.yml new file mode 100644 index 00000000000..590c9800db6 --- /dev/null +++ b/data/deprecations/14-8-iteration-started-field.yml @@ -0,0 +1,15 @@ +- name: "`started` iterations API field" # The name of the feature to be deprecated + announcement_milestone: "14.8" # The milestone when this feature was first announced as deprecated. + announcement_date: "2022-02-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post. + removal_milestone: "15.0" # The milestone when this feature is planned to be removed + removal_date: "2022-05-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post. + breaking_change: true # If this deprecation is a breaking change, set this value to true + body: | # Do not modify this line, instead modify the lines below. + The `started` field in the [iterations API](https://docs.gitlab.com/ee/api/iterations.html#list-project-iterations) is being deprecated and will be removed in GitLab 15.0. This field is being replaced with the `current` field (already available) which aligns with the naming for other time-based entities, such as milestones. +# The following items are not published on the docs page, but may be used in the future. + stage: plan + tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate] + issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334018 + documentation_url: # (optional) This is a link to the current documentation page + image_url: # (optional) This is a link to a thumbnail image depicting the feature + video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg diff --git a/doc/development/service_ping/metrics_lifecycle.md b/doc/development/service_ping/metrics_lifecycle.md index ebfab6341e9..d438beecbc9 100644 --- a/doc/development/service_ping/metrics_lifecycle.md +++ b/doc/development/service_ping/metrics_lifecycle.md @@ -85,10 +85,9 @@ To remove a metric: 1. Verify that removing the metric from the Service Ping payload does not cause errors in [Version App](https://gitlab.com/gitlab-services/version-gitlab-com) when the updated payload is collected and processed. Version App collects - and persists all Service Ping reports. To do that you can modify - [fixtures](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/master/spec/support/usage_data_helpers.rb#L540) - used to test - [`UsageDataController#create`](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/3760ef28/spec/controllers/usage_data_controller_spec.rb#L75) + and persists all Service Ping reports. To verify Service Ping processing in your local development environment, follow this [guide](https://www.youtube.com/watch?v=FS5emplabRU). + Alternatively, you can modify [fixtures](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/master/spec/support/usage_data_helpers.rb#L540) + used to test the [`UsageDataController#create`](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/3760ef28/spec/controllers/usage_data_controller_spec.rb#L75) endpoint, and assure that test suite does not fail when metric that you wish to remove is not included into test payload. 1. Create an issue in the diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md index 0591b7e9afa..6a2504f64d2 100644 --- a/doc/update/deprecations.md +++ b/doc/update/deprecations.md @@ -716,3 +716,15 @@ Support for `fixup!` is now considered deprecated, and will be removed in GitLab 15.0. **Planned removal milestone: 15.0 (2022-06-22)** + +### `started` iterations API field + +WARNING: +This feature will be changed or removed in 15.0 +as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes). +Before updating GitLab, review the details carefully to determine if you need to make any +changes to your code, settings, or workflow. + +The `started` field in the [iterations API](https://docs.gitlab.com/ee/api/iterations.html#list-project-iterations) is being deprecated and will be removed in GitLab 15.0. This field is being replaced with the `current` field (already available) which aligns with the naming for other time-based entities, such as milestones. + +**Planned removal milestone: 15.0 (2022-05-22)** diff --git a/lib/gitlab/import_export/saver.rb b/lib/gitlab/import_export/saver.rb index bec709f4a36..a20e545f6aa 100644 --- a/lib/gitlab/import_export/saver.rb +++ b/lib/gitlab/import_export/saver.rb @@ -54,6 +54,8 @@ module Gitlab File.open(archive_file) { |file| upload.export_file = file } upload.save! + + true end def error_message diff --git a/qa/qa/resource/project_access_token.rb b/qa/qa/resource/project_access_token.rb index cf6b4b51f88..f5cd8798f19 100644 --- a/qa/qa/resource/project_access_token.rb +++ b/qa/qa/resource/project_access_token.rb @@ -9,9 +9,7 @@ module QA attribute :id attribute :project do - Project.fabricate! do |project| - project.initialize_with_readme = true - end + Project.fabricate! end attribute :token do Page::Project::Settings::AccessTokens.perform(&:created_access_token) diff --git a/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb b/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb index b95e388b449..6480b880400 100644 --- a/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb +++ b/qa/qa/specs/features/api/1_manage/project_access_token_spec.rb @@ -4,7 +4,9 @@ module QA RSpec.describe 'Manage' do describe 'Project access token' do before(:all) do - @project_access_token = QA::Resource::ProjectAccessToken.fabricate_via_api! + @project_access_token = QA::Resource::ProjectAccessToken.fabricate_via_api! do |pat| + pat.project = Resource::ReusableProject.fabricate_via_api! + end @user_api_client = Runtime::API::Client.new(:gitlab, personal_access_token: @project_access_token.token) end @@ -76,11 +78,6 @@ module QA @different_project.remove_via_api! end end - - after(:all) do - @project_access_token.remove_via_api! - @project_access_token.project.remove_via_api! - end end end end diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb index 50b084748e8..ab9af872753 100644 --- a/qa/qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb +++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb @@ -11,12 +11,7 @@ module QA let(:title) { "MR push options test #{SecureRandom.hex(8)}" } let(:commit_message) { 'Add README.md' } - let(:project) do - Resource::Project.fabricate_via_api! do |project| - project.name = 'merge-request-push-options' - project.initialize_with_readme = true - end - end + let(:project) { Resource::ReusableProject.fabricate_via_api! } def create_new_mr_via_push Resource::Repository::ProjectPush.fabricate! do |push| diff --git a/spec/commands/sidekiq_cluster/cli_spec.rb b/spec/commands/sidekiq_cluster/cli_spec.rb index d7488e8d965..4d4e1f35cff 100644 --- a/spec/commands/sidekiq_cluster/cli_spec.rb +++ b/spec/commands/sidekiq_cluster/cli_spec.rb @@ -3,9 +3,10 @@ require 'fast_spec_helper' require 'rspec-parameterized' +require_relative '../../support/stub_settings_source' require_relative '../../../sidekiq_cluster/cli' -RSpec.describe Gitlab::SidekiqCluster::CLI, stubbing_settings_source: true do # rubocop:disable RSpec/FilePath +RSpec.describe Gitlab::SidekiqCluster::CLI, stub_settings_source: true do # rubocop:disable RSpec/FilePath let(:cli) { described_class.new('/dev/null') } let(:timeout) { Gitlab::SidekiqCluster::DEFAULT_SOFT_TIMEOUT_SECONDS } let(:default_options) do diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb index d514c486f60..ea15d483c90 100644 --- a/spec/controllers/projects/group_links_controller_spec.rb +++ b/spec/controllers/projects/group_links_controller_spec.rb @@ -178,7 +178,7 @@ RSpec.describe Projects::GroupLinksController do context 'when `expires_at` is set' do it 'returns correct json response' do - expect(json_response).to eq({ "expires_in" => "about 1 month", "expires_soon" => false }) + expect(json_response).to eq({ "expires_in" => controller.helpers.time_ago_with_tooltip(expiry_date), "expires_soon" => false }) end end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index e136ab41966..e0172e63d48 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -528,7 +528,7 @@ RSpec.describe 'Admin updates settings' do expect(find_field('Allow access to members of the following group').value).to be_nil end - it 'loads usage ping payload on click', :js do + it 'loads togglable usage ping payload on click', :js do stub_usage_data_connections stub_database_flavor_check @@ -544,6 +544,10 @@ RSpec.describe 'Admin updates settings' do expect(page).to have_selector '.js-service-ping-payload' expect(page).to have_button 'Hide payload' expect(page).to have_content expected_payload_content + + click_button('Hide payload') + + expect(page).not_to have_content expected_payload_content end end end diff --git a/spec/frontend/badges/components/badge_form_spec.js b/spec/frontend/badges/components/badge_form_spec.js index e375fcb4705..ba2ec775b61 100644 --- a/spec/frontend/badges/components/badge_form_spec.js +++ b/spec/frontend/badges/components/badge_form_spec.js @@ -1,5 +1,5 @@ import MockAdapter from 'axios-mock-adapter'; -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import { DUMMY_IMAGE_URL, TEST_HOST } from 'helpers/test_constants'; import { mountComponentWithStore } from 'helpers/vue_mount_component_helper'; import BadgeForm from '~/badges/components/badge_form.vue'; @@ -74,7 +74,7 @@ describe('BadgeForm component', () => { expect(feedbackElement).toBeVisible(); }; - beforeEach((done) => { + beforeEach(async () => { jest.spyOn(vm, submitAction).mockReturnValue(Promise.resolve()); store.replaceState({ ...store.state, @@ -83,14 +83,10 @@ describe('BadgeForm component', () => { isSaving: false, }); - Vue.nextTick() - .then(() => { - setValue(nameSelector, 'TestBadge'); - setValue(linkUrlSelector, `${TEST_HOST}/link/url`); - setValue(imageUrlSelector, `${window.location.origin}${DUMMY_IMAGE_URL}`); - }) - .then(done) - .catch(done.fail); + await nextTick(); + setValue(nameSelector, 'TestBadge'); + setValue(linkUrlSelector, `${TEST_HOST}/link/url`); + setValue(imageUrlSelector, `${window.location.origin}${DUMMY_IMAGE_URL}`); }); it('returns immediately if imageUrl is empty', () => { diff --git a/spec/frontend/badges/components/badge_list_row_spec.js b/spec/frontend/badges/components/badge_list_row_spec.js index 372663017e2..0fb0fa86a02 100644 --- a/spec/frontend/badges/components/badge_list_row_spec.js +++ b/spec/frontend/badges/components/badge_list_row_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import { mountComponentWithStore } from 'helpers/vue_mount_component_helper'; import BadgeListRow from '~/badges/components/badge_list_row.vue'; import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants'; @@ -73,25 +73,21 @@ describe('BadgeListRow component', () => { expect(vm.editBadge).toHaveBeenCalled(); }); - it('calls updateBadgeInModal and shows modal when clicking then delete button', (done) => { + it('calls updateBadgeInModal and shows modal when clicking then delete button', async () => { jest.spyOn(vm, 'updateBadgeInModal').mockImplementation(() => {}); const deleteButton = vm.$el.querySelector('.table-button-footer button:last-of-type'); deleteButton.click(); - Vue.nextTick() - .then(() => { - expect(vm.updateBadgeInModal).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); + await nextTick(); + expect(vm.updateBadgeInModal).toHaveBeenCalled(); }); describe('for a group badge', () => { - beforeEach((done) => { + beforeEach(async () => { badge.kind = GROUP_BADGE; - Vue.nextTick().then(done).catch(done.fail); + await nextTick(); }); it('renders the badge kind', () => { diff --git a/spec/frontend/badges/components/badge_list_spec.js b/spec/frontend/badges/components/badge_list_spec.js index 6cc90c6de46..39fa502b207 100644 --- a/spec/frontend/badges/components/badge_list_spec.js +++ b/spec/frontend/badges/components/badge_list_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import { mountComponentWithStore } from 'helpers/vue_mount_component_helper'; import BadgeList from '~/badges/components/badge_list.vue'; import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants'; @@ -48,46 +48,34 @@ describe('BadgeList component', () => { expect(rows).toHaveLength(numberOfDummyBadges); }); - it('renders a message if no badges exist', (done) => { + it('renders a message if no badges exist', async () => { store.state.badges = []; - Vue.nextTick() - .then(() => { - expect(vm.$el.innerText).toMatch('This project has no badges'); - }) - .then(done) - .catch(done.fail); + await nextTick(); + expect(vm.$el.innerText).toMatch('This project has no badges'); }); - it('shows a loading icon when loading', (done) => { + it('shows a loading icon when loading', async () => { store.state.isLoading = true; - Vue.nextTick() - .then(() => { - const loadingIcon = vm.$el.querySelector('.gl-spinner'); + await nextTick(); + const loadingIcon = vm.$el.querySelector('.gl-spinner'); - expect(loadingIcon).toBeVisible(); - }) - .then(done) - .catch(done.fail); + expect(loadingIcon).toBeVisible(); }); describe('for group badges', () => { - beforeEach((done) => { + beforeEach(async () => { store.state.kind = GROUP_BADGE; - Vue.nextTick().then(done).catch(done.fail); + await nextTick(); }); - it('renders a message if no badges exist', (done) => { + it('renders a message if no badges exist', async () => { store.state.badges = []; - Vue.nextTick() - .then(() => { - expect(vm.$el.innerText).toMatch('This group has no badges'); - }) - .then(done) - .catch(done.fail); + await nextTick(); + expect(vm.$el.innerText).toMatch('This group has no badges'); }); }); }); diff --git a/spec/frontend/badges/components/badge_spec.js b/spec/frontend/badges/components/badge_spec.js index 990bc094d59..2310fb8bd8e 100644 --- a/spec/frontend/badges/components/badge_spec.js +++ b/spec/frontend/badges/components/badge_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import mountComponent from 'helpers/vue_mount_component_helper'; import { DUMMY_IMAGE_URL, TEST_HOST } from 'spec/test_constants'; import Badge from '~/badges/components/badge.vue'; @@ -27,7 +27,7 @@ describe('Badge component', () => { badgeImage.addEventListener('load', resolve); // Manually dispatch load event as it is not triggered badgeImage.dispatchEvent(new Event('load')); - }).then(() => Vue.nextTick()); + }).then(() => nextTick()); }; afterEach(() => { @@ -36,34 +36,25 @@ describe('Badge component', () => { describe('watchers', () => { describe('imageUrl', () => { - it('sets isLoading and resets numRetries and hasError', (done) => { + it('sets isLoading and resets numRetries and hasError', async () => { const props = { ...dummyProps }; - createComponent(props) - .then(() => { - expect(vm.isLoading).toBe(false); - vm.hasError = true; - vm.numRetries = 42; + await createComponent(props); + expect(vm.isLoading).toBe(false); + vm.hasError = true; + vm.numRetries = 42; - vm.imageUrl = `${props.imageUrl}#something/else`; - - return Vue.nextTick(); - }) - .then(() => { - expect(vm.isLoading).toBe(true); - expect(vm.numRetries).toBe(0); - expect(vm.hasError).toBe(false); - }) - .then(done) - .catch(done.fail); + vm.imageUrl = `${props.imageUrl}#something/else`; + await nextTick(); + expect(vm.isLoading).toBe(true); + expect(vm.numRetries).toBe(0); + expect(vm.hasError).toBe(false); }); }); }); describe('methods', () => { - beforeEach((done) => { - createComponent({ ...dummyProps }) - .then(done) - .catch(done.fail); + beforeEach(async () => { + await createComponent({ ...dummyProps }); }); it('onError resets isLoading and sets hasError', () => { @@ -116,37 +107,29 @@ describe('Badge component', () => { expect(vm.$el.querySelector('.btn-group')).toBeHidden(); }); - it('shows a loading icon when loading', (done) => { + it('shows a loading icon when loading', async () => { vm.isLoading = true; - Vue.nextTick() - .then(() => { - const { badgeImage, loadingIcon, reloadButton } = findElements(); + await nextTick(); + const { badgeImage, loadingIcon, reloadButton } = findElements(); - expect(badgeImage).toBeHidden(); - expect(loadingIcon).toBeVisible(); - expect(reloadButton).toBeHidden(); - expect(vm.$el.querySelector('.btn-group')).toBeHidden(); - }) - .then(done) - .catch(done.fail); + expect(badgeImage).toBeHidden(); + expect(loadingIcon).toBeVisible(); + expect(reloadButton).toBeHidden(); + expect(vm.$el.querySelector('.btn-group')).toBeHidden(); }); - it('shows an error and reload button if loading failed', (done) => { + it('shows an error and reload button if loading failed', async () => { vm.hasError = true; - Vue.nextTick() - .then(() => { - const { badgeImage, loadingIcon, reloadButton } = findElements(); + await nextTick(); + const { badgeImage, loadingIcon, reloadButton } = findElements(); - expect(badgeImage).toBeHidden(); - expect(loadingIcon).toBeHidden(); - expect(reloadButton).toBeVisible(); - expect(reloadButton).toHaveSpriteIcon('retry'); - expect(vm.$el.innerText.trim()).toBe('No badge image'); - }) - .then(done) - .catch(done.fail); + expect(badgeImage).toBeHidden(); + expect(loadingIcon).toBeHidden(); + expect(reloadButton).toBeVisible(); + expect(reloadButton).toHaveSpriteIcon('retry'); + expect(vm.$el.innerText.trim()).toBe('No badge image'); }); }); }); diff --git a/spec/frontend/batch_comments/components/preview_dropdown_spec.js b/spec/frontend/batch_comments/components/preview_dropdown_spec.js index 5327879f003..bf3bbf4de26 100644 --- a/spec/frontend/batch_comments/components/preview_dropdown_spec.js +++ b/spec/frontend/batch_comments/components/preview_dropdown_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import PreviewDropdown from '~/batch_comments/components/preview_dropdown.vue'; @@ -49,7 +49,7 @@ describe('Batch comments preview dropdown', () => { wrapper.findByTestId('preview-item').vm.$emit('click'); - await Vue.nextTick(); + await nextTick(); expect(setCurrentFileHash).toHaveBeenCalledWith(expect.anything(), 'hash'); expect(scrollToDraft).toHaveBeenCalledWith(expect.anything(), { id: 1, file_hash: 'hash' }); @@ -63,7 +63,7 @@ describe('Batch comments preview dropdown', () => { wrapper.findByTestId('preview-item').vm.$emit('click'); - await Vue.nextTick(); + await nextTick(); expect(scrollToDraft).toHaveBeenCalledWith(expect.anything(), { id: 1 }); }); diff --git a/spec/frontend/confirm_modal_spec.js b/spec/frontend/confirm_modal_spec.js index 5e5345cbd2b..e5e85910945 100644 --- a/spec/frontend/confirm_modal_spec.js +++ b/spec/frontend/confirm_modal_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import { nextTick } from 'vue'; import { TEST_HOST } from 'helpers/test_constants'; import initConfirmModal from '~/confirm_modal'; @@ -92,10 +92,9 @@ describe('ConfirmModal', () => { }); describe('Cancel Button', () => { - beforeEach(() => { + beforeEach(async () => { findModalCancelButton(findModal()).click(); - - return Vue.nextTick(); + await nextTick(); }); it('closes the modal', () => { diff --git a/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js index 8b8791a6b2b..1509d26c99d 100644 --- a/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js +++ b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js @@ -1,6 +1,6 @@ import { GlFormCheckbox } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import EksClusterConfigurationForm from '~/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue'; @@ -223,108 +223,98 @@ describe('EksClusterConfigurationForm', () => { }); }); - it('sets isLoadingRoles to RoleDropdown loading property', () => { + it('sets isLoadingRoles to RoleDropdown loading property', async () => { rolesState.isLoadingItems = true; - return Vue.nextTick().then(() => { - expect(findRoleDropdown().props('loading')).toBe(rolesState.isLoadingItems); - }); + await nextTick(); + expect(findRoleDropdown().props('loading')).toBe(rolesState.isLoadingItems); }); it('sets roles to RoleDropdown items property', () => { expect(findRoleDropdown().props('items')).toBe(rolesState.items); }); - it('sets RoleDropdown hasErrors to true when loading roles failed', () => { + it('sets RoleDropdown hasErrors to true when loading roles failed', async () => { rolesState.loadingItemsError = new Error(); - return Vue.nextTick().then(() => { - expect(findRoleDropdown().props('hasErrors')).toEqual(true); - }); + await nextTick(); + expect(findRoleDropdown().props('hasErrors')).toEqual(true); }); it('disables KeyPairDropdown when no region is selected', () => { expect(findKeyPairDropdown().props('disabled')).toBe(true); }); - it('enables KeyPairDropdown when no region is selected', () => { + it('enables KeyPairDropdown when no region is selected', async () => { state.selectedRegion = { name: 'west-1 ' }; - return Vue.nextTick().then(() => { - expect(findKeyPairDropdown().props('disabled')).toBe(false); - }); + await nextTick(); + expect(findKeyPairDropdown().props('disabled')).toBe(false); }); - it('sets isLoadingKeyPairs to KeyPairDropdown loading property', () => { + it('sets isLoadingKeyPairs to KeyPairDropdown loading property', async () => { keyPairsState.isLoadingItems = true; - return Vue.nextTick().then(() => { - expect(findKeyPairDropdown().props('loading')).toBe(keyPairsState.isLoadingItems); - }); + await nextTick(); + expect(findKeyPairDropdown().props('loading')).toBe(keyPairsState.isLoadingItems); }); it('sets keyPairs to KeyPairDropdown items property', () => { expect(findKeyPairDropdown().props('items')).toBe(keyPairsState.items); }); - it('sets KeyPairDropdown hasErrors to true when loading key pairs fails', () => { + it('sets KeyPairDropdown hasErrors to true when loading key pairs fails', async () => { keyPairsState.loadingItemsError = new Error(); - return Vue.nextTick().then(() => { - expect(findKeyPairDropdown().props('hasErrors')).toEqual(true); - }); + await nextTick(); + expect(findKeyPairDropdown().props('hasErrors')).toEqual(true); }); it('disables VpcDropdown when no region is selected', () => { expect(findVpcDropdown().props('disabled')).toBe(true); }); - it('enables VpcDropdown when no region is selected', () => { + it('enables VpcDropdown when no region is selected', async () => { state.selectedRegion = { name: 'west-1 ' }; - return Vue.nextTick().then(() => { - expect(findVpcDropdown().props('disabled')).toBe(false); - }); + await nextTick(); + expect(findVpcDropdown().props('disabled')).toBe(false); }); - it('sets isLoadingVpcs to VpcDropdown loading property', () => { + it('sets isLoadingVpcs to VpcDropdown loading property', async () => { vpcsState.isLoadingItems = true; - return Vue.nextTick().then(() => { - expect(findVpcDropdown().props('loading')).toBe(vpcsState.isLoadingItems); - }); + await nextTick(); + expect(findVpcDropdown().props('loading')).toBe(vpcsState.isLoadingItems); }); it('sets vpcs to VpcDropdown items property', () => { expect(findVpcDropdown().props('items')).toBe(vpcsState.items); }); - it('sets VpcDropdown hasErrors to true when loading vpcs fails', () => { + it('sets VpcDropdown hasErrors to true when loading vpcs fails', async () => { vpcsState.loadingItemsError = new Error(); - return Vue.nextTick().then(() => { - expect(findVpcDropdown().props('hasErrors')).toEqual(true); - }); + await nextTick(); + expect(findVpcDropdown().props('hasErrors')).toEqual(true); }); it('disables SubnetDropdown when no vpc is selected', () => { expect(findSubnetDropdown().props('disabled')).toBe(true); }); - it('enables SubnetDropdown when a vpc is selected', () => { + it('enables SubnetDropdown when a vpc is selected', async () => { state.selectedVpc = { name: 'vpc-1 ' }; - return Vue.nextTick().then(() => { - expect(findSubnetDropdown().props('disabled')).toBe(false); - }); + await nextTick(); + expect(findSubnetDropdown().props('disabled')).toBe(false); }); - it('sets isLoadingSubnets to SubnetDropdown loading property', () => { + it('sets isLoadingSubnets to SubnetDropdown loading property', async () => { subnetsState.isLoadingItems = true; - return Vue.nextTick().then(() => { - expect(findSubnetDropdown().props('loading')).toBe(subnetsState.isLoadingItems); - }); + await nextTick(); + expect(findSubnetDropdown().props('loading')).toBe(subnetsState.isLoadingItems); }); it('sets subnets to SubnetDropdown items property', () => { @@ -360,32 +350,29 @@ describe('EksClusterConfigurationForm', () => { expect(findSecurityGroupDropdown().props('disabled')).toBe(true); }); - it('enables SecurityGroupDropdown when a vpc is selected', () => { + it('enables SecurityGroupDropdown when a vpc is selected', async () => { state.selectedVpc = { name: 'vpc-1 ' }; - return Vue.nextTick().then(() => { - expect(findSecurityGroupDropdown().props('disabled')).toBe(false); - }); + await nextTick(); + expect(findSecurityGroupDropdown().props('disabled')).toBe(false); }); - it('sets isLoadingSecurityGroups to SecurityGroupDropdown loading property', () => { + it('sets isLoadingSecurityGroups to SecurityGroupDropdown loading property', async () => { securityGroupsState.isLoadingItems = true; - return Vue.nextTick().then(() => { - expect(findSecurityGroupDropdown().props('loading')).toBe(securityGroupsState.isLoadingItems); - }); + await nextTick(); + expect(findSecurityGroupDropdown().props('loading')).toBe(securityGroupsState.isLoadingItems); }); it('sets securityGroups to SecurityGroupDropdown items property', () => { expect(findSecurityGroupDropdown().props('items')).toBe(securityGroupsState.items); }); - it('sets SecurityGroupDropdown hasErrors to true when loading security groups fails', () => { + it('sets SecurityGroupDropdown hasErrors to true when loading security groups fails', async () => { securityGroupsState.loadingItemsError = new Error(); - return Vue.nextTick().then(() => { - expect(findSecurityGroupDropdown().props('hasErrors')).toEqual(true); - }); + await nextTick(); + expect(findSecurityGroupDropdown().props('hasErrors')).toEqual(true); }); it('dispatches setClusterName when cluster name input changes', () => { diff --git a/spec/frontend/groups/components/group_folder_spec.js b/spec/frontend/groups/components/group_folder_spec.js index 1d8e10479b6..98b7c2dd6c6 100644 --- a/spec/frontend/groups/components/group_folder_spec.js +++ b/spec/frontend/groups/components/group_folder_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import groupFolderComponent from '~/groups/components/group_folder.vue'; import groupItemComponent from '~/groups/components/group_item.vue'; @@ -18,13 +18,13 @@ const createComponent = (groups = mockGroups, parentGroup = mockParentGroupItem) describe('GroupFolderComponent', () => { let vm; - beforeEach(() => { + beforeEach(async () => { Vue.component('GroupItem', groupItemComponent); vm = createComponent(); vm.$mount(); - return Vue.nextTick(); + await nextTick(); }); afterEach(() => { diff --git a/spec/frontend/ide/components/commit_sidebar/list_spec.js b/spec/frontend/ide/components/commit_sidebar/list_spec.js index eb12fc994a5..1d42512c9ee 100644 --- a/spec/frontend/ide/components/commit_sidebar/list_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/list_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import commitSidebarList from '~/ide/components/commit_sidebar/list.vue'; import { createStore } from '~/ide/stores'; @@ -31,12 +31,11 @@ describe('Multi-file editor commit sidebar list', () => { }); describe('with a list of files', () => { - beforeEach((done) => { + beforeEach(async () => { const f = file('file name'); f.changed = true; vm.fileList.push(f); - - Vue.nextTick(done); + await nextTick(); }); it('renders list', () => { diff --git a/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js b/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js index a6f3253321b..d899bc4f7d8 100644 --- a/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import radioGroup from '~/ide/components/commit_sidebar/radio_group.vue'; import { createStore } from '~/ide/stores'; @@ -7,7 +7,7 @@ describe('IDE commit sidebar radio group', () => { let vm; let store; - beforeEach((done) => { + beforeEach(async () => { store = createStore(); const Component = Vue.extend(radioGroup); @@ -22,7 +22,7 @@ describe('IDE commit sidebar radio group', () => { vm.$mount(); - Vue.nextTick(done); + await nextTick(); }); afterEach(() => { @@ -33,7 +33,7 @@ describe('IDE commit sidebar radio group', () => { expect(vm.$el.textContent).toContain('test'); }); - it('uses slot if label is not present', (done) => { + it('uses slot if label is not present', async () => { vm.$destroy(); vm = new Vue({ @@ -47,25 +47,19 @@ describe('IDE commit sidebar radio group', () => { vm.$mount(); - Vue.nextTick(() => { - expect(vm.$el.textContent).toContain('Testing slot'); - - done(); - }); + await nextTick(); + expect(vm.$el.textContent).toContain('Testing slot'); }); - it('updates store when changing radio button', (done) => { + it('updates store when changing radio button', async () => { vm.$el.querySelector('input').dispatchEvent(new Event('change')); - Vue.nextTick(() => { - expect(store.state.commit.commitAction).toBe('1'); - - done(); - }); + await nextTick(); + expect(store.state.commit.commitAction).toBe('1'); }); describe('with input', () => { - beforeEach((done) => { + beforeEach(async () => { vm.$destroy(); const Component = Vue.extend(radioGroup); @@ -82,32 +76,27 @@ describe('IDE commit sidebar radio group', () => { vm.$mount(); - Vue.nextTick(done); + await nextTick(); }); it('renders input box when commitAction matches value', () => { expect(vm.$el.querySelector('.form-control')).not.toBeNull(); }); - it('hides input when commitAction doesnt match value', (done) => { + it('hides input when commitAction doesnt match value', async () => { store.state.commit.commitAction = '2'; - Vue.nextTick(() => { - expect(vm.$el.querySelector('.form-control')).toBeNull(); - done(); - }); + await nextTick(); + expect(vm.$el.querySelector('.form-control')).toBeNull(); }); - it('updates branch name in store on input', (done) => { + it('updates branch name in store on input', async () => { const input = vm.$el.querySelector('.form-control'); input.value = 'testing-123'; input.dispatchEvent(new Event('input')); - Vue.nextTick(() => { - expect(store.state.commit.newBranchName).toBe('testing-123'); - - done(); - }); + await nextTick(); + expect(store.state.commit.newBranchName).toBe('testing-123'); }); it('renders newBranchName if present', () => { diff --git a/spec/frontend/ide/components/commit_sidebar/success_message_spec.js b/spec/frontend/ide/components/commit_sidebar/success_message_spec.js index 7bbe47d37af..52e35bdbb73 100644 --- a/spec/frontend/ide/components/commit_sidebar/success_message_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/success_message_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import successMessage from '~/ide/components/commit_sidebar/success_message.vue'; import { createStore } from '~/ide/stores'; @@ -23,13 +23,10 @@ describe('IDE commit panel successful commit state', () => { vm.$destroy(); }); - it('renders last commit message when it exists', (done) => { + it('renders last commit message when it exists', async () => { vm.$store.state.lastCommitMsg = 'testing commit message'; - Vue.nextTick(() => { - expect(vm.$el.textContent).toContain('testing commit message'); - - done(); - }); + await nextTick(); + expect(vm.$el.textContent).toContain('testing commit message'); }); }); diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js index 6b94d7cf6f1..45d1beea3f8 100644 --- a/spec/frontend/ide/stores/actions/file_spec.js +++ b/spec/frontend/ide/stores/actions/file_spec.js @@ -1,5 +1,5 @@ import MockAdapter from 'axios-mock-adapter'; -import Vue from 'vue'; +import { nextTick } from 'vue'; import eventHub from '~/ide/eventhub'; import { createRouter } from '~/ide/ide_router'; import service from '~/ide/services'; @@ -68,7 +68,7 @@ describe('IDE store file actions', () => { return store .dispatch('closeFile', localFile) - .then(Vue.nextTick) + .then(nextTick) .then(() => { expect(store.state.openFiles.length).toBe(0); expect(store.state.changedFiles.length).toBe(1); @@ -83,7 +83,7 @@ describe('IDE store file actions', () => { return store .dispatch('closeFile', localFile) - .then(Vue.nextTick) + .then(nextTick) .then(() => { expect(router.push).toHaveBeenCalledWith('/project/test/test/tree/main/-/newOpenFile/'); }); diff --git a/spec/frontend/issuable/components/issue_milestone_spec.js b/spec/frontend/issuable/components/issue_milestone_spec.js index 44416676180..9d67f602136 100644 --- a/spec/frontend/issuable/components/issue_milestone_spec.js +++ b/spec/frontend/issuable/components/issue_milestone_spec.js @@ -1,6 +1,6 @@ import { GlIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import { mockMilestone } from 'jest/boards/mock_data'; import IssueMilestone from '~/issuable/components/issue_milestone.vue'; @@ -19,12 +19,12 @@ describe('IssueMilestoneComponent', () => { let wrapper; let vm; - beforeEach((done) => { + beforeEach(async () => { wrapper = createComponent(); ({ vm } = wrapper); - Vue.nextTick(done); + await nextTick(); }); afterEach(() => { @@ -37,7 +37,7 @@ describe('IssueMilestoneComponent', () => { wrapper.setProps({ milestone: { ...mockMilestone, start_date: '' }, }); - await wrapper.vm.$nextTick(); + await nextTick(); expect(wrapper.vm.isMilestoneStarted).toBe(false); }); @@ -46,7 +46,7 @@ describe('IssueMilestoneComponent', () => { await wrapper.setProps({ milestone: { ...mockMilestone, start_date: '1990-07-22' }, }); - await wrapper.vm.$nextTick(); + await nextTick(); expect(wrapper.vm.isMilestoneStarted).toBe(true); }); @@ -57,7 +57,7 @@ describe('IssueMilestoneComponent', () => { wrapper.setProps({ milestone: { ...mockMilestone, due_date: '' }, }); - await wrapper.vm.$nextTick(); + await nextTick(); expect(wrapper.vm.isMilestonePastDue).toBe(false); }); @@ -80,7 +80,7 @@ describe('IssueMilestoneComponent', () => { wrapper.setProps({ milestone: { ...mockMilestone, due_date: '' }, }); - await wrapper.vm.$nextTick(); + await nextTick(); expect(wrapper.vm.milestoneDatesAbsolute).toBe('(January 1, 2018)'); }); @@ -89,7 +89,7 @@ describe('IssueMilestoneComponent', () => { wrapper.setProps({ milestone: { ...mockMilestone, start_date: '', due_date: '' }, }); - await wrapper.vm.$nextTick(); + await nextTick(); expect(wrapper.vm.milestoneDatesAbsolute).toBe(''); }); @@ -100,7 +100,7 @@ describe('IssueMilestoneComponent', () => { wrapper.setProps({ milestone: { ...mockMilestone, due_date: `${new Date().getFullYear() + 10}-01-01` }, }); - await wrapper.vm.$nextTick(); + await nextTick(); expect(wrapper.vm.milestoneDatesHuman).toContain('years remaining'); }); @@ -109,7 +109,7 @@ describe('IssueMilestoneComponent', () => { wrapper.setProps({ milestone: { ...mockMilestone, start_date: '1990-07-22', due_date: '' }, }); - await wrapper.vm.$nextTick(); + await nextTick(); expect(wrapper.vm.milestoneDatesHuman).toContain('Started'); }); @@ -122,7 +122,7 @@ describe('IssueMilestoneComponent', () => { due_date: '', }, }); - await wrapper.vm.$nextTick(); + await nextTick(); expect(wrapper.vm.milestoneDatesHuman).toContain('Starts'); }); @@ -131,7 +131,7 @@ describe('IssueMilestoneComponent', () => { wrapper.setProps({ milestone: { ...mockMilestone, start_date: '', due_date: '' }, }); - await wrapper.vm.$nextTick(); + await nextTick(); expect(wrapper.vm.milestoneDatesHuman).toBe(''); }); diff --git a/spec/frontend/issues/list/components/jira_issues_import_status_app_spec.js b/spec/frontend/issues/list/components/jira_issues_import_status_app_spec.js index d6d6bb14e9d..2d773e8bf56 100644 --- a/spec/frontend/issues/list/components/jira_issues_import_status_app_spec.js +++ b/spec/frontend/issues/list/components/jira_issues_import_status_app_spec.js @@ -1,6 +1,6 @@ import { GlAlert, GlLabel } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; +import { nextTick } from 'vue'; import JiraIssuesImportStatus from '~/issues/list/components/jira_issues_import_status_app.vue'; describe('JiraIssuesImportStatus', () => { @@ -100,7 +100,7 @@ describe('JiraIssuesImportStatus', () => { }); describe('alert message', () => { - it('is hidden when dismissed', () => { + it('is hidden when dismissed', async () => { wrapper = mountComponent({ shouldShowInProgressAlert: true, }); @@ -109,9 +109,8 @@ describe('JiraIssuesImportStatus', () => { findAlert().vm.$emit('dismiss'); - return Vue.nextTick(() => { - expect(wrapper.find(GlAlert).exists()).toBe(false); - }); + await nextTick(); + expect(wrapper.find(GlAlert).exists()).toBe(false); }); }); }); diff --git a/spec/frontend/issues/show/components/title_spec.js b/spec/frontend/issues/show/components/title_spec.js index f9026557be2..29b5353ef1c 100644 --- a/spec/frontend/issues/show/components/title_spec.js +++ b/spec/frontend/issues/show/components/title_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import titleComponent from '~/issues/show/components/title.vue'; import eventHub from '~/issues/show/event_hub'; import Store from '~/issues/show/stores'; @@ -29,36 +29,33 @@ describe('Title component', () => { expect(vm.$el.querySelector('.title').innerHTML.trim()).toBe('Testing '); }); - it('updates page title when changing titleHtml', () => { + it('updates page title when changing titleHtml', async () => { const spy = jest.spyOn(vm, 'setPageTitle'); vm.titleHtml = 'test'; - return vm.$nextTick().then(() => { - expect(spy).toHaveBeenCalled(); - }); + await nextTick(); + expect(spy).toHaveBeenCalled(); }); - it('animates title changes', () => { + it('animates title changes', async () => { vm.titleHtml = 'test'; - return vm - .$nextTick() - .then(() => { - expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-pre-pulse'); - jest.runAllTimers(); - return vm.$nextTick(); - }) - .then(() => { - expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-trigger-pulse'); - }); + + await nextTick(); + + expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-pre-pulse'); + jest.runAllTimers(); + + await nextTick(); + + expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-trigger-pulse'); }); - it('updates page title after changing title', () => { + it('updates page title after changing title', async () => { vm.titleHtml = 'changed'; vm.titleText = 'changed'; - return vm.$nextTick().then(() => { - expect(document.querySelector('title').textContent.trim()).toContain('changed'); - }); + await nextTick(); + expect(document.querySelector('title').textContent.trim()).toContain('changed'); }); describe('inline edit button', () => { @@ -80,16 +77,15 @@ describe('Title component', () => { expect(vm.$el.querySelector('.btn-edit')).toBeDefined(); }); - it('should trigger open.form event when clicked', () => { + it('should trigger open.form event when clicked', async () => { jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); vm.showInlineEditButton = true; vm.canUpdate = true; - Vue.nextTick(() => { - vm.$el.querySelector('.btn-edit').click(); + await nextTick(); + vm.$el.querySelector('.btn-edit').click(); - expect(eventHub.$emit).toHaveBeenCalledWith('open.form'); - }); + expect(eventHub.$emit).toHaveBeenCalledWith('open.form'); }); }); }); diff --git a/spec/frontend/jira_import/components/jira_import_app_spec.js b/spec/frontend/jira_import/components/jira_import_app_spec.js index 27314a0eb6e..cd8024d4962 100644 --- a/spec/frontend/jira_import/components/jira_import_app_spec.js +++ b/spec/frontend/jira_import/components/jira_import_app_spec.js @@ -1,6 +1,6 @@ import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; +import { nextTick } from 'vue'; import JiraImportApp from '~/jira_import/components/jira_import_app.vue'; import JiraImportForm from '~/jira_import/components/jira_import_form.vue'; import JiraImportProgress from '~/jira_import/components/jira_import_progress.vue'; @@ -230,7 +230,7 @@ describe('JiraImportApp', () => { getFormComponent().vm.$emit('error', 'There was an error'); - await Vue.nextTick(); + await nextTick(); expect(getAlert().exists()).toBe(true); }); @@ -248,7 +248,7 @@ describe('JiraImportApp', () => { getAlert().vm.$emit('dismiss'); - await Vue.nextTick(); + await nextTick(); expect(getAlert().exists()).toBe(false); }); diff --git a/spec/frontend/notes/components/note_actions_spec.js b/spec/frontend/notes/components/note_actions_spec.js index 842c669c489..780f24b3aa8 100644 --- a/spec/frontend/notes/components/note_actions_spec.js +++ b/spec/frontend/notes/components/note_actions_spec.js @@ -1,6 +1,6 @@ import { mount, createWrapper } from '@vue/test-utils'; import AxiosMockAdapter from 'axios-mock-adapter'; -import Vue from 'vue'; +import { nextTick } from 'vue'; import { TEST_HOST } from 'spec/test_constants'; import axios from '~/lib/utils/axios_utils'; import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; @@ -76,15 +76,14 @@ describe('noteActions', () => { expect(findUserAccessRoleBadgeText(1)).toBe(props.accessLevel); }); - it('should render contributor badge', () => { + it('should render contributor badge', async () => { wrapper.setProps({ accessLevel: null, isContributor: true, }); - return wrapper.vm.$nextTick().then(() => { - expect(findUserAccessRoleBadgeText(1)).toBe('Contributor'); - }); + await nextTick(); + expect(findUserAccessRoleBadgeText(1)).toBe('Contributor'); }); it('should render emoji link', () => { @@ -105,7 +104,7 @@ describe('noteActions', () => { expect(wrapper.find('.js-btn-copy-note-link').exists()).toBe(true); }); - it('should not show copy link action when `noteUrl` prop is empty', (done) => { + it('should not show copy link action when `noteUrl` prop is empty', async () => { wrapper.setProps({ ...props, author: { @@ -119,30 +118,23 @@ describe('noteActions', () => { noteUrl: '', }); - Vue.nextTick() - .then(() => { - expect(wrapper.find('.js-btn-copy-note-link').exists()).toBe(false); - }) - .then(done) - .catch(done.fail); + await nextTick(); + expect(wrapper.find('.js-btn-copy-note-link').exists()).toBe(false); }); it('should be possible to delete comment', () => { expect(wrapper.find('.js-note-delete').exists()).toBe(true); }); - it('closes tooltip when dropdown opens', (done) => { + it('closes tooltip when dropdown opens', async () => { wrapper.find('.more-actions-toggle').trigger('click'); const rootWrapper = createWrapper(wrapper.vm.$root); - Vue.nextTick() - .then(() => { - const emitted = Object.keys(rootWrapper.emitted()); - expect(emitted).toEqual([BV_HIDE_TOOLTIP]); - done(); - }) - .catch(done.fail); + await nextTick(); + const emitted = Object.keys(rootWrapper.emitted()); + + expect(emitted).toEqual([BV_HIDE_TOOLTIP]); }); it('should not be possible to assign or unassign the comment author in a merge request', () => { diff --git a/spec/frontend/notes/components/note_body_spec.js b/spec/frontend/notes/components/note_body_spec.js index 4e345c9ac8d..63f3cd865d5 100644 --- a/spec/frontend/notes/components/note_body_spec.js +++ b/spec/frontend/notes/components/note_body_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import { suggestionCommitMessage } from '~/diffs/store/getters'; @@ -46,9 +46,9 @@ describe('issue_note_body component', () => { }); describe('isEditing', () => { - beforeEach((done) => { + beforeEach(async () => { vm.isEditing = true; - Vue.nextTick(done); + await nextTick(); }); it('renders edit form', () => { diff --git a/spec/frontend/notes/components/notes_app_spec.js b/spec/frontend/notes/components/notes_app_spec.js index 84d94857fe5..bf36d6cb7a2 100644 --- a/spec/frontend/notes/components/notes_app_spec.js +++ b/spec/frontend/notes/components/notes_app_spec.js @@ -1,7 +1,7 @@ import { mount, shallowMount } from '@vue/test-utils'; import AxiosMockAdapter from 'axios-mock-adapter'; import $ from 'jquery'; -import Vue from 'vue'; +import { nextTick } from 'vue'; import setWindowLocation from 'helpers/set_window_location_helper'; import { setTestTimeout } from 'helpers/timeout'; import waitForPromises from 'helpers/wait_for_promises'; @@ -294,24 +294,22 @@ describe('note_app', () => { return waitForDiscussionsRequest(); }); - it('should render markdown docs url', () => { + it('should render markdown docs url', async () => { wrapper.find('.js-note-edit').trigger('click'); const { markdownDocsPath } = mockData.notesDataMock; - return Vue.nextTick().then(() => { - expect(wrapper.find(`.edit-note a[href="${markdownDocsPath}"]`).text().trim()).toEqual( - 'Markdown is supported', - ); - }); + await nextTick(); + expect(wrapper.find(`.edit-note a[href="${markdownDocsPath}"]`).text().trim()).toEqual( + 'Markdown is supported', + ); }); - it('should not render quick actions docs url', () => { + it('should not render quick actions docs url', async () => { wrapper.find('.js-note-edit').trigger('click'); const { quickActionsDocsPath } = mockData.notesDataMock; - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.find(`.edit-note a[href="${quickActionsDocsPath}"]`).exists()).toBe(false); - }); + await nextTick(); + expect(wrapper.find(`.edit-note a[href="${quickActionsDocsPath}"]`).exists()).toBe(false); }); }); diff --git a/spec/frontend/profile/account/components/delete_account_modal_spec.js b/spec/frontend/profile/account/components/delete_account_modal_spec.js index f1784500baf..ad62d84c43c 100644 --- a/spec/frontend/profile/account/components/delete_account_modal_spec.js +++ b/spec/frontend/profile/account/components/delete_account_modal_spec.js @@ -1,6 +1,6 @@ import { mount } from '@vue/test-utils'; import { merge } from 'lodash'; -import Vue from 'vue'; +import { nextTick } from 'vue'; import { TEST_HOST } from 'helpers/test_constants'; import deleteAccountModal from '~/profile/account/components/delete_account_modal.vue'; @@ -56,7 +56,7 @@ describe('DeleteAccountModal component', () => { const findModal = () => wrapper.find(GlModalStub); describe('with password confirmation', () => { - beforeEach((done) => { + beforeEach(async () => { createWrapper({ propsData: { confirmWithPassword: true, @@ -65,48 +65,40 @@ describe('DeleteAccountModal component', () => { vm.isOpen = true; - Vue.nextTick().then(done).catch(done.fail); + await nextTick(); }); - it('does not accept empty password', (done) => { + it('does not accept empty password', async () => { const { form, input } = findElements(); jest.spyOn(form, 'submit').mockImplementation(() => {}); input.value = ''; input.dispatchEvent(new Event('input')); - Vue.nextTick() - .then(() => { - expect(vm.enteredPassword).toBe(input.value); - expect(findModal().attributes('ok-disabled')).toBe('true'); - findModal().vm.$emit('primary'); + await nextTick(); + expect(vm.enteredPassword).toBe(input.value); + expect(findModal().attributes('ok-disabled')).toBe('true'); + findModal().vm.$emit('primary'); - expect(form.submit).not.toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); + expect(form.submit).not.toHaveBeenCalled(); }); - it('submits form with password', (done) => { + it('submits form with password', async () => { const { form, input } = findElements(); jest.spyOn(form, 'submit').mockImplementation(() => {}); input.value = 'anything'; input.dispatchEvent(new Event('input')); - Vue.nextTick() - .then(() => { - expect(vm.enteredPassword).toBe(input.value); - expect(findModal().attributes('ok-disabled')).toBeUndefined(); - findModal().vm.$emit('primary'); + await nextTick(); + expect(vm.enteredPassword).toBe(input.value); + expect(findModal().attributes('ok-disabled')).toBeUndefined(); + findModal().vm.$emit('primary'); - expect(form.submit).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); + expect(form.submit).toHaveBeenCalled(); }); }); describe('with username confirmation', () => { - beforeEach((done) => { + beforeEach(async () => { createWrapper({ propsData: { confirmWithPassword: false, @@ -115,43 +107,35 @@ describe('DeleteAccountModal component', () => { vm.isOpen = true; - Vue.nextTick().then(done).catch(done.fail); + await nextTick(); }); - it('does not accept wrong username', (done) => { + it('does not accept wrong username', async () => { const { form, input } = findElements(); jest.spyOn(form, 'submit').mockImplementation(() => {}); input.value = 'this is wrong'; input.dispatchEvent(new Event('input')); - Vue.nextTick() - .then(() => { - expect(vm.enteredUsername).toBe(input.value); - expect(findModal().attributes('ok-disabled')).toBe('true'); - findModal().vm.$emit('primary'); + await nextTick(); + expect(vm.enteredUsername).toBe(input.value); + expect(findModal().attributes('ok-disabled')).toBe('true'); + findModal().vm.$emit('primary'); - expect(form.submit).not.toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); + expect(form.submit).not.toHaveBeenCalled(); }); - it('submits form with correct username', (done) => { + it('submits form with correct username', async () => { const { form, input } = findElements(); jest.spyOn(form, 'submit').mockImplementation(() => {}); input.value = username; input.dispatchEvent(new Event('input')); - Vue.nextTick() - .then(() => { - expect(vm.enteredUsername).toBe(input.value); - expect(findModal().attributes('ok-disabled')).toBeUndefined(); - findModal().vm.$emit('primary'); + await nextTick(); + expect(vm.enteredUsername).toBe(input.value); + expect(findModal().attributes('ok-disabled')).toBeUndefined(); + findModal().vm.$emit('primary'); - expect(form.submit).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); + expect(form.submit).toHaveBeenCalled(); }); }); }); diff --git a/spec/frontend/reports/components/report_section_spec.js b/spec/frontend/reports/components/report_section_spec.js index 39932b62dbb..f9eb6dd05f3 100644 --- a/spec/frontend/reports/components/report_section_spec.js +++ b/spec/frontend/reports/components/report_section_spec.js @@ -1,5 +1,5 @@ import { mount } from '@vue/test-utils'; -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import mountComponent, { mountComponentWithSlots } from 'helpers/vue_mount_component_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import reportSection from '~/reports/components/report_section.vue'; @@ -71,16 +71,12 @@ describe('Report section', () => { const issues = hasIssues ? 'has issues' : 'has no issues'; const open = alwaysOpen ? 'is always open' : 'is not always open'; - it(`is ${isCollapsible}, if the report ${issues} and ${open}`, (done) => { + it(`is ${isCollapsible}, if the report ${issues} and ${open}`, async () => { vm.hasIssues = hasIssues; vm.alwaysOpen = alwaysOpen; - Vue.nextTick() - .then(() => { - expect(vm.isCollapsible).toBe(isCollapsible); - }) - .then(done) - .catch(done.fail); + await nextTick(); + expect(vm.isCollapsible).toBe(isCollapsible); }); }); }); @@ -97,16 +93,12 @@ describe('Report section', () => { const issues = isCollapsed ? 'is collapsed' : 'is not collapsed'; const open = alwaysOpen ? 'is always open' : 'is not always open'; - it(`is ${isExpanded}, if the report ${issues} and ${open}`, (done) => { + it(`is ${isExpanded}, if the report ${issues} and ${open}`, async () => { vm.isCollapsed = isCollapsed; vm.alwaysOpen = alwaysOpen; - Vue.nextTick() - .then(() => { - expect(vm.isExpanded).toBe(isExpanded); - }) - .then(done) - .catch(done.fail); + await nextTick(); + expect(vm.isExpanded).toBe(isExpanded); }); }); }); @@ -148,79 +140,55 @@ describe('Report section', () => { describe('toggleCollapsed', () => { const hiddenCss = { display: 'none' }; - it('toggles issues', (done) => { + it('toggles issues', async () => { vm.$el.querySelector('button').click(); - Vue.nextTick() - .then(() => { - expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss); - expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Collapse'); + await nextTick(); + expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss); + expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Collapse'); - vm.$el.querySelector('button').click(); - }) - .then(Vue.nextTick) - .then(() => { - expect(vm.$el.querySelector('.js-report-section-container')).toHaveCss(hiddenCss); - expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Expand'); - }) - .then(done) - .catch(done.fail); + vm.$el.querySelector('button').click(); + + await nextTick(); + expect(vm.$el.querySelector('.js-report-section-container')).toHaveCss(hiddenCss); + expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Expand'); }); - it('is always expanded, if always-open is set to true', (done) => { + it('is always expanded, if always-open is set to true', async () => { vm.alwaysOpen = true; - Vue.nextTick() - .then(() => { - expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss); - expect(vm.$el.querySelector('button')).toBeNull(); - }) - .then(done) - .catch(done.fail); + await nextTick(); + expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss); + expect(vm.$el.querySelector('button')).toBeNull(); }); }); }); describe('snowplow events', () => { - it('does emit an event on issue toggle if the shouldEmitToggleEvent prop does exist', (done) => { + it('does emit an event on issue toggle if the shouldEmitToggleEvent prop does exist', async () => { createComponent({ hasIssues: true, shouldEmitToggleEvent: true }); expect(wrapper.emitted().toggleEvent).toBeUndefined(); findCollapseButton().trigger('click'); - return wrapper.vm - .$nextTick() - .then(() => { - expect(wrapper.emitted().toggleEvent).toHaveLength(1); - }) - .then(done) - .catch(done.fail); + await nextTick(); + expect(wrapper.emitted().toggleEvent).toHaveLength(1); }); - it('does not emit an event on issue toggle if the shouldEmitToggleEvent prop does not exist', (done) => { + it('does not emit an event on issue toggle if the shouldEmitToggleEvent prop does not exist', async () => { createComponent({ hasIssues: true }); expect(wrapper.emitted().toggleEvent).toBeUndefined(); findCollapseButton().trigger('click'); - return wrapper.vm - .$nextTick() - .then(() => { - expect(wrapper.emitted().toggleEvent).toBeUndefined(); - }) - .then(done) - .catch(done.fail); + await nextTick(); + expect(wrapper.emitted().toggleEvent).toBeUndefined(); }); - it('does not emit an event if always-open is set to true', (done) => { + it('does not emit an event if always-open is set to true', async () => { createComponent({ alwaysOpen: true, hasIssues: true, shouldEmitToggleEvent: true }); - wrapper.vm - .$nextTick() - .then(() => { - expect(wrapper.emitted().toggleEvent).toBeUndefined(); - }) - .then(done) - .catch(done.fail); + await nextTick(); + expect(wrapper.emitted().toggleEvent).toBeUndefined(); }); }); diff --git a/spec/frontend/sidebar/participants_spec.js b/spec/frontend/sidebar/participants_spec.js index 94cdbe7f2ef..356628849d9 100644 --- a/spec/frontend/sidebar/participants_spec.js +++ b/spec/frontend/sidebar/participants_spec.js @@ -1,6 +1,6 @@ import { GlLoadingIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; +import { nextTick } from 'vue'; import Participants from '~/sidebar/components/participants/participants.vue'; const PARTICIPANT = { @@ -77,7 +77,7 @@ describe('Participants', () => { expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); }); - it('when only showing visible participants, shows an avatar only for each participant under the limit', () => { + it('when only showing visible participants, shows an avatar only for each participant under the limit', async () => { const numberOfLessParticipants = 2; wrapper = mountComponent({ loading: false, @@ -91,12 +91,11 @@ describe('Participants', () => { isShowingMoreParticipants: false, }); - return Vue.nextTick().then(() => { - expect(wrapper.findAll('.participants-author')).toHaveLength(numberOfLessParticipants); - }); + await nextTick(); + expect(wrapper.findAll('.participants-author')).toHaveLength(numberOfLessParticipants); }); - it('when only showing all participants, each has an avatar', () => { + it('when only showing all participants, each has an avatar', async () => { wrapper = mountComponent({ loading: false, participants: PARTICIPANT_LIST, @@ -109,9 +108,8 @@ describe('Participants', () => { isShowingMoreParticipants: true, }); - return Vue.nextTick().then(() => { - expect(wrapper.findAll('.participants-author')).toHaveLength(PARTICIPANT_LIST.length); - }); + await nextTick(); + expect(wrapper.findAll('.participants-author')).toHaveLength(PARTICIPANT_LIST.length); }); it('does not have more participants link when they can all be shown', () => { @@ -126,7 +124,7 @@ describe('Participants', () => { expect(getMoreParticipantsButton().exists()).toBe(false); }); - it('when too many participants, has more participants link to show more', () => { + it('when too many participants, has more participants link to show more', async () => { wrapper = mountComponent({ loading: false, participants: PARTICIPANT_LIST, @@ -139,12 +137,11 @@ describe('Participants', () => { isShowingMoreParticipants: false, }); - return Vue.nextTick().then(() => { - expect(getMoreParticipantsButton().text()).toBe('+ 1 more'); - }); + await nextTick(); + expect(getMoreParticipantsButton().text()).toBe('+ 1 more'); }); - it('when too many participants and already showing them, has more participants link to show less', () => { + it('when too many participants and already showing them, has more participants link to show less', async () => { wrapper = mountComponent({ loading: false, participants: PARTICIPANT_LIST, @@ -157,9 +154,8 @@ describe('Participants', () => { isShowingMoreParticipants: true, }); - return Vue.nextTick().then(() => { - expect(getMoreParticipantsButton().text()).toBe('- show less'); - }); + await nextTick(); + expect(getMoreParticipantsButton().text()).toBe('- show less'); }); it('clicking more participants link emits event', () => { @@ -176,7 +172,7 @@ describe('Participants', () => { expect(wrapper.vm.isShowingMoreParticipants).toBe(true); }); - it('clicking on participants icon emits `toggleSidebar` event', () => { + it('clicking on participants icon emits `toggleSidebar` event', async () => { wrapper = mountComponent({ loading: false, participants: PARTICIPANT_LIST, @@ -187,11 +183,9 @@ describe('Participants', () => { wrapper.find('.sidebar-collapsed-icon').trigger('click'); - return Vue.nextTick(() => { - expect(spy).toHaveBeenCalledWith('toggleSidebar'); - - spy.mockRestore(); - }); + await nextTick(); + expect(spy).toHaveBeenCalledWith('toggleSidebar'); + spy.mockRestore(); }); }); diff --git a/spec/frontend/user_lists/components/user_lists_spec.js b/spec/frontend/user_lists/components/user_lists_spec.js index 7a33c6faac9..10742c029c1 100644 --- a/spec/frontend/user_lists/components/user_lists_spec.js +++ b/spec/frontend/user_lists/components/user_lists_spec.js @@ -1,7 +1,7 @@ import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui'; import { within } from '@testing-library/dom'; import { mount, createWrapper } from '@vue/test-utils'; -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import waitForPromises from 'helpers/wait_for_promises'; import Api from '~/api'; @@ -82,7 +82,7 @@ describe('~/user_lists/components/user_lists.vue', () => { factory(); await waitForPromises(); - await Vue.nextTick(); + await nextTick(); emptyState = wrapper.findComponent(GlEmptyState); }); @@ -130,7 +130,7 @@ describe('~/user_lists/components/user_lists.vue', () => { factory(); jest.spyOn(store, 'dispatch'); - await Vue.nextTick(); + await nextTick(); table = wrapper.findComponent(UserListsTable); }); @@ -171,7 +171,7 @@ describe('~/user_lists/components/user_lists.vue', () => { Api.fetchFeatureFlagUserLists.mockRejectedValue(); factory(); - await Vue.nextTick(); + await nextTick(); }); it('should render error state', () => { diff --git a/spec/frontend/vue_alerts_spec.js b/spec/frontend/vue_alerts_spec.js index 30be606292f..1952eea4a01 100644 --- a/spec/frontend/vue_alerts_spec.js +++ b/spec/frontend/vue_alerts_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import { nextTick } from 'vue'; import { setHTMLFixture } from 'helpers/fixtures'; import { TEST_HOST } from 'helpers/test_constants'; import initVueAlerts from '~/vue_alerts'; @@ -75,10 +75,9 @@ describe('VueAlerts', () => { }); describe('when dismissed', () => { - beforeEach(() => { + beforeEach(async () => { findAlertDismiss(findAlerts()[0]).click(); - - return Vue.nextTick(); + await nextTick(); }); it('hides the alert', () => { diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js index f55d313a719..45e9335abbe 100644 --- a/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js +++ b/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js @@ -1,6 +1,6 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue'; import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; @@ -184,7 +184,7 @@ describe('MemoryUsage', () => { vm.hasMetrics = false; vm.loadFailed = false; - Vue.nextTick(() => { + nextTick(() => { expect(el.querySelector('.js-usage-info.usage-info-loading')).toBeDefined(); expect(el.querySelector('.js-usage-info .usage-info-load-spinner')).toBeDefined(); @@ -203,7 +203,7 @@ describe('MemoryUsage', () => { vm.loadFailed = false; vm.memoryMetrics = metricsMockData.metrics.memory_values[0].values; - Vue.nextTick(() => { + nextTick(() => { expect(el.querySelector('.memory-graph-container')).toBeDefined(); expect(el.querySelector('.js-usage-info').innerText).toContain(messages.hasMetrics); done(); @@ -215,7 +215,7 @@ describe('MemoryUsage', () => { vm.hasMetrics = false; vm.loadFailed = true; - Vue.nextTick(() => { + nextTick(() => { expect(el.querySelector('.js-usage-info.usage-info-failed')).toBeDefined(); expect(el.querySelector('.js-usage-info').innerText).toContain(messages.loadFailed); @@ -228,7 +228,7 @@ describe('MemoryUsage', () => { vm.hasMetrics = false; vm.loadFailed = false; - Vue.nextTick(() => { + nextTick(() => { expect(el.querySelector('.js-usage-info.usage-info-unavailable')).toBeDefined(); expect(el.querySelector('.js-usage-info').innerText).toContain(messages.metricsUnavailable); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js index e0f1f091129..677cff84672 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js @@ -1,5 +1,5 @@ import { getByRole } from '@testing-library/dom'; -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import mountComponent from 'helpers/vue_mount_component_helper'; import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '~/projects/commit/constants'; import modalEventHub from '~/projects/commit/event_hub'; @@ -200,7 +200,7 @@ describe('MRWidgetMerged', () => { it('hides button to copy commit SHA if SHA does not exist', (done) => { vm.mr.mergeCommitSha = null; - Vue.nextTick(() => { + nextTick(() => { expect(selectors.copyMergeShaButton).toBe(null); expect(vm.$el.querySelector('.mr-info-list').innerText).not.toContain('with'); done(); @@ -216,7 +216,7 @@ describe('MRWidgetMerged', () => { it('should not show source branch deleted text', (done) => { vm.mr.sourceBranchRemoved = false; - Vue.nextTick(() => { + nextTick(() => { expect(vm.$el.innerText).not.toContain('The source branch has been deleted'); done(); }); @@ -226,7 +226,7 @@ describe('MRWidgetMerged', () => { vm.mr.isRemovingSourceBranch = true; vm.mr.sourceBranchRemoved = false; - Vue.nextTick(() => { + nextTick(() => { expect(vm.$el.innerText).toContain('The source branch is being deleted'); expect(vm.$el.innerText).not.toContain('The source branch has been deleted'); done(); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js index 2c04905d3a9..c7c0b69425d 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import NothingToMerge from '~/vue_merge_request_widget/components/states/nothing_to_merge.vue'; describe('NothingToMerge', () => { @@ -20,7 +20,7 @@ describe('NothingToMerge', () => { it('should not show new blob link if there is no link available', () => { vm.mr.newBlobPath = null; - Vue.nextTick(() => { + nextTick(() => { expect(vm.$el.querySelector('[data-testid="createFileButton"]')).toEqual(null); }); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index f4ecebbb40c..b4ef40ccd72 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; +import { nextTick } from 'vue'; import { GlSprintf } from '@gitlab/ui'; import simplePoll from '~/lib/utils/simple_poll'; import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue'; @@ -196,7 +196,7 @@ describe('ReadyToMerge', () => { // eslint-disable-next-line no-restricted-syntax wrapper.setData({ isMergingImmediately: true }); - await Vue.nextTick(); + await nextTick(); expect(wrapper.vm.mergeButtonText).toEqual('Merge in progress'); }); @@ -266,7 +266,7 @@ describe('ReadyToMerge', () => { // eslint-disable-next-line no-restricted-syntax wrapper.setData({ isMakingRequest: true }); - await Vue.nextTick(); + await nextTick(); expect(wrapper.vm.isMergeButtonDisabled).toBe(true); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js index 4070ca8d8dc..303e9aed113 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import WorkInProgress from '~/vue_merge_request_widget/components/states/work_in_progress.vue'; import toast from '~/vue_shared/plugins/global_toast'; import eventHub from '~/vue_merge_request_widget/event_hub'; @@ -94,7 +94,7 @@ describe('Wip', () => { it('should not show removeWIP button is user cannot update MR', (done) => { vm.mr.removeWIPPath = ''; - Vue.nextTick(() => { + nextTick(() => { expect(el.querySelector('.js-remove-draft')).toEqual(null); done(); }); diff --git a/spec/frontend/vue_shared/components/expand_button_spec.js b/spec/frontend/vue_shared/components/expand_button_spec.js index 7874658cc0f..87d6ed6b21f 100644 --- a/spec/frontend/vue_shared/components/expand_button_spec.js +++ b/spec/frontend/vue_shared/components/expand_button_spec.js @@ -1,5 +1,5 @@ import { mount } from '@vue/test-utils'; -import Vue from 'vue'; +import { nextTick } from 'vue'; import ExpandButton from '~/vue_shared/components/expand_button.vue'; const text = { @@ -66,9 +66,9 @@ describe('Expand button', () => { }); describe('on click', () => { - beforeEach((done) => { + beforeEach(async () => { expanderPrependEl().trigger('click'); - Vue.nextTick(done); + await nextTick(); }); afterEach(() => { @@ -85,7 +85,7 @@ describe('Expand button', () => { }); describe('when short text is provided', () => { - beforeEach((done) => { + beforeEach(async () => { factory({ slots: { expanded: `

${text.expanded}

`, @@ -94,7 +94,7 @@ describe('Expand button', () => { }); expanderPrependEl().trigger('click'); - Vue.nextTick(done); + await nextTick(); }); it('only renders expanded text', () => { @@ -110,31 +110,29 @@ describe('Expand button', () => { }); describe('append button', () => { - beforeEach((done) => { + beforeEach(async () => { expanderPrependEl().trigger('click'); - Vue.nextTick(done); + await nextTick(); }); - it('clicking hides itself and shows prepend', () => { + it('clicking hides itself and shows prepend', async () => { expect(expanderAppendEl().isVisible()).toBe(true); expanderAppendEl().trigger('click'); - return wrapper.vm.$nextTick().then(() => { - expect(expanderPrependEl().isVisible()).toBe(true); - }); + await nextTick(); + expect(expanderPrependEl().isVisible()).toBe(true); }); - it('clicking hides expanded text', () => { + it('clicking hides expanded text', async () => { expect(wrapper.find(ExpandButton).text().trim()).toBe(text.expanded); expanderAppendEl().trigger('click'); - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.find(ExpandButton).text().trim()).not.toBe(text.expanded); - }); + await nextTick(); + expect(wrapper.find(ExpandButton).text().trim()).not.toBe(text.expanded); }); describe('when short text is provided', () => { - beforeEach((done) => { + beforeEach(async () => { factory({ slots: { expanded: `

${text.expanded}

`, @@ -143,16 +141,15 @@ describe('Expand button', () => { }); expanderPrependEl().trigger('click'); - Vue.nextTick(done); + await nextTick(); }); - it('clicking reveals short text', () => { + it('clicking reveals short text', async () => { expect(wrapper.find(ExpandButton).text().trim()).toBe(text.expanded); expanderAppendEl().trigger('click'); - return wrapper.vm.$nextTick().then(() => { - expect(wrapper.find(ExpandButton).text().trim()).toBe(text.short); - }); + await nextTick(); + expect(wrapper.find(ExpandButton).text().trim()).toBe(text.short); }); }); }); diff --git a/spec/frontend/vue_shared/components/gl_countdown_spec.js b/spec/frontend/vue_shared/components/gl_countdown_spec.js index 82d18c7fd3f..0d1d42082ab 100644 --- a/spec/frontend/vue_shared/components/gl_countdown_spec.js +++ b/spec/frontend/vue_shared/components/gl_countdown_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import mountComponent from 'helpers/vue_mount_component_helper'; import GlCountdown from '~/vue_shared/components/gl_countdown.vue'; @@ -17,38 +17,34 @@ describe('GlCountdown', () => { }); describe('when there is time remaining', () => { - beforeEach((done) => { + beforeEach(async () => { vm = mountComponent(Component, { endDateString: '2000-01-01T01:02:03Z', }); - Vue.nextTick().then(done).catch(done.fail); + await nextTick(); }); it('displays remaining time', () => { expect(vm.$el.textContent).toContain('01:02:03'); }); - it('updates remaining time', (done) => { + it('updates remaining time', async () => { now = '2000-01-01T00:00:01Z'; jest.advanceTimersByTime(1000); - Vue.nextTick() - .then(() => { - expect(vm.$el.textContent).toContain('01:02:02'); - done(); - }) - .catch(done.fail); + await nextTick(); + expect(vm.$el.textContent).toContain('01:02:02'); }); }); describe('when there is no time remaining', () => { - beforeEach((done) => { + beforeEach(async () => { vm = mountComponent(Component, { endDateString: '1900-01-01T00:00:00Z', }); - Vue.nextTick().then(done).catch(done.fail); + await nextTick(); }); it('displays 00:00:00', () => { diff --git a/spec/frontend/vue_shared/components/markdown/suggestions_spec.js b/spec/frontend/vue_shared/components/markdown/suggestions_spec.js index 6fcac2df0b6..8f4235cfe41 100644 --- a/spec/frontend/vue_shared/components/markdown/suggestions_spec.js +++ b/spec/frontend/vue_shared/components/markdown/suggestions_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import SuggestionsComponent from '~/vue_shared/components/markdown/suggestions.vue'; const MOCK_DATA = { @@ -51,7 +51,7 @@ describe('Suggestion component', () => { let vm; let diffTable; - beforeEach((done) => { + beforeEach(async () => { const Component = Vue.extend(SuggestionsComponent); vm = new Component({ @@ -62,7 +62,7 @@ describe('Suggestion component', () => { jest.spyOn(vm, 'renderSuggestions').mockImplementation(() => {}); vm.renderSuggestions(); - Vue.nextTick(done); + await nextTick(); }); describe('mounted', () => { diff --git a/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js index 40f0c0f29f2..7536df24ac6 100644 --- a/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js +++ b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js @@ -1,6 +1,6 @@ import { mount } from '@vue/test-utils'; import $ from 'jquery'; -import Vue from 'vue'; +import { nextTick } from 'vue'; import ResizableChartContainer from '~/vue_shared/components/resizable_chart/resizable_chart_container.vue'; jest.mock('~/lib/utils/common_utils', () => ({ @@ -35,7 +35,7 @@ describe('Resizable Chart Container', () => { expect(wrapper.element).toMatchSnapshot(); }); - it('updates the slot width and height props', () => { + it('updates the slot width and height props', async () => { const width = 1920; const height = 1080; @@ -44,13 +44,12 @@ describe('Resizable Chart Container', () => { $(document).trigger('content.resize'); - return Vue.nextTick().then(() => { - const widthNode = wrapper.find('.slot > .width'); - const heightNode = wrapper.find('.slot > .height'); + await nextTick(); + const widthNode = wrapper.find('.slot > .width'); + const heightNode = wrapper.find('.slot > .height'); - expect(parseInt(widthNode.text(), 10)).toEqual(width); - expect(parseInt(heightNode.text(), 10)).toEqual(height); - }); + expect(parseInt(widthNode.text(), 10)).toEqual(width); + expect(parseInt(heightNode.text(), 10)).toEqual(height); }); it('calls onResize on manual resize', () => { diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 1957c58ec81..0f67531382d 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -894,4 +894,15 @@ RSpec.describe Member do end end end + + describe '#set_member_namespace_id' do + let(:group) { create(:group) } + let(:member) { create(:group_member, group: group) } + + describe 'on create' do + it 'sets the member_namespace_id' do + expect(member.member_namespace_id).to eq group.id + end + end + end end diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 031caefbd43..3923f4161cc 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -257,4 +257,15 @@ RSpec.describe ProjectMember do it_behaves_like 'calls AuthorizedProjectUpdate::UserRefreshFromReplicaWorker with a delay to update project authorizations' end end + + describe '#set_member_namespace_id' do + let(:project) { create(:project) } + let(:member) { create(:project_member, project: project) } + + context 'on create' do + it 'sets the member_namespace_id' do + expect(member.member_namespace_id).to eq project.project_namespace_id + end + end + end end diff --git a/spec/models/state_note_spec.rb b/spec/models/state_note_spec.rb index bd07af7ceca..e91150695b0 100644 --- a/spec/models/state_note_spec.rb +++ b/spec/models/state_note_spec.rb @@ -55,7 +55,7 @@ RSpec.describe StateNote do it 'contains the expected values' do expect(subject.author).to eq(author) expect(subject.created_at).to eq(event.created_at) - expect(subject.note).to eq('resolved the corresponding error and closed the issue.') + expect(subject.note).to eq('resolved the corresponding error and closed the issue') end end @@ -65,7 +65,7 @@ RSpec.describe StateNote do it 'contains the expected values' do expect(subject.author).to eq(author) expect(subject.created_at).to eq(event.created_at) - expect(subject.note).to eq('automatically closed this issue because the alert resolved.') + expect(subject.note).to eq('automatically closed this incident because the alert resolved') end end end diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index 158f9dec83e..1f6118e9fcc 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -118,7 +118,7 @@ RSpec.describe Issues::CloseService do expect { service.execute(issue) }.to change { issue.notes.count }.by(1) new_note = issue.notes.last - expect(new_note.note).to eq('changed the status to **Resolved** by closing the incident') + expect(new_note.note).to eq('changed the incident status to **Resolved** by closing the incident') expect(new_note.author).to eq(user) end @@ -334,8 +334,12 @@ RSpec.describe Issues::CloseService do let!(:alert) { create(:alert_management_alert, issue: issue, project: project) } it 'resolves an alert and sends a system note' do - expect_next_instance_of(SystemNotes::AlertManagementService) do |notes_service| - expect(notes_service).to receive(:closed_alert_issue).with(issue) + expect_any_instance_of(SystemNoteService) do |notes_service| + expect(notes_service).to receive(:change_alert_status).with( + alert, + current_user, + " by closing issue #{issue.to_reference(project)}" + ) end close_issue diff --git a/spec/services/projects/import_export/export_service_spec.rb b/spec/services/projects/import_export/export_service_spec.rb index 6002aaf427a..54abbc04084 100644 --- a/spec/services/projects/import_export/export_service_spec.rb +++ b/spec/services/projects/import_export/export_service_spec.rb @@ -93,11 +93,23 @@ RSpec.describe Projects::ImportExport::ExportService do end it 'saves the project in the file system' do - expect(Gitlab::ImportExport::Saver).to receive(:save).with(exportable: project, shared: shared) + expect(Gitlab::ImportExport::Saver).to receive(:save).with(exportable: project, shared: shared).and_return(true) service.execute end + context 'when the upload fails' do + before do + expect(Gitlab::ImportExport::Saver).to receive(:save).with(exportable: project, shared: shared).and_return(false) + end + + it 'notifies the user of an error' do + expect(service).to receive(:notify_error).and_call_original + + expect { service.execute }.to raise_error(Gitlab::ImportExport::Error) + end + end + it 'calls the after export strategy' do expect(after_export_strategy).to receive(:execute) @@ -107,6 +119,7 @@ RSpec.describe Projects::ImportExport::ExportService do context 'when after export strategy fails' do before do allow(after_export_strategy).to receive(:execute).and_return(false) + expect(Gitlab::ImportExport::Saver).to receive(:save).with(exportable: project, shared: shared).and_return(true) end after do diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index bbf8735add6..44acd34ae6a 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -632,18 +632,6 @@ RSpec.describe SystemNoteService do end end - describe '.resolve_incident_status' do - let(:incident) { build(:incident, :closed) } - - it 'calls IncidentService' do - expect_next_instance_of(SystemNotes::IncidentService) do |service| - expect(service).to receive(:resolve_incident_status) - end - - described_class.resolve_incident_status(incident, author) - end - end - describe '.change_incident_status' do let(:incident) { instance_double('Issue', project: project) } diff --git a/spec/services/system_notes/alert_management_service_spec.rb b/spec/services/system_notes/alert_management_service_spec.rb index cd7393def0c..039975c1bf6 100644 --- a/spec/services/system_notes/alert_management_service_spec.rb +++ b/spec/services/system_notes/alert_management_service_spec.rb @@ -54,21 +54,7 @@ RSpec.describe ::SystemNotes::AlertManagementService do end it 'has the appropriate message' do - expect(subject.note).to eq("created issue #{issue.to_reference(project)} for this alert") - end - end - - describe '#closed_alert_issue' do - let_it_be(:issue) { noteable.issue } - - subject { described_class.new(noteable: noteable, project: project, author: author).closed_alert_issue(issue) } - - it_behaves_like 'a system note' do - let(:action) { 'status' } - end - - it 'has the appropriate message' do - expect(subject.note).to eq("changed the status to **Resolved** by closing issue #{issue.to_reference(project)}") + expect(subject.note).to eq("created incident #{issue.to_reference(project)} for this alert") end end diff --git a/spec/services/system_notes/incident_service_spec.rb b/spec/services/system_notes/incident_service_spec.rb index d1addaa3b8d..5de352ad8fa 100644 --- a/spec/services/system_notes/incident_service_spec.rb +++ b/spec/services/system_notes/incident_service_spec.rb @@ -57,16 +57,6 @@ RSpec.describe ::SystemNotes::IncidentService do end end - describe '#resolve_incident_status' do - subject(:resolve_incident_status) { described_class.new(noteable: noteable, project: project, author: author).resolve_incident_status } - - it 'creates a new note about resolved incident', :aggregate_failures do - expect { resolve_incident_status }.to change { noteable.notes.count }.by(1) - - expect(noteable.notes.last.note).to eq('changed the status to **Resolved** by closing the incident') - end - end - describe '#change_incident_status' do let_it_be(:escalation_status) { create(:incident_management_issuable_escalation_status, issue: noteable) } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6d5036365e1..17f091278e5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -462,14 +462,6 @@ RSpec.configure do |config| $stdout = STDOUT end - config.around(:each, stubbing_settings_source: true) do |example| - original_instance = ::Settings.instance_variable_get(:@instance) - - example.run - - ::Settings.instance_variable_set(:@instance, original_instance) - end - config.disable_monkey_patching! end diff --git a/spec/support/stub_settings_source.rb b/spec/support/stub_settings_source.rb new file mode 100644 index 00000000000..c0e4e468b90 --- /dev/null +++ b/spec/support/stub_settings_source.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +RSpec.configure do |config| + config.around(:each, stub_settings_source: true) do |example| + original_instance = ::Settings.instance_variable_get(:@instance) + + example.run + + ::Settings.instance_variable_set(:@instance, original_instance) + end +end