Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-01-25 18:12:32 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-01-25 18:12:32 +0300
commit7d8d5a3dab415672a41ab29c3bfa9581f275dc50 (patch)
tree7b9249d8ca8c12ad899b4e6d968193d58e63f458
parent868c8c35fbddd439f4df76a5954e2a1caa2af3cc (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/issues/show/components/description.vue118
-rw-r--r--app/assets/javascripts/sidebar/graphql.js3
-rw-r--r--app/assets/javascripts/work_items/pages/create_work_item.vue46
-rw-r--r--app/assets/stylesheets/pages/issues.scss29
-rw-r--r--app/controllers/projects/issues_controller.rb1
-rw-r--r--app/controllers/registrations_controller.rb9
-rw-r--r--app/graphql/types/ci/runner_status_enum.rb12
-rw-r--r--app/graphql/types/ci/runner_type.rb5
-rw-r--r--app/models/ci/namespace_mirror.rb4
-rw-r--r--app/models/ci/pipeline.rb6
-rw-r--r--app/models/ci/runner.rb2
-rw-r--r--app/models/project.rb4
-rw-r--r--app/presenters/ci/runner_presenter.rb4
-rw-r--r--app/services/groups/create_service.rb11
-rw-r--r--app/views/layouts/_page.html.haml1
-rw-r--r--app/views/layouts/header/_default.html.haml6
-rw-r--r--config/feature_flags/development/ci_pipeline_merge_request_presence_check.yml8
-rw-r--r--config/metrics/counts_7d/20211102202454_p_ci_templates_security_sast_iac_latest_weekly.yml1
-rw-r--r--db/post_migrate/20211209103048_backfill_project_namespaces_for_group.rb42
-rw-r--r--db/post_migrate/20220119151221_remove_merge_requests_ci_pipelines_merge_request_id_fk.rb19
-rw-r--r--db/post_migrate/20220121221651_remove_projects_ci_variables_project_id_fk.rb19
-rw-r--r--db/post_migrate/20220124204046_remove_projects_ci_sources_pipelines_project_id_fk.rb19
-rw-r--r--db/post_migrate/20220124214131_remove_projects_ci_refs_project_id_fk.rb19
-rw-r--r--db/schema_migrations/202112091030481
-rw-r--r--db/schema_migrations/202201191512211
-rw-r--r--db/schema_migrations/202201212216511
-rw-r--r--db/schema_migrations/202201242040461
-rw-r--r--db/schema_migrations/202201242141311
-rw-r--r--db/structure.sql12
-rw-r--r--doc/api/graphql/reference/index.md7
-rw-r--r--doc/api/settings.md1
-rw-r--r--doc/development/cicd/templates.md72
-rw-r--r--doc/development/service_ping/index.md4
-rw-r--r--doc/user/infrastructure/iac/terraform_state.md12
-rw-r--r--doc/user/search/advanced_search.md25
-rw-r--r--lib/gitlab/background_migration/batching_strategies/backfill_project_namespace_per_group_batching_strategy.rb38
-rw-r--r--lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy.rb3
-rw-r--r--lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces.rb33
-rw-r--r--lib/gitlab/database/background_migration/batched_migration_runner.rb3
-rw-r--r--lib/gitlab/database/gitlab_loose_foreign_keys.yml14
-rw-r--r--lib/gitlab/subscription_portal.rb4
-rw-r--r--locale/gitlab.pot50
-rwxr-xr-xscripts/decomposition/generate-loose-foreign-key405
-rw-r--r--spec/controllers/registrations_controller_spec.rb22
-rw-r--r--spec/features/admin/admin_runners_spec.rb4
-rw-r--r--spec/features/issues/filtered_search/dropdown_base_spec.rb17
-rw-r--r--spec/features/issues/user_edits_issue_spec.rb2
-rw-r--r--spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js45
-rw-r--r--spec/frontend/admin/users/components/modals/delete_user_modal_spec.js19
-rw-r--r--spec/frontend/admin/users/components/modals/user_modal_manager_spec.js22
-rw-r--r--spec/frontend/alert_management/components/alert_management_table_spec.js3
-rw-r--r--spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js4
-rw-r--r--spec/frontend/analytics/shared/components/daterange_spec.js8
-rw-r--r--spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js31
-rw-r--r--spec/frontend/analytics/usage_trends/components/usage_trends_count_chart_spec.js4
-rw-r--r--spec/frontend/analytics/usage_trends/components/users_chart_spec.js14
-rw-r--r--spec/frontend/artifacts_settings/components/keep_latest_artifact_checkbox_spec.js6
-rw-r--r--spec/frontend/badges/components/badge_settings_spec.js4
-rw-r--r--spec/frontend/batch_comments/components/draft_note_spec.js58
-rw-r--r--spec/frontend/batch_comments/components/drafts_count_spec.js11
-rw-r--r--spec/frontend/batch_comments/components/publish_button_spec.js11
-rw-r--r--spec/frontend/blob/components/blob_edit_header_spec.js8
-rw-r--r--spec/frontend/blob/components/blob_header_spec.js15
-rw-r--r--spec/frontend/blob/components/blob_header_viewer_switcher_spec.js15
-rw-r--r--spec/frontend/boards/board_card_inner_spec.js7
-rw-r--r--spec/frontend/boards/board_list_spec.js17
-rw-r--r--spec/frontend/boards/components/board_add_new_column_trigger_spec.js4
-rw-r--r--spec/frontend/boards/components/board_blocked_icon_spec.js4
-rw-r--r--spec/frontend/boards/components/board_card_spec.js6
-rw-r--r--spec/frontend/boards/components/board_list_header_spec.js8
-rw-r--r--spec/frontend/boards/components/board_new_issue_spec.js8
-rw-r--r--spec/frontend/boards/components/board_new_item_spec.js7
-rw-r--r--spec/frontend/boards/components/board_settings_sidebar_spec.js6
-rw-r--r--spec/frontend/boards/components/sidebar/board_editable_item_spec.js20
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js11
-rw-r--r--spec/frontend/boards/project_select_spec.js4
-rw-r--r--spec/frontend/ci_lint/components/ci_lint_spec.js5
-rw-r--r--spec/frontend/ci_settings_pipeline_triggers/components/triggers_list_spec.js5
-rw-r--r--spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js6
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_table_spec.js11
-rw-r--r--spec/frontend/clusters/components/new_cluster_spec.js5
-rw-r--r--spec/frontend/clusters/components/remove_cluster_confirmation_spec.js5
-rw-r--r--spec/frontend/clusters/forms/components/integration_form_spec.js40
-rw-r--r--spec/frontend/clusters_list/components/agent_options_spec.js6
-rw-r--r--spec/frontend/clusters_list/components/agents_spec.js15
-rw-r--r--spec/frontend/clusters_list/components/ancestor_notice_spec.js13
-rw-r--r--spec/frontend/clusters_list/components/clusters_spec.js5
-rw-r--r--spec/frontend/clusters_list/components/node_error_help_text_spec.js5
-rw-r--r--spec/frontend/commit/commit_pipeline_status_component_spec.js12
-rw-r--r--spec/frontend/commit/pipelines/pipelines_table_spec.js3
-rw-r--r--spec/frontend/contributors/component/contributors_spec.js20
-rw-r--r--spec/frontend/create_cluster/components/cluster_form_dropdown_spec.js134
-rw-r--r--spec/frontend/create_cluster/eks_cluster/components/service_credentials_form_spec.js13
-rw-r--r--spec/frontend/create_cluster/gke_cluster/components/gke_machine_type_dropdown_spec.js16
-rw-r--r--spec/frontend/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js36
-rw-r--r--spec/frontend/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js24
-rw-r--r--spec/frontend/cycle_analytics/filter_bar_spec.js4
-rw-r--r--spec/frontend/cycle_analytics/stage_table_spec.js3
-rw-r--r--spec/frontend/cycle_analytics/value_stream_metrics_spec.js5
-rw-r--r--spec/frontend/deploy_freeze/components/deploy_freeze_table_spec.js6
-rw-r--r--spec/frontend/deploy_keys/components/action_btn_spec.js15
-rw-r--r--spec/frontend/deploy_keys/components/app_spec.js114
-rw-r--r--spec/frontend/deploy_keys/components/key_spec.js14
-rw-r--r--spec/frontend/design_management/components/delete_button_spec.js18
-rw-r--r--spec/frontend/design_management/components/design_notes/design_discussion_spec.js64
-rw-r--r--spec/frontend/design_management/components/design_notes/design_note_spec.js33
-rw-r--r--spec/frontend/design_management/components/design_notes/design_reply_form_spec.js61
-rw-r--r--spec/frontend/design_management/components/design_overlay_spec.js149
-rw-r--r--spec/frontend/design_management/components/design_presentation_spec.js117
-rw-r--r--spec/frontend/design_management/components/design_scaler_spec.js9
-rw-r--r--spec/frontend/design_management/components/design_sidebar_spec.js22
-rw-r--r--spec/frontend/design_management/components/design_todo_button_spec.js9
-rw-r--r--spec/frontend/design_management/components/image_spec.js46
-rw-r--r--spec/frontend/design_management/components/list/item_spec.js30
-rw-r--r--spec/frontend/design_management/components/toolbar/design_navigation_spec.js8
-rw-r--r--spec/frontend/design_management/components/toolbar/index_spec.js56
-rw-r--r--spec/frontend/design_management/components/upload/design_version_dropdown_spec.js59
-rw-r--r--spec/frontend/design_management/pages/design/index_spec.js59
-rw-r--r--spec/frontend/diffs/components/settings_dropdown_spec.js7
-rw-r--r--spec/frontend/filtered_search/recent_searches_root_spec.js5
-rw-r--r--spec/frontend/groups/components/app_spec.js64
-rw-r--r--spec/frontend/groups/components/groups_spec.js26
-rw-r--r--spec/frontend/ide/components/activity_bar_spec.js11
-rw-r--r--spec/frontend/ide/components/commit_sidebar/actions_spec.js29
-rw-r--r--spec/frontend/ide/components/commit_sidebar/list_item_spec.js31
-rw-r--r--spec/frontend/ide/components/commit_sidebar/message_field_spec.js138
-rw-r--r--spec/frontend/ide/components/commit_sidebar/new_merge_request_option_spec.js52
-rw-r--r--spec/frontend/ide/components/file_row_extra_spec.js74
-rw-r--r--spec/frontend/ide/components/file_templates/bar_spec.js28
-rw-r--r--spec/frontend/ide/components/ide_status_bar_spec.js14
-rw-r--r--spec/frontend/ide/components/ide_tree_list_spec.js13
-rw-r--r--spec/frontend/ide/components/jobs/detail_spec.js29
-rw-r--r--spec/frontend/ide/components/jobs/item_spec.js11
-rw-r--r--spec/frontend/ide/components/nav_dropdown_button_spec.js32
-rw-r--r--spec/frontend/ide/components/nav_dropdown_spec.js23
-rw-r--r--spec/frontend/ide/components/new_dropdown/button_spec.js20
-rw-r--r--spec/frontend/ide/components/new_dropdown/modal_spec.js17
-rw-r--r--spec/frontend/ide/components/repo_editor_spec.js28
-rw-r--r--spec/frontend/ide/components/shared/tokened_input_spec.js12
-rw-r--r--spec/frontend/issues/show/components/description_spec.js270
-rw-r--r--spec/frontend/issues/show/mock_data/mock_data.js14
-rw-r--r--spec/frontend/notebook/cells/code_spec.js10
-rw-r--r--spec/frontend/notebook/cells/markdown_spec.js71
-rw-r--r--spec/frontend/popovers/index_spec.js17
-rw-r--r--spec/frontend/prometheus_alerts/components/reset_key_spec.js44
-rw-r--r--spec/frontend/repository/components/last_commit_spec.js25
-rw-r--r--spec/frontend/repository/components/preview/index_spec.js25
-rw-r--r--spec/frontend/repository/components/table/index_spec.js14
-rw-r--r--spec/frontend/repository/components/table/row_spec.js84
-rw-r--r--spec/frontend/repository/components/tree_content_spec.js13
-rw-r--r--spec/frontend/serverless/components/functions_spec.js11
-rw-r--r--spec/frontend/snippets/components/snippet_blob_view_spec.js38
-rw-r--r--spec/frontend/tooltips/index_spec.js15
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js5
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js3
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js5
-rw-r--r--spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js10
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js8
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js32
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js5
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js5
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js14
-rw-r--r--spec/frontend/vue_shared/alert_details/alert_details_spec.js10
-rw-r--r--spec/frontend/vue_shared/alert_details/alert_management_sidebar_todo_spec.js9
-rw-r--r--spec/frontend/vue_shared/alert_details/alert_metrics_spec.js3
-rw-r--r--spec/frontend/vue_shared/alert_details/alert_status_spec.js3
-rw-r--r--spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_assignees_spec.js21
-rw-r--r--spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js12
-rw-r--r--spec/frontend/vue_shared/components/chronic_duration_input_spec.js25
-rw-r--r--spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js258
-rw-r--r--spec/frontend/vue_shared/components/deployment_instance/deployment_instance_spec.js29
-rw-r--r--spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js70
-rw-r--r--spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js26
-rw-r--r--spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/dropdown/dropdown_search_input_spec.js8
-rw-r--r--spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/dropdown_keyboard_navigation_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/file_finder/index_spec.js26
-rw-r--r--spec/frontend/vue_shared/components/file_finder/item_spec.js53
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js33
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js26
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js7
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/release_token_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/gl_modal_vuex_spec.js24
-rw-r--r--spec/frontend/vue_shared/components/local_storage_sync_spec.js27
-rw-r--r--spec/frontend/vue_shared/components/markdown/field_spec.js77
-rw-r--r--spec/frontend/vue_shared/components/markdown/header_spec.js19
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/modal_copy_button_spec.js21
-rw-r--r--spec/frontend/vue_shared/components/notes/noteable_warning_spec.js15
-rw-r--r--spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js15
-rw-r--r--spec/frontend/vue_shared/components/project_avatar/default_spec.js30
-rw-r--r--spec/frontend/vue_shared/components/project_selector/project_selector_spec.js46
-rw-r--r--spec/frontend/vue_shared/components/registry/list_item_spec.js7
-rw-r--r--spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/sidebar/date_picker_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js23
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js65
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js68
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js33
-rw-r--r--spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/split_button_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js42
-rw-r--r--spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/web_ide_link_spec.js4
-rw-r--r--spec/frontend/vue_shared/directives/track_event_spec.js11
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar_spec.js5
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js21
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js15
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_body_spec.js11
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js5
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js5
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js5
-rw-r--r--spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js3
-rw-r--r--spec/frontend/vue_shared/new_namespace/components/welcome_spec.js5
-rw-r--r--spec/frontend/work_items/pages/create_work_item_spec.js90
-rw-r--r--spec/frontend_integration/ide/ide_integration_spec.js7
-rw-r--r--spec/graphql/types/ci/runner_type_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/batching_strategies/backfill_project_namespace_per_group_batching_strategy_spec.rb53
-rw-r--r--spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb8
-rw-r--r--spec/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces_spec.rb28
-rw-r--r--spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb4
-rw-r--r--spec/lib/gitlab/subscription_portal_spec.rb1
-rw-r--r--spec/migrations/backfill_project_namespaces_for_group_spec.rb43
-rw-r--r--spec/models/ci/namespace_mirror_spec.rb4
-rw-r--r--spec/models/ci/pipeline_spec.rb72
-rw-r--r--spec/models/ci/ref_spec.rb7
-rw-r--r--spec/models/ci/sources/pipeline_spec.rb7
-rw-r--r--spec/models/ci/variable_spec.rb7
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb1
-rw-r--r--spec/services/groups/create_service_spec.rb29
240 files changed, 3341 insertions, 2532 deletions
diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue
index 7be4c13f544..3f42f825866 100644
--- a/app/assets/javascripts/issues/show/components/description.vue
+++ b/app/assets/javascripts/issues/show/components/description.vue
@@ -1,18 +1,31 @@
<script>
-import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import {
+ GlSafeHtmlDirective as SafeHtml,
+ GlModal,
+ GlModalDirective,
+ GlPopover,
+ GlButton,
+} from '@gitlab/ui';
import $ from 'jquery';
import createFlash from '~/flash';
import { __, sprintf } from '~/locale';
import TaskList from '~/task_list';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import CreateWorkItem from '~/work_items/pages/create_work_item.vue';
import animateMixin from '../mixins/animate';
export default {
directives: {
SafeHtml,
+ GlModal: GlModalDirective,
},
-
- mixins: [animateMixin],
-
+ components: {
+ GlModal,
+ GlPopover,
+ CreateWorkItem,
+ GlButton,
+ },
+ mixins: [animateMixin, glFeatureFlagMixin()],
props: {
canUpdate: {
type: Boolean,
@@ -53,8 +66,15 @@ export default {
preAnimation: false,
pulseAnimation: false,
initialUpdate: true,
+ taskButtons: [],
+ activeTask: {},
};
},
+ computed: {
+ workItemsEnabled() {
+ return this.glFeatures.workItems;
+ },
+ },
watch: {
descriptionHtml(newDescription, oldDescription) {
if (!this.initialUpdate && newDescription !== oldDescription) {
@@ -74,6 +94,10 @@ export default {
mounted() {
this.renderGFM();
this.updateTaskStatusText();
+
+ if (this.workItemsEnabled) {
+ this.renderTaskActions();
+ }
},
methods: {
renderGFM() {
@@ -132,6 +156,55 @@ export default {
$tasksShort.text('');
}
},
+ renderTaskActions() {
+ const taskListFields = this.$el.querySelectorAll('.task-list-item');
+ taskListFields.forEach((item, index) => {
+ const button = document.createElement('button');
+ button.classList.add(
+ 'btn',
+ 'btn-default',
+ 'btn-md',
+ 'gl-button',
+ 'btn-default-tertiary',
+ 'gl-left-0',
+ 'gl-p-0!',
+ 'gl-top-2',
+ 'gl-absolute',
+ 'js-add-task',
+ );
+ button.id = `js-task-button-${index}`;
+ this.taskButtons.push(button.id);
+ button.innerHTML =
+ '<svg data-testid="ellipsis_v-icon" role="img" aria-hidden="true" class="dropdown-icon gl-icon s14"><use href="/assets/icons-7f1680a3670112fe4c8ef57b9dfb93f0f61b43a2a479d7abd6c83bcb724b9201.svg#ellipsis_v"></use></svg>';
+ item.prepend(button);
+ });
+ },
+ openCreateTaskModal(id) {
+ this.activeTask = { id, title: this.$el.querySelector(`#${id}`).parentElement.innerText };
+ this.$refs.modal.show();
+ },
+ closeCreateTaskModal() {
+ this.$refs.modal.hide();
+ },
+ handleCreateTask(title) {
+ const listItem = this.$el.querySelector(`#${this.activeTask.id}`).parentElement;
+ const taskBadge = document.createElement('span');
+ taskBadge.innerHTML = `
+ <svg data-testid="issue-open-m-icon" role="img" aria-hidden="true" class="gl-icon gl-fill-green-500 s12">
+ <use href="/assets/icons-7f1680a3670112fe4c8ef57b9dfb93f0f61b43a2a479d7abd6c83bcb724b9201.svg#issue-open-m"></use>
+ </svg>
+ <span class="badge badge-info badge-pill gl-badge sm gl-mr-1">
+ ${__('Task')}
+ </span>
+ <a href="#">${title}</a>
+ `;
+ listItem.insertBefore(taskBadge, listItem.lastChild);
+ listItem.removeChild(listItem.lastChild);
+ this.closeCreateTaskModal();
+ },
+ focusButton() {
+ this.$refs.convertButton[0].$el.focus();
+ },
},
safeHtmlConfig: { ADD_TAGS: ['gl-emoji', 'copy-code'] },
};
@@ -142,12 +215,14 @@ export default {
v-if="descriptionHtml"
:class="{
'js-task-list-container': canUpdate,
+ 'work-items-enabled': workItemsEnabled,
}"
class="description"
>
<div
ref="gfm-content"
v-safe-html:[$options.safeHtmlConfig]="descriptionHtml"
+ data-testid="gfm-content"
:class="{
'issue-realtime-pre-pulse': preAnimation,
'issue-realtime-trigger-pulse': pulseAnimation,
@@ -157,13 +232,46 @@ export default {
<!-- eslint-disable vue/no-mutating-props -->
<textarea
v-if="descriptionText"
- ref="textarea"
v-model="descriptionText"
:data-update-url="updateUrl"
class="hidden js-task-list-field"
dir="auto"
+ data-testid="textarea"
>
</textarea>
<!-- eslint-enable vue/no-mutating-props -->
+ <gl-modal
+ ref="modal"
+ modal-id="create-task-modal"
+ :title="s__('WorkItem|New Task')"
+ hide-footer
+ body-class="gl-py-0!"
+ >
+ <create-work-item
+ :is-modal="true"
+ :initial-title="activeTask.title"
+ @closeModal="closeCreateTaskModal"
+ @onCreate="handleCreateTask"
+ />
+ </gl-modal>
+ <template v-if="workItemsEnabled">
+ <gl-popover
+ v-for="item in taskButtons"
+ :key="item"
+ :target="item"
+ placement="top"
+ triggers="focus"
+ @shown="focusButton"
+ >
+ <gl-button
+ ref="convertButton"
+ variant="link"
+ data-testid="convert-to-task"
+ class="gl-text-gray-900! gl-text-decoration-none! gl-outline-0!"
+ @click="openCreateTaskModal(item)"
+ >{{ s__('WorkItem|Convert to work item') }}</gl-button
+ >
+ </gl-popover>
+ </template>
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/graphql.js b/app/assets/javascripts/sidebar/graphql.js
index 5b2ce3fe446..c5d94cfa5e8 100644
--- a/app/assets/javascripts/sidebar/graphql.js
+++ b/app/assets/javascripts/sidebar/graphql.js
@@ -2,6 +2,7 @@ import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import produce from 'immer';
import VueApollo from 'vue-apollo';
import getIssueStateQuery from '~/issues/show/queries/get_issue_state.query.graphql';
+import { resolvers as workItemResolvers } from '~/work_items/graphql/resolvers';
import createDefaultClient from '~/lib/graphql';
import introspectionQueryResultData from './fragmentTypes.json';
@@ -10,6 +11,7 @@ const fragmentMatcher = new IntrospectionFragmentMatcher({
});
const resolvers = {
+ ...workItemResolvers,
Mutation: {
updateIssueState: (_, { issueType = undefined, isDirty = false }, { cache }) => {
const sourceData = cache.readQuery({ query: getIssueStateQuery });
@@ -18,6 +20,7 @@ const resolvers = {
});
cache.writeQuery({ query: getIssueStateQuery, data });
},
+ ...workItemResolvers.Mutation,
},
};
diff --git a/app/assets/javascripts/work_items/pages/create_work_item.vue b/app/assets/javascripts/work_items/pages/create_work_item.vue
index 12bad5606d4..2b9db3e3db5 100644
--- a/app/assets/javascripts/work_items/pages/create_work_item.vue
+++ b/app/assets/javascripts/work_items/pages/create_work_item.vue
@@ -10,9 +10,21 @@ export default {
GlAlert,
ItemTitle,
},
+ props: {
+ isModal: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ initialTitle: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
data() {
return {
- title: '',
+ title: this.initialTitle,
error: false,
};
},
@@ -35,7 +47,11 @@ export default {
},
},
} = response;
- this.$router.push({ name: 'workItem', params: { id } });
+ if (!this.isModal) {
+ this.$router.push({ name: 'workItem', params: { id } });
+ } else {
+ this.$emit('onCreate', this.title);
+ }
} catch {
this.error = true;
}
@@ -43,6 +59,13 @@ export default {
handleTitleInput(title) {
this.title = title;
},
+ handleCancelClick() {
+ if (!this.isModal) {
+ this.$router.go(-1);
+ return;
+ }
+ this.$emit('closeModal');
+ },
},
};
</script>
@@ -52,18 +75,27 @@ export default {
<gl-alert v-if="error" variant="danger" @dismiss="error = false">{{
__('Something went wrong when creating a work item. Please try again')
}}</gl-alert>
- <item-title data-testid="title-input" @title-input="handleTitleInput" />
- <div class="gl-bg-gray-10 gl-py-5 gl-px-6">
+ <item-title :initial-title="title" data-testid="title-input" @title-input="handleTitleInput" />
+ <div
+ class="gl-bg-gray-10 gl-py-5 gl-px-6"
+ :class="{ 'gl-display-flex gl-justify-content-end': isModal }"
+ >
<gl-button
variant="confirm"
:disabled="title.length === 0"
- class="gl-mr-3"
+ :class="{ 'gl-mr-3': !isModal }"
data-testid="create-button"
type="submit"
>
- {{ __('Create') }}
+ {{ s__('WorkItem|Create work item') }}
</gl-button>
- <gl-button type="button" data-testid="cancel-button" @click="$router.go(-1)">
+ <gl-button
+ type="button"
+ data-testid="cancel-button"
+ class="gl-order-n1"
+ :class="{ 'gl-mr-3': isModal }"
+ @click="handleCancelClick"
+ >
{{ __('Cancel') }}
</gl-button>
</div>
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index d77c8a40a79..6411d5e1000 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -305,3 +305,32 @@ ul.related-merge-requests > li gl-emoji {
.issuable-header-slide-leave-to {
transform: translateY(-100%);
}
+
+.description.work-items-enabled {
+ ul.task-list {
+ > li.task-list-item {
+ padding-inline-start: 2.25rem;
+
+ .js-add-task {
+ svg {
+ visibility: hidden;
+ }
+
+ &:focus svg {
+ visibility: visible;
+ }
+ }
+
+ > input.task-list-item-checkbox {
+ left: 0.875rem;
+ }
+
+ &:hover,
+ &:focus-within {
+ .js-add-task svg {
+ visibility: visible;
+ }
+ }
+ }
+ }
+}
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 785fbdaa611..8b5e9fa8bb9 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -54,6 +54,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:issue_assignees_widget, @project, default_enabled: :yaml)
push_frontend_feature_flag(:paginated_issue_discussions, @project, default_enabled: :yaml)
push_frontend_feature_flag(:fix_comment_scroll, @project, default_enabled: :yaml)
+ push_frontend_feature_flag(:work_items, project, default_enabled: :yaml)
end
around_action :allow_gitaly_ref_name_caching, only: [:discussions]
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index c1765d367d1..7b688c0ccc2 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -35,6 +35,7 @@ class RegistrationsController < Devise::RegistrationsController
persist_accepted_terms_if_required(new_user)
set_role_required(new_user)
+ track_experiment_event(new_user)
if pending_approval?
NotificationService.new.new_instance_access_request(new_user)
@@ -223,6 +224,14 @@ class RegistrationsController < Devise::RegistrationsController
def context_user
current_user
end
+
+ def track_experiment_event(new_user)
+ # Track signed up event to relate it with click "Sign up" button events from
+ # the experimental logged out header with marketing links. This allows us to
+ # have a funnel of visitors clicking on the header and those visitors
+ # signing up and becoming users
+ experiment(:logged_out_marketing_header, actor: new_user).track(:signed_up) if new_user.persisted?
+ end
end
RegistrationsController.prepend_mod_with('RegistrationsController')
diff --git a/app/graphql/types/ci/runner_status_enum.rb b/app/graphql/types/ci/runner_status_enum.rb
index dd056191ceb..2e65e2d4e1e 100644
--- a/app/graphql/types/ci/runner_status_enum.rb
+++ b/app/graphql/types/ci/runner_status_enum.rb
@@ -7,12 +7,20 @@ module Types
value 'ACTIVE',
description: 'Runner that is not paused.',
- deprecated: { reason: 'Use CiRunnerType.active instead', milestone: '14.6' },
+ deprecated: {
+ reason: :renamed,
+ replacement: 'CiRunner.paused',
+ milestone: '14.6'
+ },
value: :active
value 'PAUSED',
description: 'Runner that is paused.',
- deprecated: { reason: 'Use CiRunnerType.active instead', milestone: '14.6' },
+ deprecated: {
+ reason: :renamed,
+ replacement: 'CiRunner.paused',
+ milestone: '14.6'
+ },
value: :paused
value 'ONLINE',
diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb
index f4a3379c5ca..e3f04ec5814 100644
--- a/app/graphql/types/ci/runner_type.rb
+++ b/app/graphql/types/ci/runner_type.rb
@@ -29,7 +29,10 @@ module Types
field :access_level, ::Types::Ci::RunnerAccessLevelEnum, null: false,
description: 'Access level of the runner.'
field :active, GraphQL::Types::Boolean, null: false,
- description: 'Indicates the runner is allowed to receive jobs.'
+ description: 'Indicates the runner is allowed to receive jobs.',
+ deprecated: { reason: 'Use paused', milestone: '14.8' }
+ field :paused, GraphQL::Types::Boolean, null: false,
+ description: 'Indicates the runner is paused and not available to run jobs.'
field :status,
Types::Ci::RunnerStatusEnum,
null: false,
diff --git a/app/models/ci/namespace_mirror.rb b/app/models/ci/namespace_mirror.rb
index ce3faf3546b..d5cbbb96134 100644
--- a/app/models/ci/namespace_mirror.rb
+++ b/app/models/ci/namespace_mirror.rb
@@ -6,7 +6,7 @@ module Ci
class NamespaceMirror < ApplicationRecord
belongs_to :namespace
- scope :contains_namespace, -> (id) do
+ scope :by_group_and_descendants, -> (id) do
where('traversal_ids @> ARRAY[?]::int[]', id)
end
@@ -32,7 +32,7 @@ module Ci
private
def sync_children_namespaces!(namespace_id, traversal_ids)
- contains_namespace(namespace_id)
+ by_group_and_descendants(namespace_id)
.where.not(namespace_id: namespace_id)
.update_all(
"traversal_ids = ARRAY[#{sanitize_sql(traversal_ids.join(','))}]::int[] || traversal_ids[array_position(traversal_ids, #{sanitize_sql(namespace_id)}) + 1:]"
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 0f09b1b8996..032959c385e 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -1163,7 +1163,11 @@ module Ci
end
def merge_request?
- merge_request_id.present?
+ if Feature.enabled?(:ci_pipeline_merge_request_presence_check, default_enabled: :yaml)
+ merge_request_id.present? && merge_request
+ else
+ merge_request_id.present?
+ end
end
def external_pull_request?
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index e7bc6bffcaf..fc13ebec7c2 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -101,7 +101,7 @@ module Ci
}
scope :belonging_to_group_or_project_descendants, -> (group_id) {
- group_ids = Ci::NamespaceMirror.contains_namespace(group_id).select(:namespace_id)
+ group_ids = Ci::NamespaceMirror.by_group_and_descendants(group_id).select(:namespace_id)
project_ids = Ci::ProjectMirror.by_namespace_id(group_ids).select(:project_id)
group_runners = joins(:runner_namespaces).where(ci_runner_namespaces: { namespace_id: group_ids })
diff --git a/app/models/project.rb b/app/models/project.rb
index 1070bb6db4f..27ec475cc8b 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1519,6 +1519,10 @@ class Project < ApplicationRecord
group || namespace.try(:owner)
end
+ def owners
+ Array.wrap(owner)
+ end
+
def first_owner
obj = owner
diff --git a/app/presenters/ci/runner_presenter.rb b/app/presenters/ci/runner_presenter.rb
index ffd826fab64..482534f27b9 100644
--- a/app/presenters/ci/runner_presenter.rb
+++ b/app/presenters/ci/runner_presenter.rb
@@ -15,5 +15,9 @@ module Ci
def executor_name
Ci::Runner::EXECUTOR_TYPE_TO_NAMES[executor_type&.to_sym]
end
+
+ def paused
+ !active
+ end
end
end
diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb
index da3cebc2e6d..67cbbaf84f6 100644
--- a/app/services/groups/create_service.rb
+++ b/app/services/groups/create_service.rb
@@ -61,6 +61,8 @@ module Groups
delay = Namespaces::InviteTeamEmailService::DELIVERY_DELAY_IN_MINUTES
Namespaces::InviteTeamEmailWorker.perform_in(delay, group.id, current_user.id)
end
+
+ track_experiment_event
end
def remove_unallowed_params
@@ -112,6 +114,15 @@ module Groups
@group.shared_runners_enabled = @group.parent.shared_runners_enabled
@group.allow_descendants_override_disabled_shared_runners = @group.parent.allow_descendants_override_disabled_shared_runners
end
+
+ def track_experiment_event
+ return unless group.persisted?
+
+ # Track namespace created events to relate them with signed up events for
+ # the same experiment. This will let us associate created namespaces to
+ # users that signed up from the experimental logged out header.
+ experiment(:logged_out_marketing_header, actor: current_user).track(:namespace_created, namespace: group)
+ end
end
end
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index d6557772241..b7299df1bc1 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -16,6 +16,7 @@
= render "shared/service_ping_consent"
= render_two_factor_auth_recovery_settings_check
= render_if_exists "layouts/header/ee_subscribable_banner"
+ = render_if_exists "layouts/header/seats_count_alert"
= render_if_exists "shared/namespace_storage_limit_alert"
= render_if_exists "shared/namespace_user_cap_reached_alert"
= render_if_exists "shared/new_user_signups_cap_reached_alert"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index d0a06c7d5bf..246a31f86c9 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -139,15 +139,15 @@
- experiment(:logged_out_marketing_header, actor: nil) do |e|
- e.candidate do
%li.nav-item.gl-display-none.gl-sm-display-block
- = link_to _('Sign up now'), new_user_registration_path, class: 'gl-button btn btn-default btn-sign-in'
+ = link_to _('Sign up now'), new_user_registration_path, class: 'gl-button btn btn-default btn-sign-in', data: { track_action: 'click_button', track_experiment: e.name, track_label: 'sign_up_now' }
%li.nav-item.gl-display-none.gl-sm-display-block
= link_to _('Login'), new_session_path(:user, redirect_to_referer: 'yes')
= render 'layouts/header/sign_in_register_button', class: 'gl-sm-display-none'
- e.try(:trial_focused) do
%li.nav-item.gl-display-none.gl-sm-display-block
- = link_to _('Get a free trial'), 'https://about.gitlab.com/free-trial/', class: 'gl-button btn btn-default btn-sign-in'
+ = link_to _('Get a free trial'), 'https://about.gitlab.com/free-trial/', class: 'gl-button btn btn-default btn-sign-in', data: { track_action: 'click_button', track_experiment: e.name, track_label: 'get_a_free_trial' }
%li.nav-item.gl-display-none.gl-sm-display-block
- = link_to _('Sign up'), new_user_registration_path
+ = link_to _('Sign up'), new_user_registration_path, data: { track_action: 'click_button', track_experiment: e.name, track_label: 'sign_up' }
%li.nav-item.gl-display-none.gl-sm-display-block
= link_to _('Login'), new_session_path(:user, redirect_to_referer: 'yes')
= render 'layouts/header/sign_in_register_button', class: 'gl-sm-display-none'
diff --git a/config/feature_flags/development/ci_pipeline_merge_request_presence_check.yml b/config/feature_flags/development/ci_pipeline_merge_request_presence_check.yml
new file mode 100644
index 00000000000..19f674aa27c
--- /dev/null
+++ b/config/feature_flags/development/ci_pipeline_merge_request_presence_check.yml
@@ -0,0 +1,8 @@
+---
+name: ci_pipeline_merge_request_presence_check
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78574
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350734
+milestone: '14.8'
+type: development
+group: group::pipeline execution
+default_enabled: true
diff --git a/config/metrics/counts_7d/20211102202454_p_ci_templates_security_sast_iac_latest_weekly.yml b/config/metrics/counts_7d/20211102202454_p_ci_templates_security_sast_iac_latest_weekly.yml
index e7e2c096902..4fb9a64a234 100644
--- a/config/metrics/counts_7d/20211102202454_p_ci_templates_security_sast_iac_latest_weekly.yml
+++ b/config/metrics/counts_7d/20211102202454_p_ci_templates_security_sast_iac_latest_weekly.yml
@@ -12,7 +12,6 @@ milestone: "14.5"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73076
time_frame: 7d
data_source: redis_hll
-data_category: optional
instrumentation_class: RedisHLLMetric
performance_indicator_type: []
distribution:
diff --git a/db/post_migrate/20211209103048_backfill_project_namespaces_for_group.rb b/db/post_migrate/20211209103048_backfill_project_namespaces_for_group.rb
new file mode 100644
index 00000000000..0b65db7aab4
--- /dev/null
+++ b/db/post_migrate/20211209103048_backfill_project_namespaces_for_group.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+class BackfillProjectNamespacesForGroup < Gitlab::Database::Migration[1.0]
+ MIGRATION = 'ProjectNamespaces::BackfillProjectNamespaces'
+ DELAY_INTERVAL = 2.minutes
+ GROUP_ID = 9970 # picking gitlab-org group.
+
+ disable_ddl_transaction!
+
+ def up
+ return unless Gitlab.com? || Gitlab.staging?
+
+ projects_table = ::Gitlab::BackgroundMigration::ProjectNamespaces::Models::Project.arel_table
+ hierarchy_cte_sql = Arel.sql(::Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNamespaces.hierarchy_cte(GROUP_ID))
+ group_projects = ::Gitlab::BackgroundMigration::ProjectNamespaces::Models::Project.where(projects_table[:namespace_id].in(hierarchy_cte_sql))
+
+ min_id = group_projects.minimum(:id)
+ max_id = group_projects.maximum(:id)
+
+ return if min_id.blank? || max_id.blank?
+
+ queue_batched_background_migration(
+ MIGRATION,
+ :projects,
+ :id,
+ GROUP_ID,
+ 'up',
+ job_interval: DELAY_INTERVAL,
+ batch_min_value: min_id,
+ batch_max_value: max_id,
+ sub_batch_size: 25,
+ batch_class_name: 'BackfillProjectNamespacePerGroupBatchingStrategy'
+ )
+ end
+
+ def down
+ return unless Gitlab.com? || Gitlab.staging?
+
+ Gitlab::Database::BackgroundMigration::BatchedMigration
+ .for_configuration(MIGRATION, :projects, :id, [GROUP_ID, 'up']).delete_all
+ end
+end
diff --git a/db/post_migrate/20220119151221_remove_merge_requests_ci_pipelines_merge_request_id_fk.rb b/db/post_migrate/20220119151221_remove_merge_requests_ci_pipelines_merge_request_id_fk.rb
new file mode 100644
index 00000000000..c7ced0ae3b7
--- /dev/null
+++ b/db/post_migrate/20220119151221_remove_merge_requests_ci_pipelines_merge_request_id_fk.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RemoveMergeRequestsCiPipelinesMergeRequestIdFk < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ return unless foreign_key_exists?(:ci_pipelines, :merge_requests, name: "fk_a23be95014")
+
+ with_lock_retries do
+ execute('LOCK merge_requests, ci_pipelines IN ACCESS EXCLUSIVE MODE') if transaction_open?
+
+ remove_foreign_key_if_exists(:ci_pipelines, :merge_requests, name: "fk_a23be95014")
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key(:ci_pipelines, :merge_requests, name: "fk_a23be95014", column: :merge_request_id, target_column: :id, on_delete: :cascade)
+ end
+end
diff --git a/db/post_migrate/20220121221651_remove_projects_ci_variables_project_id_fk.rb b/db/post_migrate/20220121221651_remove_projects_ci_variables_project_id_fk.rb
new file mode 100644
index 00000000000..f647d4376ac
--- /dev/null
+++ b/db/post_migrate/20220121221651_remove_projects_ci_variables_project_id_fk.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RemoveProjectsCiVariablesProjectIdFk < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ return unless foreign_key_exists?(:ci_variables, :projects, name: "fk_ada5eb64b3")
+
+ with_lock_retries do
+ execute('LOCK projects, ci_variables IN ACCESS EXCLUSIVE MODE') if transaction_open?
+
+ remove_foreign_key_if_exists(:ci_variables, :projects, name: "fk_ada5eb64b3")
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key(:ci_variables, :projects, name: "fk_ada5eb64b3", column: :project_id, target_column: :id, on_delete: :cascade)
+ end
+end
diff --git a/db/post_migrate/20220124204046_remove_projects_ci_sources_pipelines_project_id_fk.rb b/db/post_migrate/20220124204046_remove_projects_ci_sources_pipelines_project_id_fk.rb
new file mode 100644
index 00000000000..e15d337045e
--- /dev/null
+++ b/db/post_migrate/20220124204046_remove_projects_ci_sources_pipelines_project_id_fk.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RemoveProjectsCiSourcesPipelinesProjectIdFk < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ return unless foreign_key_exists?(:ci_sources_pipelines, :projects, name: "fk_1e53c97c0a")
+
+ with_lock_retries do
+ execute('LOCK projects, ci_sources_pipelines IN ACCESS EXCLUSIVE MODE') if transaction_open?
+
+ remove_foreign_key_if_exists(:ci_sources_pipelines, :projects, name: "fk_1e53c97c0a")
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key(:ci_sources_pipelines, :projects, name: "fk_1e53c97c0a", column: :project_id, target_column: :id, on_delete: :cascade)
+ end
+end
diff --git a/db/post_migrate/20220124214131_remove_projects_ci_refs_project_id_fk.rb b/db/post_migrate/20220124214131_remove_projects_ci_refs_project_id_fk.rb
new file mode 100644
index 00000000000..f90d477673a
--- /dev/null
+++ b/db/post_migrate/20220124214131_remove_projects_ci_refs_project_id_fk.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class RemoveProjectsCiRefsProjectIdFk < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ return unless foreign_key_exists?(:ci_refs, :projects, name: "fk_rails_4249db8cc3")
+
+ with_lock_retries do
+ execute('LOCK projects, ci_refs IN ACCESS EXCLUSIVE MODE') if transaction_open?
+
+ remove_foreign_key_if_exists(:ci_refs, :projects, name: "fk_rails_4249db8cc3")
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key(:ci_refs, :projects, name: "fk_rails_4249db8cc3", column: :project_id, target_column: :id, on_delete: :cascade)
+ end
+end
diff --git a/db/schema_migrations/20211209103048 b/db/schema_migrations/20211209103048
new file mode 100644
index 00000000000..133d5fc1ed4
--- /dev/null
+++ b/db/schema_migrations/20211209103048
@@ -0,0 +1 @@
+d2d270a335b3a2441a20673bf19d47553f607533d4503e3a01bc3d6d108bcdb3 \ No newline at end of file
diff --git a/db/schema_migrations/20220119151221 b/db/schema_migrations/20220119151221
new file mode 100644
index 00000000000..377170d9cb1
--- /dev/null
+++ b/db/schema_migrations/20220119151221
@@ -0,0 +1 @@
+7865f26c43c79681f37ceb6e4fecf6153282856907ddfd8211d6d1d57d1fb7f3 \ No newline at end of file
diff --git a/db/schema_migrations/20220121221651 b/db/schema_migrations/20220121221651
new file mode 100644
index 00000000000..562751c6084
--- /dev/null
+++ b/db/schema_migrations/20220121221651
@@ -0,0 +1 @@
+a1681c1c619db7f4e7e5a760cee7d06a931aa1f02dccfce46be81d75a03ce8ac \ No newline at end of file
diff --git a/db/schema_migrations/20220124204046 b/db/schema_migrations/20220124204046
new file mode 100644
index 00000000000..f8de3adc215
--- /dev/null
+++ b/db/schema_migrations/20220124204046
@@ -0,0 +1 @@
+90115936ede32bbf9a299582409cb0686e8072c204c4f91364dfb13195155929 \ No newline at end of file
diff --git a/db/schema_migrations/20220124214131 b/db/schema_migrations/20220124214131
new file mode 100644
index 00000000000..7454bd8b3d1
--- /dev/null
+++ b/db/schema_migrations/20220124214131
@@ -0,0 +1 @@
+af60c6df0fb178a4820ea8cb40b402178da7fb4b6ebeabb8739dc45b96225f89 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 0c1cf010705..4e6592174cc 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -29240,9 +29240,6 @@ ALTER TABLE ONLY agent_project_authorizations
ALTER TABLE ONLY vulnerabilities
ADD CONSTRAINT fk_1d37cddf91 FOREIGN KEY (epic_id) REFERENCES epics(id) ON DELETE SET NULL;
-ALTER TABLE ONLY ci_sources_pipelines
- ADD CONSTRAINT fk_1e53c97c0a FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY boards
ADD CONSTRAINT fk_1e9a074a35 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
@@ -29657,9 +29654,6 @@ ALTER TABLE ONLY issues
ALTER TABLE ONLY ci_builds
ADD CONSTRAINT fk_a2141b1522 FOREIGN KEY (auto_canceled_by_id) REFERENCES ci_pipelines(id) ON DELETE SET NULL;
-ALTER TABLE ONLY ci_pipelines
- ADD CONSTRAINT fk_a23be95014 FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY bulk_import_entities
ADD CONSTRAINT fk_a44ff95be5 FOREIGN KEY (parent_id) REFERENCES bulk_import_entities(id) ON DELETE CASCADE;
@@ -29696,9 +29690,6 @@ ALTER TABLE ONLY member_tasks
ALTER TABLE ONLY merge_requests
ADD CONSTRAINT fk_ad525e1f87 FOREIGN KEY (merge_user_id) REFERENCES users(id) ON DELETE SET NULL;
-ALTER TABLE ONLY ci_variables
- ADD CONSTRAINT fk_ada5eb64b3 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY merge_request_metrics
ADD CONSTRAINT fk_ae440388cc FOREIGN KEY (latest_closed_by_id) REFERENCES users(id) ON DELETE SET NULL;
@@ -30419,9 +30410,6 @@ ALTER TABLE ONLY geo_node_namespace_links
ALTER TABLE ONLY epic_issues
ADD CONSTRAINT fk_rails_4209981af6 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
-ALTER TABLE ONLY ci_refs
- ADD CONSTRAINT fk_rails_4249db8cc3 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY ci_resources
ADD CONSTRAINT fk_rails_430336af2d FOREIGN KEY (resource_group_id) REFERENCES ci_resource_groups(id) ON DELETE CASCADE;
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 1b1adff7993..f2d713e9c97 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -9015,7 +9015,7 @@ Represents the total number of issues and their weights for a particular day.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="cirunneraccesslevel"></a>`accessLevel` | [`CiRunnerAccessLevel!`](#cirunneraccesslevel) | Access level of the runner. |
-| <a id="cirunneractive"></a>`active` | [`Boolean!`](#boolean) | Indicates the runner is allowed to receive jobs. |
+| <a id="cirunneractive"></a>`active` **{warning-solid}** | [`Boolean!`](#boolean) | **Deprecated** in 14.8. Use paused. |
| <a id="cirunneradminurl"></a>`adminUrl` | [`String`](#string) | Admin URL of the runner. Only available for administrators. |
| <a id="cirunnercontactedat"></a>`contactedAt` | [`Time`](#time) | Timestamp of last contact from this runner. |
| <a id="cirunnercreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of creation of this runner. |
@@ -9028,6 +9028,7 @@ Represents the total number of issues and their weights for a particular day.
| <a id="cirunnerjobcount"></a>`jobCount` | [`Int`](#int) | Number of jobs processed by the runner (limited to 1000, plus one to indicate that more items exist). |
| <a id="cirunnerlocked"></a>`locked` | [`Boolean`](#boolean) | Indicates the runner is locked. |
| <a id="cirunnermaximumtimeout"></a>`maximumTimeout` | [`Int`](#int) | Maximum timeout (in seconds) for jobs processed by the runner. |
+| <a id="cirunnerpaused"></a>`paused` | [`Boolean!`](#boolean) | Indicates the runner is paused and not available to run jobs. |
| <a id="cirunnerprivateprojectsminutescostfactor"></a>`privateProjectsMinutesCostFactor` | [`Float`](#float) | Private projects' "minutes cost factor" associated with the runner (GitLab.com only). |
| <a id="cirunnerprojectcount"></a>`projectCount` | [`Int`](#int) | Number of projects that the runner is associated with. |
| <a id="cirunnerprojects"></a>`projects` | [`ProjectConnection`](#projectconnection) | Projects the runner is associated with. For project runners only. (see [Connections](#connections)) |
@@ -16491,12 +16492,12 @@ Values for sorting runners.
| Value | Description |
| ----- | ----------- |
-| <a id="cirunnerstatusactive"></a>`ACTIVE` **{warning-solid}** | **Deprecated** in 14.6. Use CiRunnerType.active instead. |
+| <a id="cirunnerstatusactive"></a>`ACTIVE` **{warning-solid}** | **Deprecated** in 14.6. This was renamed. Use: [`CiRunner.paused`](#cirunnerpaused). |
| <a id="cirunnerstatusnever_contacted"></a>`NEVER_CONTACTED` | Runner that has never contacted this instance. Set legacyMode to null to utilize this value. Will replace NOT_CONNECTED starting in 15.0. |
| <a id="cirunnerstatusnot_connected"></a>`NOT_CONNECTED` **{warning-solid}** | **Deprecated** in 14.6. Use NEVER_CONTACTED instead. NEVER_CONTACTED will have a slightly different scope starting in 15.0, with STALE being returned instead after 3 months of no contact. |
| <a id="cirunnerstatusoffline"></a>`OFFLINE` **{warning-solid}** | **Deprecated** in 14.6. This field will have a slightly different scope starting in 15.0, with STALE being returned after a certain period offline. |
| <a id="cirunnerstatusonline"></a>`ONLINE` | Runner that contacted this instance within the last 2 hours. |
-| <a id="cirunnerstatuspaused"></a>`PAUSED` **{warning-solid}** | **Deprecated** in 14.6. Use CiRunnerType.active instead. |
+| <a id="cirunnerstatuspaused"></a>`PAUSED` **{warning-solid}** | **Deprecated** in 14.6. This was renamed. Use: [`CiRunner.paused`](#cirunnerpaused). |
| <a id="cirunnerstatusstale"></a>`STALE` | Runner that has not contacted this instance within the last 3 months. Only available if legacyMode is null. Will be a possible return value starting in 15.0. |
### `CiRunnerType`
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 2ed841b885c..65196225561 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -370,6 +370,7 @@ listed in the descriptions of the relevant settings.
| `push_event_hooks_limit` | integer | no | Number of changes (branches or tags) in a single push to determine whether webhooks and services fire or not. Webhooks and services aren't submitted if it surpasses that value. |
| `rate_limiting_response_text` | string | no | When rate limiting is enabled via the `throttle_*` settings, send this plain text response when a rate limit is exceeded. 'Retry later' is sent if this is blank. |
| `raw_blob_request_limit` | integer | no | Max number of requests per minute for each raw path. Default: 300. To disable throttling set to 0.|
+| `user_email_lookup_limit` | integer | no | Max number of requests per minute for email lookup. Default: 60. To disable throttling set to 0.|
| `recaptcha_enabled` | boolean | no | (**If enabled, requires:** `recaptcha_private_key` and `recaptcha_site_key`) Enable reCAPTCHA. |
| `recaptcha_private_key` | string | required by: `recaptcha_enabled` | Private key for reCAPTCHA. |
| `recaptcha_site_key` | string | required by: `recaptcha_enabled` | Site key for reCAPTCHA. |
diff --git a/doc/development/cicd/templates.md b/doc/development/cicd/templates.md
index b1252b86cc0..ba5ca18896e 100644
--- a/doc/development/cicd/templates.md
+++ b/doc/development/cicd/templates.md
@@ -18,6 +18,7 @@ Before submitting a merge request with a new or updated CI/CD template, you must
- Name the template following the `*.gitlab-ci.yml` format.
- Use valid [`.gitlab-ci.yml` syntax](../../ci/yaml/index.md). Verify it's valid
with the [CI/CD lint tool](../../ci/lint.md).
+- [Add template metrics](#add-metrics).
- Include [a changelog](../changelog.md) if the merge request introduces a user-facing change.
- Follow the [template review process](#contribute-cicd-template-merge-requests).
- (Optional but highly recommended) Test the template in an example GitLab project
@@ -382,6 +383,77 @@ you must:
This information is important for users when [a stable template](#stable-version)
is updated in a major version GitLab release.
+### Add metrics
+
+Every CI/CD template must also have metrics defined to track their use.
+
+To add a metric definition for a new template:
+
+1. Install and start the [GitLab GDK](https://gitlab.com/gitlab-org/gitlab-development-kit#installation).
+1. In the `gitlab` directory in your GDK, check out the branch that contains the new template.
+1. [Add the template inclusion event](../service_ping/implement.md#add-new-events)
+ with this Rake task:
+
+ ```shell
+ bin/rake gitlab:usage_data:generate_ci_template_events
+ ```
+
+ The task adds a section like the following to [`lib/gitlab/usage_data_counters/known_events/ci_templates.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data_counters/known_events/ci_templates.yml):
+
+ ```yaml
+ - name: p_ci_templates_my_template_name
+ category: ci_templates
+ redis_slot: ci_templates
+ aggregation: weekly
+ ```
+
+1. Copy the value of `name` from the new YAML section, and add it to the weekly
+ and monthly CI/CD template total count metrics:
+ - [`config/metrics/counts_7d/20210216184557_ci_templates_total_unique_counts_weekly.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_7d/20210216184557_ci_templates_total_unique_counts_weekly.yml)
+ - [`config/metrics/counts_28d/20210216184559_ci_templates_total_unique_counts_monthly.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216184559_ci_templates_total_unique_counts_monthly.yml)
+
+1. Use the same `name` as above as the last argument in the following command to
+ [add new metric definitions](../service_ping/metrics_dictionary.md#metrics-added-dynamic-to-service-ping-payload):
+
+ ```shell
+ bundle exec rails generate gitlab:usage_metric_definition:redis_hll ci_templates <template_metric_event_name>
+ ```
+
+ The output should look like:
+
+ ```shell
+ $ bundle exec rails generate gitlab:usage_metric_definition:redis_hll ci_templates p_ci_templates_my_template_name
+ create config/metrics/counts_7d/20220120073740_p_ci_templates_my_template_name_weekly.yml
+ create config/metrics/counts_28d/20220120073746_p_ci_templates_my_template_name_monthly.yml
+ ```
+
+1. Edit both newly generated files as follows:
+
+ - `name:` and `performance_indicator_type:`: Delete (not needed).
+ - `introduced_by_url:`: The URL of the MR adding the template.
+ - `data_source:`: Set to `redis_hll`.
+ - All other fields that have no values: Set to empty strings (`''`).
+ - Add the following to the end of each file:
+
+ ```yaml
+ options:
+ events:
+ - p_ci_templates_my_template_name
+ ```
+
+1. Commit and push the changes.
+
+For example, these are the metrics configuration files for the
+[5 Minute Production App template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/5-Minute-Production-App.gitlab-ci.yml):
+
+- The template inclusion event: [`lib/gitlab/usage_data_counters/known_events/ci_templates.yml#L438-L441`](https://gitlab.com/gitlab-org/gitlab/-/blob/dcddbf83c29d1ad0839d55362c1b43888304f453/lib/gitlab/usage_data_counters/known_events/ci_templates.yml#L438-L441)
+- The weekly and monthly metrics definitions:
+ - [`config/metrics/counts_7d/20210901223501_p_ci_templates_5_minute_production_app_weekly.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/1a6eceff3914f240864b2ca15ae2dc076ea67bf6/config/metrics/counts_7d/20210216184515_p_ci_templates_5_min_production_app_weekly.yml)
+ - [`config/metrics/counts_28d/20210901223505_p_ci_templates_5_minute_production_app_monthly.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216184517_p_ci_templates_5_min_production_app_monthly.yml)
+- The metrics count totals:
+ - [`config/metrics/counts_7d/20210216184557_ci_templates_total_unique_counts_weekly.yml#L19`](https://gitlab.com/gitlab-org/gitlab/-/blob/4e01ef2b094763943348655ef77008aba7a052ae/config/metrics/counts_7d/20210216184557_ci_templates_total_unique_counts_weekly.yml#L19)
+ - [`config/metrics/counts_28d/20210216184559_ci_templates_total_unique_counts_monthly.yml#L19`](https://gitlab.com/gitlab-org/gitlab/-/blob/4e01ef2b094763943348655ef77008aba7a052ae/config/metrics/counts_28d/20210216184559_ci_templates_total_unique_counts_monthly.yml#L19)
+
## Security
A template could contain malicious code. For example, a template that contains the `export` shell command in a job
diff --git a/doc/development/service_ping/index.md b/doc/development/service_ping/index.md
index 315ff2b090c..428a34461d5 100644
--- a/doc/development/service_ping/index.md
+++ b/doc/development/service_ping/index.md
@@ -557,6 +557,10 @@ skip_db_write:
ServicePing::SubmitService.new(skip_db_write: true).execute
```
+## Monitoring
+
+Service Ping reporting process state is monitored with [internal SiSense dashboard](https://app.periscopedata.com/app/gitlab/968489/Product-Intelligence---Service-Ping-Health).
+
## Troubleshooting
### Cannot disable Service Ping using the configuration file
diff --git a/doc/user/infrastructure/iac/terraform_state.md b/doc/user/infrastructure/iac/terraform_state.md
index 7e994cd5923..9665dc11db7 100644
--- a/doc/user/infrastructure/iac/terraform_state.md
+++ b/doc/user/infrastructure/iac/terraform_state.md
@@ -445,3 +445,15 @@ This happens because the value of `$CI_JOB_TOKEN` is only valid for the duration
As a workaround, use [http backend configuration variables](https://www.terraform.io/docs/language/settings/backends/http.html#configuration-variables) in your CI job,
which is what happens behind the scenes when following the
[Get started using GitLab CI](#get-started-using-gitlab-ci) instructions.
+
+### Error: "address": required field is not set
+
+By default, we set `TF_ADDRESS` to `${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${TF_STATE_NAME}`.
+If you don't set `TF_STATE_NAME` or `TF_ADDRESS` in your job, the job fails with the error message
+`Error: "address": required field is not set`.
+
+To resolve this, ensure that either `TF_ADDRESS` or `TF_STATE_NAME` is accessible in the
+job that returned the error:
+
+1. Configure the [CI/CD environment scope](../../../ci/variables/#add-a-cicd-variable-to-a-project) for the job.
+1. Set the job's [environment](../../../ci/yaml/#environment), matching the environment scope from the previous step.
diff --git a/doc/user/search/advanced_search.md b/doc/user/search/advanced_search.md
index 13fba126169..05579696d35 100644
--- a/doc/user/search/advanced_search.md
+++ b/doc/user/search/advanced_search.md
@@ -140,3 +140,28 @@ its performance:
| Issues | `global_search_issues_tab` | When enabled, the global search includes issues as part of the search. |
| Merge Requests | `global_search_merge_requests_tab` | When enabled, the global search includes merge requests as part of the search. |
| Wiki | `global_search_wiki_tab` | When enabled, the global search includes wiki as part of the search. [Group wikis](../project/wiki/group.md) are not included. |
+
+## Global Search validation
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/346263) in GitLab 14.6 [with a flag](../../administration/feature_flags.md) named `prevent_abusive_searches`. Disabled by default.
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available,
+ ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `prevent_abusive_searches`.
+ The feature is not ready for production use.
+
+To prevent abusive searches, such as searches that may result in a Distributed Denial of Service (DDoS), Global Search ignores, logs, and
+doesn't return any results for searches considered abusive according to the following criteria, if `prevent_abusive_searches` feature flag is enabled:
+
+- Searches with less than 2 characters.
+- Searches with any term greater than 100 characters. URL search terms have a maximum of 200 characters.
+- Searches with a stop word as the only term (ie: "the", "and", "if", etc.).
+- Searches with a `group_id` or `project_id` parameter that is not completely numeric.
+- Searches with a `repository_ref` or `project_ref` parameter that has special characters not allowed by [Git refname](https://git-scm.com/docs/git-check-ref-format).
+- Searches with a `scope` that is unknown.
+
+Regardless of the status of the `prevent_abusive_searches` feature flag, searches that don't
+comply with the criteria described below aren't logged as abusive but are flagged with an error:
+
+- Searches with more than 4096 characters.
+- Searches with more than 64 terms.
diff --git a/lib/gitlab/background_migration/batching_strategies/backfill_project_namespace_per_group_batching_strategy.rb b/lib/gitlab/background_migration/batching_strategies/backfill_project_namespace_per_group_batching_strategy.rb
new file mode 100644
index 00000000000..f352c527b54
--- /dev/null
+++ b/lib/gitlab/background_migration/batching_strategies/backfill_project_namespace_per_group_batching_strategy.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ module BatchingStrategies
+ # Batching class to use for back-filling project namespaces for a single group.
+ # Batches over the projects table and id column combination, scoped to a given group returning the MIN() and MAX()
+ # values for the next batch as an array.
+ #
+ # If no more batches exist in the table, returns nil.
+ class BackfillProjectNamespacePerGroupBatchingStrategy < PrimaryKeyBatchingStrategy
+ # Finds and returns the next batch in the table.
+ #
+ # table_name - The table to batch over
+ # column_name - The column to batch over
+ # batch_min_value - The minimum value which the next batch will start at
+ # batch_size - The size of the next batch
+ # job_arguments - The migration job arguments
+ def next_batch(table_name, column_name, batch_min_value:, batch_size:, job_arguments:)
+ next_batch_bounds = nil
+ model_class = ::Gitlab::BackgroundMigration::ProjectNamespaces::Models::Project
+ quoted_column_name = model_class.connection.quote_column_name(column_name)
+ projects_table = model_class.arel_table
+ hierarchy_cte_sql = Arel::Nodes::SqlLiteral.new(::Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNamespaces.hierarchy_cte(job_arguments.first))
+ relation = model_class.where(projects_table[:namespace_id].in(hierarchy_cte_sql)).where("#{quoted_column_name} >= ?", batch_min_value)
+
+ relation.each_batch(of: batch_size, column: column_name) do |batch| # rubocop:disable Lint/UnreachableLoop
+ next_batch_bounds = batch.pluck(Arel.sql("MIN(#{quoted_column_name}), MAX(#{quoted_column_name})")).first
+
+ break
+ end
+
+ next_batch_bounds
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy.rb b/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy.rb
index 80693728e86..8c2139c6207 100644
--- a/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy.rb
+++ b/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy.rb
@@ -17,7 +17,8 @@ module Gitlab
# column_name - The column to batch over
# batch_min_value - The minimum value which the next batch will start at
# batch_size - The size of the next batch
- def next_batch(table_name, column_name, batch_min_value:, batch_size:)
+ # job_arguments - The migration job arguments
+ def next_batch(table_name, column_name, batch_min_value:, batch_size:, job_arguments:)
model_class = define_batchable_model(table_name)
quoted_column_name = model_class.connection.quote_column_name(column_name)
diff --git a/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces.rb b/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces.rb
index 8e94c16369e..ba3f7c47047 100644
--- a/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces.rb
+++ b/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces.rb
@@ -5,19 +5,15 @@ module Gitlab
module ProjectNamespaces
# Back-fill project namespaces for projects that do not yet have a namespace.
#
- # TODO: remove this comment when an actuall backfill migration is added.
- #
- # This is first being added without an actual migration as we need to initially test
- # if backfilling project namespaces affects performance in any significant way.
# rubocop: disable Metrics/ClassLength
class BackfillProjectNamespaces
- BATCH_SIZE = 100
- DELETE_BATCH_SIZE = 10
+ SUB_BATCH_SIZE = 25
PROJECT_NAMESPACE_STI_NAME = 'Project'
IsolatedModels = ::Gitlab::BackgroundMigration::ProjectNamespaces::Models
- def perform(start_id, end_id, namespace_id, migration_type = 'up')
+ def perform(start_id, end_id, migration_table_name, migration_column_name, sub_batch_size, pause_ms, namespace_id, migration_type = 'up')
+ @sub_batch_size = sub_batch_size || SUB_BATCH_SIZE
load_project_ids(start_id, end_id, namespace_id)
case migration_type
@@ -34,10 +30,13 @@ module Gitlab
private
- attr_accessor :project_ids
+ attr_accessor :project_ids, :sub_batch_size
def backfill_project_namespaces(namespace_id)
- project_ids.each_slice(BATCH_SIZE) do |project_ids|
+ project_ids.each_slice(sub_batch_size) do |project_ids|
+ ActiveRecord::Base.connection.execute("select gin_clean_pending_list('index_namespaces_on_name_trigram')")
+ ActiveRecord::Base.connection.execute("select gin_clean_pending_list('index_namespaces_on_path_trigram')")
+
# We need to lock these project records for the period when we create project namespaces
# and link them to projects so that if a project is modified in the time between creating
# project namespaces `batch_insert_namespaces` and linking them to projects `batch_update_projects`
@@ -45,18 +44,17 @@ module Gitlab
#
# see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72527#note_730679469
Project.transaction do
- Project.where(id: project_ids).select(:id).lock!('FOR UPDATE')
+ Project.where(id: project_ids).select(:id).lock!('FOR UPDATE').load
batch_insert_namespaces(project_ids)
batch_update_projects(project_ids)
+ batch_update_project_namespaces_traversal_ids(project_ids)
end
-
- batch_update_project_namespaces_traversal_ids(project_ids)
end
end
def cleanup_backfilled_project_namespaces(namespace_id)
- project_ids.each_slice(BATCH_SIZE) do |project_ids|
+ project_ids.each_slice(sub_batch_size) do |project_ids|
# IMPORTANT: first nullify project_namespace_id in projects table to avoid removing projects when records
# from namespaces are deleted due to FK/triggers
nullify_project_namespaces_in_projects(project_ids)
@@ -109,7 +107,10 @@ module Gitlab
end
def delete_project_namespace_records(project_ids)
- project_ids.each_slice(DELETE_BATCH_SIZE) do |p_ids|
+ # keep the deletes a 10x smaller batch as deletes seem to be much more expensive
+ delete_batch_size = (sub_batch_size / 10).to_i + 1
+
+ project_ids.each_slice(delete_batch_size) do |p_ids|
IsolatedModels::Namespace.where(type: PROJECT_NAMESPACE_STI_NAME).where(tmp_project_id: p_ids).delete_all
end
end
@@ -117,7 +118,7 @@ module Gitlab
def load_project_ids(start_id, end_id, namespace_id)
projects = IsolatedModels::Project.arel_table
relation = IsolatedModels::Project.where(projects[:id].between(start_id..end_id))
- relation = relation.where(projects[:namespace_id].in(Arel::Nodes::SqlLiteral.new(hierarchy_cte(namespace_id)))) if namespace_id
+ relation = relation.where(projects[:namespace_id].in(Arel::Nodes::SqlLiteral.new(self.class.hierarchy_cte(namespace_id)))) if namespace_id
@project_ids = relation.pluck(:id)
end
@@ -126,7 +127,7 @@ module Gitlab
::Gitlab::Database::BackgroundMigrationJob.mark_all_as_succeeded('BackfillProjectNamespaces', arguments)
end
- def hierarchy_cte(root_namespace_id)
+ def self.hierarchy_cte(root_namespace_id)
<<-SQL
WITH RECURSIVE "base_and_descendants" AS (
(
diff --git a/lib/gitlab/database/background_migration/batched_migration_runner.rb b/lib/gitlab/database/background_migration/batched_migration_runner.rb
index 14e3919986e..ea994cc09f6 100644
--- a/lib/gitlab/database/background_migration/batched_migration_runner.rb
+++ b/lib/gitlab/database/background_migration/batched_migration_runner.rb
@@ -95,7 +95,8 @@ module Gitlab
active_migration.table_name,
active_migration.column_name,
batch_min_value: batch_min_value,
- batch_size: active_migration.batch_size)
+ batch_size: active_migration.batch_size,
+ job_arguments: active_migration.job_arguments)
return if next_batch_bounds.nil?
diff --git a/lib/gitlab/database/gitlab_loose_foreign_keys.yml b/lib/gitlab/database/gitlab_loose_foreign_keys.yml
index 13a27c0d1ec..235b49a1878 100644
--- a/lib/gitlab/database/gitlab_loose_foreign_keys.yml
+++ b/lib/gitlab/database/gitlab_loose_foreign_keys.yml
@@ -27,6 +27,10 @@ clusters_applications_runners:
- table: ci_runners
column: runner_id
on_delete: async_nullify
+ci_variables:
+ - table: projects
+ column: project_id
+ on_delete: async_delete
ci_job_token_project_scope_links:
- table: users
column: added_by_id
@@ -83,6 +87,9 @@ ci_pipelines:
- table: users
column: user_id
on_delete: async_nullify
+ - table: merge_requests
+ column: merge_request_id
+ on_delete: async_delete
ci_project_mirrors:
- table: projects
column: project_id
@@ -138,6 +145,10 @@ merge_trains:
- table: ci_pipelines
column: pipeline_id
on_delete: async_nullify
+ci_refs:
+ - table: projects
+ column: project_id
+ on_delete: async_delete
ci_group_variables:
- table: namespaces
column: group_id
@@ -166,6 +177,9 @@ ci_sources_pipelines:
- table: projects
column: source_project_id
on_delete: async_delete
+ - table: projects
+ column: project_id
+ on_delete: async_delete
ci_triggers:
- table: users
column: owner_id
diff --git a/lib/gitlab/subscription_portal.rb b/lib/gitlab/subscription_portal.rb
index d987247fdc4..b8d124541f9 100644
--- a/lib/gitlab/subscription_portal.rb
+++ b/lib/gitlab/subscription_portal.rb
@@ -42,10 +42,6 @@ module Gitlab
"#{self.subscriptions_url}/subscriptions"
end
- def self.subscriptions_plans_url
- Gitlab::Saas.about_pricing_url
- end
-
def self.subscriptions_gitlab_plans_url
"#{self.subscriptions_url}/gitlab_plans"
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a53e184471b..fa5fe2dea99 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -632,6 +632,9 @@ msgstr ""
msgid "%{group_name} group members"
msgstr ""
+msgid "%{group_name} is approaching the limit of available seats"
+msgstr ""
+
msgid "%{group_name} uses group managed accounts. You need to create a new GitLab account which will be managed by %{group_name}."
msgstr ""
@@ -5421,9 +5424,6 @@ msgstr ""
msgid "BillingPlans|@%{user_name} you are currently using the %{plan_name}."
msgstr ""
-msgid "BillingPlans|Compare all plans"
-msgstr ""
-
msgid "BillingPlans|Congratulations, your free trial is activated."
msgstr ""
@@ -5460,9 +5460,6 @@ msgstr ""
msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}."
msgstr ""
-msgid "BillingPlans|Upgrade to GitLab %{planNameForUpgrade}"
-msgstr ""
-
msgid "BillingPlans|While GitLab is ending availability of the Bronze plan, you can still renew your Bronze subscription one additional time before %{eoa_bronze_plan_end_date}. We are also offering a limited time free upgrade to our Premium Plan (up to 25 users)! Learn more about the changes and offers in our %{announcement_link}."
msgstr ""
@@ -15141,14 +15138,6 @@ msgstr ""
msgid "FeatureFlag|User List"
msgstr ""
-msgid "FeatureHighlight|%{daysRemaining} day remaining to enjoy %{featureName}"
-msgid_plural "FeatureHighlight|%{daysRemaining} days remaining to enjoy %{featureName}"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "FeatureHighlight|Enjoying your GitLab %{planNameForTrial} trial? To continue using %{featureName} after your trial ends, upgrade to GitLab %{planNameForUpgrade}."
-msgstr ""
-
msgid "Feb"
msgstr ""
@@ -31127,9 +31116,6 @@ msgstr ""
msgid "SSL verification"
msgstr ""
-msgid "SVG illustration"
-msgstr ""
-
msgid "Satisfied"
msgstr ""
@@ -35058,6 +35044,9 @@ msgstr ""
msgid "Target-Branch"
msgstr ""
+msgid "Task"
+msgstr ""
+
msgid "Task ID: %{elastic_task}"
msgstr ""
@@ -35475,9 +35464,6 @@ msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
-msgid "The %{featureName} feature is part of your GitLab Ultimate trial."
-msgstr ""
-
msgid "The %{link_start}true-up model%{link_end} allows having more users, and additional users will incur a retroactive charge on renewal."
msgstr ""
@@ -36481,9 +36467,6 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
-msgid "This feature is part of your GitLab Ultimate trial."
-msgstr ""
-
msgid "This feature requires local storage to be enabled"
msgstr ""
@@ -39529,6 +39512,9 @@ msgstr[1] ""
msgid "View replaced file @ "
msgstr ""
+msgid "View seat usage"
+msgstr ""
+
msgid "View setting"
msgstr ""
@@ -39865,6 +39851,9 @@ msgstr ""
msgid "Vulnerability|Scanner Provider"
msgstr ""
+msgid "Vulnerability|Secure Code Warrior"
+msgstr ""
+
msgid "Vulnerability|Security Audit"
msgstr ""
@@ -39898,6 +39887,9 @@ msgstr ""
msgid "Vulnerability|Unmodified Response"
msgstr ""
+msgid "Vulnerability|View training"
+msgstr ""
+
msgid "WARNING:"
msgstr ""
@@ -40617,6 +40609,15 @@ msgstr ""
msgid "Work in progress Limit"
msgstr ""
+msgid "WorkItem|Convert to work item"
+msgstr ""
+
+msgid "WorkItem|Create work item"
+msgstr ""
+
+msgid "WorkItem|New Task"
+msgstr ""
+
msgid "WorkItem|Work Items"
msgstr ""
@@ -41588,6 +41589,9 @@ msgstr ""
msgid "Your subscription expired!"
msgstr ""
+msgid "Your subscription has %{remaining_seats_count} out of %{total_seats_count} seats remaining. Even if you reach the number of seats in your subscription, you can continue to add users, and GitLab will bill you for the overage."
+msgstr ""
+
msgid "Your subscription is now expired. To renew, export your license usage file and email it to %{renewal_service_email}. A new license will be emailed to the email address registered in the %{customers_dot}. You can upload this license to your instance. To use Free tier, remove your current license."
msgstr ""
diff --git a/scripts/decomposition/generate-loose-foreign-key b/scripts/decomposition/generate-loose-foreign-key
new file mode 100755
index 00000000000..860da926594
--- /dev/null
+++ b/scripts/decomposition/generate-loose-foreign-key
@@ -0,0 +1,405 @@
+#!/usr/bin/env -S ENABLE_SPRING=0 bin/rails runner -e test
+
+# This is helper script to swap foreign key to loose foreign key
+# using DB schema
+
+require 'optparse'
+
+$options = {
+ milestone: "#{Gitlab.version_info.major}.#{Gitlab.version_info.minor}",
+ cross_schema: false,
+ dry_run: false,
+ branch: true,
+ rspec: true
+}
+
+OptionParser.new do |opts|
+ opts.banner = "Usage: #{$0} [options] <filters...>"
+
+ opts.on("-c", "--cross-schema", "Show only cross-schema foreign keys") do |v|
+ $options[:cross_schema] = v
+ end
+
+ opts.on("-n", "--dry-run", "Do not execute any commands (dry run)") do |v|
+ $options[:dry_run] = v
+ end
+
+ opts.on("-b", "--[no-]branch", "Create or not a new branch") do |v|
+ $options[:branch] = v
+ end
+
+ opts.on("-r", "--[no-]rspec", "Create or not a rspecs automatically") do |v|
+ $options[:rspec] = v
+ end
+
+ opts.on("-m", "--milestone MILESTONE", "Specify custom milestone (current: #{$options[:milestone]})") do |v|
+ $options[:milestone] = v
+ end
+
+ opts.on("-h", "--help", "Prints this help") do
+ puts opts
+ exit
+ end
+end.parse!
+
+unless system("git diff --quiet db/structure.sql")
+ raise "The db/structure.sql is changed. Reset branch or commit changes."
+end
+
+unless system("git diff --quiet")
+ raise "There are uncommitted changes. Commit to continue."
+end
+
+if Gitlab::Database.database_base_models.many?
+ raise 'Cannot run in multiple-databases mode. Use only `main:` in `config/database.yml`.'
+end
+
+puts "Re-creating current test database"
+ActiveRecord::Tasks::DatabaseTasks.drop_current
+ActiveRecord::Tasks::DatabaseTasks.create_current
+ActiveRecord::Tasks::DatabaseTasks.load_schema_current
+ActiveRecord::Tasks::DatabaseTasks.migrate
+ActiveRecord::Migration.check_pending!
+ActiveRecord::Base.connection_pool.disconnect!
+puts
+
+def exec_cmd(*args, fail: nil)
+ # output full command
+ if $options[:dry_run]
+ puts ">> #{args.shelljoin}"
+ return true
+ end
+
+ # truncate up-to 60 chars or first line
+ command = args.shelljoin
+ truncated_command = command.truncate([command.lines.first.length+3, 120].min)
+
+ puts ">> #{truncated_command}"
+ return true if system(*args)
+
+ raise fail if fail
+
+ puts "--------------------------------------------------"
+ puts "This command failed:"
+ puts ">> #{command}"
+ puts "--------------------------------------------------"
+ false
+end
+
+def has_lfk?(definition)
+ Gitlab::Database::LooseForeignKeys.definitions.any? do |lfk_definition|
+ lfk_definition.from_table == definition.from_table &&
+ lfk_definition.to_table == definition.to_table &&
+ lfk_definition.column == definition.column
+ end
+end
+
+def matching_filter?(definition, filters)
+ filters.all? do |filter|
+ definition.from_table.include?(filter) ||
+ definition.to_table.include?(filter) ||
+ definition.column.include?(filter)
+ end
+end
+
+def columns(*args)
+ puts("%5s | %7s | %40s | %20s | %30s | %15s " % args)
+end
+
+def add_definition_to_yaml(definition)
+ content = YAML.load_file(Rails.root.join('lib/gitlab/database/gitlab_loose_foreign_keys.yml'))
+ table_definitions = content[definition.from_table]
+
+ # insert new entry at random place to avoid conflicts
+ unless table_definitions
+ table_definitions = []
+ insert_idx = rand(content.count+1)
+
+ # insert at a given index in ordered hash
+ content = content.to_a
+ content.insert(insert_idx, [definition.from_table, table_definitions])
+ content = content.to_h
+ end
+
+ on_delete =
+ case definition.on_delete
+ when :cascade
+ 'async_delete'
+ when :nullify
+ 'async_nullify'
+ else
+ raise "Unsupported on_delete behavior: #{definition.on_delete}"
+ end
+
+ yaml_definition = {
+ "table" => definition.to_table,
+ "column" => definition.column,
+ "on_delete" => on_delete
+ }
+
+ # match and update by "table", "column"
+ if existing = table_definitions.pluck("table", "column").index([definition.to_table, definition.column])
+ puts "Updated existing definition from #{table_definitions[existing]} to #{yaml_definition}."
+ table_definitions[existing] = yaml_definition
+ else
+ puts "Add new definition for #{yaml_definition}."
+ table_definitions.append(yaml_definition)
+ end
+
+ # emulate existing formatting
+ File.write(
+ Rails.root.join('lib/gitlab/database/gitlab_loose_foreign_keys.yml'),
+ content.to_yaml.gsub(/^([- ] )/, ' \1')
+ )
+
+ exec_cmd("git", "add", "lib/gitlab/database/gitlab_loose_foreign_keys.yml")
+end
+
+def generate_migration(definition)
+ timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
+
+ # db/post_migrate/20220111221516_remove_projects_ci_pending_builds_fk.rb
+
+ migration_name = "db/post_migrate/#{timestamp}_remove_#{definition.to_table}_#{definition.from_table}_#{definition.column}_fk.rb"
+ puts "Writing #{migration_name}"
+
+ content = <<-EOF.strip_heredoc
+ # frozen_string_literal: true
+
+ class Remove#{definition.to_table.camelcase}#{definition.from_table.camelcase}#{definition.column.camelcase}Fk < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ return unless foreign_key_exists?(:#{definition.from_table}, :#{definition.to_table}, name: "#{definition.name}")
+
+ with_lock_retries do
+ execute('LOCK #{definition.to_table}, #{definition.from_table} IN ACCESS EXCLUSIVE MODE') if transaction_open?
+
+ remove_foreign_key_if_exists(:#{definition.from_table}, :#{definition.to_table}, name: "#{definition.name}")
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key(:#{definition.from_table}, :#{definition.to_table}, name: "#{definition.name}", column: :#{definition.column}, target_column: :#{definition.primary_key}, on_delete: :#{definition.on_delete})
+ end
+ end
+ EOF
+
+ File.write(migration_name, content)
+
+ exec_cmd("git", "add", migration_name, fail: "Failed to add migration file.")
+ exec_cmd("bin/rails", "db:migrate", fail: "Failed to run db:migrate.")
+ exec_cmd("git", "add", "db/schema_migrations/#{timestamp}", "db/structure.sql", fail: "There are uncommitted changes. We should not have any.")
+ exec_cmd("git diff --exit-code --name-only", fail: "There are uncommitted changes. We should not have any.")
+end
+
+def class_by_table_name
+ @index_by_table_name ||= ActiveRecord::Base
+ .descendants
+ .reject(&:abstract_class)
+ .map(&:base_class)
+ .index_by(&:table_name)
+end
+
+def spec_from_clazz(clazz, definition)
+ %w[spec/models ee/spec/models].each do |specs_path|
+ path = File.join(specs_path, clazz.underscore + "_spec.rb")
+ return path if File.exist?(path)
+ end
+
+ raise "Cannot find specs for #{clazz} (#{definition.from_table})"
+end
+
+def add_test_to_specs(definition)
+ return unless $options[:rspec]
+
+ clazz = class_by_table_name[definition.from_table]
+ raise "Cannot map #{definition.from_table} to clazz" unless clazz
+
+ spec_path = spec_from_clazz(clazz, definition)
+ puts "Adding test to #{spec_path}..."
+
+ spec_test = <<-EOF.strip_heredoc.indent(2)
+ context 'loose foreign key on #{definition.from_table}.#{definition.column}' do
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:#{definition.to_table.singularize}) }
+ let!(:model) { create(:#{definition.from_table.singularize}, #{definition.column.delete_suffix("_id").singularize}: parent) }
+ end
+ end
+ EOF
+
+ # append to end of file with empty line before
+ lines = File.readlines(spec_path)
+ insert_line = lines.count - 1
+ lines.insert(insert_line, "\n", *spec_test.lines)
+ File.write(spec_path, lines.join(""))
+
+ # find a matching line
+ test_lines = (1..lines.count).select do |line|
+ lines[line-1].include?("it_behaves_like 'cleanup by a loose foreign key' do")
+ end.join(":")
+
+ loop do
+ if system("bin/rspec", "#{spec_path}:#{test_lines}")
+ puts "Test seems fine?"
+ break
+ end
+
+ puts "--------------------------------------------------"
+ puts "Test failed:"
+ puts "Edit: vim #{spec_path} (lines #{test_lines})"
+ puts "Re-run: bin/rspec #{spec_path}:#{test_lines}"
+ puts "--------------------------------------------------"
+ puts "Running bash. To exit do 'Ctrl-D' to re-run, or do 'Ctrl-C' to break (and ignore failure)."
+ puts
+
+ unless exec_cmd("bash")
+ break
+ end
+ end
+
+ exec_cmd("git", "add", spec_path, fail: "There are uncommitted changes. We should not have any.")
+end
+
+def update_no_cross_db_foreign_keys_spec(definition)
+ from_column = "#{definition.from_table}.#{definition.column}"
+ spec_path = "spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb"
+
+ puts "Updating #{spec_path}..."
+ lines = File.readlines(spec_path)
+ updated = lines.reject { |line| line.strip == from_column }
+
+ if lines.count == updated.count
+ puts "Nothing changed."
+ return
+ end
+
+ File.write(spec_path, updated.join(""))
+ exec_cmd("git", "add", spec_path, fail: "Failed to add changes from #{spec_path}")
+end
+
+def commit_changes(definition)
+ branch_name = "remove-#{definition.to_table}_#{definition.from_table}_#{definition.column}-fk"
+ commit_title = "Swap FK #{definition.from_table} to #{definition.to_table} for LFK"
+ mr_title = "Swap FK #{definition.from_table}.#{definition.column} to #{definition.to_table} for LFK"
+ description = <<-EOF.strip_heredoc
+ Swaps FK for #{definition.from_table}.#{definition.column} to #{definition.to_table}
+
+ Changelog: changed
+ EOF
+
+ commit_message = "#{commit_title}\n\n#{description}"
+
+ existing_branch = %x[git rev-parse --abbrev-ref HEAD].strip
+
+ if $options[:branch]
+ unless exec_cmd("git", "checkout", "-b", branch_name)
+ raise "Failed to create branch: #{branch_name}"
+ end
+ end
+
+ unless exec_cmd("git", "commit", "-m", commit_message)
+ raise "Failed to commit changes."
+ end
+
+ if $options[:branch]
+ exec_cmd("git", "push", "origin", "-u", "HEAD",
+ "-o", "merge_request.create",
+ "-o", "merge_request.target=#{existing_branch}",
+ "-o", "merge_request.milestone=#{$options[:milestone]}",
+ "-o", "merge_request.title=#{mr_title}"
+ )
+
+ puts
+ puts "--------------------------------------------------"
+ puts "Put this as MR description:"
+ puts "--------------------------------------------------"
+ puts <<-EOF.strip_heredoc
+ ## What does this MR do and why?
+
+ Per https://gitlab.com/groups/gitlab-org/-/epics/7249
+
+ As part of our CI "decomposition" efforts we need to remove all foreign keys that are cross-database (ie. between the planned \`main\` and \`ci\` databases). We are going to replace them all with ["loose foreign keys"](https://docs.gitlab.com/ee/development/database/loose_foreign_keys.html).
+
+ Related: <DETAIL>
+
+ ## Validations
+
+ - **Best team to review (check off when reviewed):** TBD
+ - [ ] No way for user to access once parent is deleted. Please explain: <DETAIL>
+ - [ ] Possible to access once parent deleted but low user impact. Please explain: <DETAIL>
+ - [ ] Possible Sidekiq workers that may load directly and possibly lead to exceptions. Please explain: <DETAIL>
+ - [ ] Possible user impact to be evaluated or mitigated. Please explain: <DETAIL>
+ - [ ] Is this FK safe to be removed to avoid LOCKing problems? (Explanation: https://gitlab.com/groups/gitlab-org/-/epics/7249#note_819662046). Please explain: <DETAIL>
+
+ ## MR acceptance checklist
+
+ This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
+
+ * [ ] I have evaluated the [MR acceptance checklist](https://docs.gitlab.com/ee/development/code_review.html#acceptance-checklist) for this MR.
+
+ /label ~"ci-decomposition::phase4" ~"database::review pending" ~"devops::enablement" ~"group::sharding" ~"section::enablement" ~"sharding::active" ~"type::feature" ~"workflow::in dev" ~backend ~"ci-decomposition" ~database ~"Category:Sharding"
+ /milestone %"#{$options[:milestone]}"
+ /assign_reviewer @ahegyi
+ EOF
+ puts "--------------------------------------------------"
+ end
+end
+
+all_foreign_keys = ActiveRecord::Base.connection.tables.flat_map do |table|
+ ActiveRecord::Base.connection.foreign_keys(table)
+end
+
+# Show only cross-schema foreign keys
+if $options[:cross_schema]
+ all_foreign_keys.select! do |definition|
+ Gitlab::Database::GitlabSchema.table_schema(definition.from_table) != Gitlab::Database::GitlabSchema.table_schema(definition.to_table)
+ end
+end
+
+if $options[:cross_schema]
+ puts "Showing cross-schema foreign keys (#{all_foreign_keys.count}):"
+else
+ puts "Showing all foreign keys (#{all_foreign_keys.count}):"
+ puts "Did you meant `#{$0} --cross-schema ...`?"
+end
+
+columns("ID", "HAS_LFK", "FROM", "TO", "COLUMN", "ON_DELETE")
+all_foreign_keys.each_with_index do |definition, idx|
+ columns(idx, has_lfk?(definition) ? 'Y' : 'N', definition.from_table, definition.to_table, definition.column, definition.on_delete)
+end
+puts
+
+puts "To match FK write one or many filters to match against FROM/TO/COLUMN:"
+puts "- #{$0} <filter(s)...>"
+puts "- #{$0} ci_job_artifacts project_id"
+puts "- #{$0} dast_site_profiles_pipelines"
+puts
+
+return if ARGV.empty?
+
+puts "Loading all models..."
+# Fix bug with loading `app/models/identity/uniqueness_scopes.rb`
+require_relative Rails.root.join('app/models/identity.rb')
+
+%w[app/models/**/*.rb ee/app/models/**/*.rb].each do |filter|
+ Dir.glob(filter).each do |path|
+ require_relative Rails.root.join(path)
+ end
+end
+puts
+
+puts "Generating Loose Foreign Key for given filters: #{ARGV}"
+
+all_foreign_keys.each_with_index do |definition, idx|
+ next unless matching_filter?(definition, ARGV)
+
+ puts "Matched: #{idx} (#{definition.from_table}, #{definition.to_table}, #{definition.column})"
+
+ add_definition_to_yaml(definition)
+ generate_migration(definition)
+ add_test_to_specs(definition)
+ update_no_cross_db_foreign_keys_spec(definition)
+ commit_changes(definition)
+end
+puts
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index d5fe32ac094..af34ae2f69b 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -456,6 +456,28 @@ RSpec.describe RegistrationsController do
subject
end
+
+ describe 'logged_out_marketing_header experiment', :experiment do
+ before do
+ stub_experiments(logged_out_marketing_header: :candidate)
+ end
+
+ it 'tracks signed_up event' do
+ expect(experiment(:logged_out_marketing_header)).to track(:signed_up).on_next_instance
+
+ subject
+ end
+
+ context 'when registration fails' do
+ let_it_be(:user_params) { { user: base_user_params.merge({ username: '' }) } }
+
+ it 'does not track signed_up event' do
+ expect(experiment(:logged_out_marketing_header)).not_to track(:signed_up)
+
+ subject
+ end
+ end
+ end
end
describe '#destroy' do
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 76d6fded531..eec7fbf625b 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -495,7 +495,7 @@ RSpec.describe "Admin Runners" do
description: 'runner-foo',
version: '14.0',
ip_address: '127.0.0.1',
- tag_list: %w(tag1 tag2)
+ tag_list: ['tag1']
)
end
@@ -519,7 +519,7 @@ RSpec.describe "Admin Runners" do
expect(page).to have_content 'IP Address 127.0.0.1'
expect(page).to have_content 'Configuration Runs untagged jobs'
expect(page).to have_content 'Maximum job timeout None'
- expect(page).to have_content 'Tags tag1 tag2'
+ expect(page).to have_content 'Tags tag1'
end
end
end
diff --git a/spec/features/issues/filtered_search/dropdown_base_spec.rb b/spec/features/issues/filtered_search/dropdown_base_spec.rb
index 3a304515cab..b8fb807dd78 100644
--- a/spec/features/issues/filtered_search/dropdown_base_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_base_spec.rb
@@ -24,23 +24,6 @@ RSpec.describe 'Dropdown base', :js do
visit project_issues_path(project)
end
- describe 'behavior' do
- it 'shows loading indicator when opened' do
- slow_requests do
- # We aren't using `input_filtered_search` because we want to see the loading indicator
- filtered_search.set('assignee:=')
-
- expect(page).to have_css("#{js_dropdown_assignee} .filter-dropdown-loading", visible: true)
- end
- end
-
- it 'hides loading indicator when loaded' do
- input_filtered_search('assignee:=', submit: false, extra_space: false)
-
- expect(find(js_dropdown_assignee)).not_to have_css('.filter-dropdown-loading')
- end
- end
-
describe 'caching requests' do
it 'caches requests after the first load' do
input_filtered_search('assignee:=', submit: false, extra_space: false)
diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb
index a036a9a5bbc..8c906e6a27c 100644
--- a/spec/features/issues/user_edits_issue_spec.rb
+++ b/spec/features/issues/user_edits_issue_spec.rb
@@ -145,7 +145,7 @@ RSpec.describe "Issues > User edits issue", :js do
fill_in 'Comment', with: '/label ~syzygy'
click_button 'Comment'
- expect(page).to have_text('added syzygy label just now', wait: 300)
+ expect(page).to have_text('added syzygy label just now')
page.within '.block.labels' do
# Remove `verisimilitude` label
diff --git a/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js b/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js
index 1918d5c5611..9b93fd26fa0 100644
--- a/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js
+++ b/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js
@@ -1,6 +1,6 @@
import { GlModal, GlSearchBoxByType } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import getDiffWithCommit from 'test_fixtures/merge_request_diffs/with_commit.json';
import AddReviewItemsModal from '~/add_context_commits_modal/components/add_context_commits_modal_wrapper.vue';
@@ -84,11 +84,10 @@ describe('AddContextCommitsModal', () => {
expect(findModal().attributes('ok-disabled')).toBe('true');
});
- it('enabled ok button when atleast one row is selected', () => {
+ it('enabled ok button when atleast one row is selected', async () => {
wrapper.vm.$store.state.selectedCommits = [{ ...commit, isSelected: true }];
- return wrapper.vm.$nextTick().then(() => {
- expect(findModal().attributes('ok-disabled')).toBeFalsy();
- });
+ await nextTick();
+ expect(findModal().attributes('ok-disabled')).toBeFalsy();
});
});
@@ -100,11 +99,10 @@ describe('AddContextCommitsModal', () => {
expect(findModal().attributes('ok-disabled')).toBe('true');
});
- it('an enabled ok button when atleast one row is selected', () => {
+ it('an enabled ok button when atleast one row is selected', async () => {
wrapper.vm.$store.state.selectedCommits = [{ ...commit, isSelected: true }];
- return wrapper.vm.$nextTick().then(() => {
- expect(findModal().attributes('ok-disabled')).toBeFalsy();
- });
+ await nextTick();
+ expect(findModal().attributes('ok-disabled')).toBeFalsy();
});
it('a disabled ok button in first tab, when row is selected in second tab', () => {
@@ -114,33 +112,30 @@ describe('AddContextCommitsModal', () => {
});
describe('has an ok button when clicked calls action', () => {
- it('"createContextCommits" when only new commits to be added ', () => {
+ it('"createContextCommits" when only new commits to be added ', async () => {
wrapper.vm.$store.state.selectedCommits = [{ ...commit, isSelected: true }];
findModal().vm.$emit('ok');
- return wrapper.vm.$nextTick().then(() => {
- expect(createContextCommits).toHaveBeenCalledWith(expect.anything(), {
- commits: [{ ...commit, isSelected: true }],
- forceReload: true,
- });
+ await nextTick();
+ expect(createContextCommits).toHaveBeenCalledWith(expect.anything(), {
+ commits: [{ ...commit, isSelected: true }],
+ forceReload: true,
});
});
- it('"removeContextCommits" when only added commits are to be removed ', () => {
+ it('"removeContextCommits" when only added commits are to be removed ', async () => {
wrapper.vm.$store.state.toRemoveCommits = [commit.short_id];
findModal().vm.$emit('ok');
- return wrapper.vm.$nextTick().then(() => {
- expect(removeContextCommits).toHaveBeenCalledWith(expect.anything(), true);
- });
+ await nextTick();
+ expect(removeContextCommits).toHaveBeenCalledWith(expect.anything(), true);
});
- it('"createContextCommits" and "removeContextCommits" when new commits are to be added and old commits are to be removed', () => {
+ it('"createContextCommits" and "removeContextCommits" when new commits are to be added and old commits are to be removed', async () => {
wrapper.vm.$store.state.selectedCommits = [{ ...commit, isSelected: true }];
wrapper.vm.$store.state.toRemoveCommits = [commit.short_id];
findModal().vm.$emit('ok');
- return wrapper.vm.$nextTick().then(() => {
- expect(createContextCommits).toHaveBeenCalledWith(expect.anything(), {
- commits: [{ ...commit, isSelected: true }],
- });
- expect(removeContextCommits).toHaveBeenCalledWith(expect.anything(), undefined);
+ await nextTick();
+ expect(createContextCommits).toHaveBeenCalledWith(expect.anything(), {
+ commits: [{ ...commit, isSelected: true }],
});
+ expect(removeContextCommits).toHaveBeenCalledWith(expect.anything(), undefined);
});
});
diff --git a/spec/frontend/admin/users/components/modals/delete_user_modal_spec.js b/spec/frontend/admin/users/components/modals/delete_user_modal_spec.js
index 025ae825e0d..f875cd24ee1 100644
--- a/spec/frontend/admin/users/components/modals/delete_user_modal_spec.js
+++ b/spec/frontend/admin/users/components/modals/delete_user_modal_spec.js
@@ -1,5 +1,6 @@
import { GlButton, GlFormInput, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DeleteUserModal from '~/admin/users/components/modals/delete_user_modal.vue';
import UserDeletionObstaclesList from '~/vue_shared/components/user_deletion_obstacles/user_deletion_obstacles_list.vue';
import ModalStub from './stubs/modal_stub';
@@ -82,11 +83,11 @@ describe('User Operation confirmation modal', () => {
});
describe('with incorrect username', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createComponent();
setUsername(badUsername);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('shows incorrect username', () => {
@@ -100,11 +101,11 @@ describe('User Operation confirmation modal', () => {
});
describe('with correct username', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createComponent();
setUsername(username);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('shows correct username', () => {
@@ -117,10 +118,10 @@ describe('User Operation confirmation modal', () => {
});
describe('when primary action is submitted', () => {
- beforeEach(() => {
+ beforeEach(async () => {
findPrimaryButton().vm.$emit('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('clears the input', () => {
@@ -136,10 +137,10 @@ describe('User Operation confirmation modal', () => {
});
describe('when secondary action is submitted', () => {
- beforeEach(() => {
+ beforeEach(async () => {
findSecondaryButton().vm.$emit('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('has correct form attributes and calls submit', () => {
@@ -168,7 +169,7 @@ describe('User Operation confirmation modal', () => {
it("shows enabled buttons when user's name is entered without whitespace", async () => {
setUsername('John Smith');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findPrimaryButton().attributes('disabled')).toBeUndefined();
expect(findSecondaryButton().attributes('disabled')).toBeUndefined();
diff --git a/spec/frontend/admin/users/components/modals/user_modal_manager_spec.js b/spec/frontend/admin/users/components/modals/user_modal_manager_spec.js
index 65ce242662b..4786357faa1 100644
--- a/spec/frontend/admin/users/components/modals/user_modal_manager_spec.js
+++ b/spec/frontend/admin/users/components/modals/user_modal_manager_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import UserModalManager from '~/admin/users/components/modals/user_modal_manager.vue';
import ModalStub from './stubs/modal_stub';
@@ -50,20 +51,19 @@ describe('Users admin page Modal Manager', () => {
expect(() => wrapper.vm.show({ glModalAction: 'action1' })).toThrow();
});
- it('renders modal with expected props when valid configuration is passed', () => {
+ it('renders modal with expected props when valid configuration is passed', async () => {
createComponent();
wrapper.vm.show({
glModalAction: 'action1',
extraProp: 'extraPropValue',
});
- return wrapper.vm.$nextTick().then(() => {
- const modal = findModal();
- expect(modal.exists()).toBeTruthy();
- expect(modal.vm.$attrs.csrfToken).toEqual('dummyCSRF');
- expect(modal.vm.$attrs.extraProp).toEqual('extraPropValue');
- expect(modal.vm.showWasCalled).toBeTruthy();
- });
+ await nextTick();
+ const modal = findModal();
+ expect(modal.exists()).toBeTruthy();
+ expect(modal.vm.$attrs.csrfToken).toEqual('dummyCSRF');
+ expect(modal.vm.$attrs.extraProp).toEqual('extraPropValue');
+ expect(modal.vm.showWasCalled).toBeTruthy();
});
});
@@ -101,7 +101,7 @@ describe('Users admin page Modal Manager', () => {
it('renders the modal when the button is clicked', async () => {
button.click();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findModal().exists()).toBe(true);
});
@@ -110,7 +110,7 @@ describe('Users admin page Modal Manager', () => {
button.removeAttribute('data-gl-modal-action');
button.click();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findModal().exists()).toBe(false);
});
@@ -118,7 +118,7 @@ describe('Users admin page Modal Manager', () => {
it('does not render the modal when a button without the selector class is clicked', async () => {
button2.click();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findModal().exists()).toBe(false);
});
diff --git a/spec/frontend/alert_management/components/alert_management_table_spec.js b/spec/frontend/alert_management/components/alert_management_table_spec.js
index 39aab8dc1f8..5b823694b99 100644
--- a/spec/frontend/alert_management/components/alert_management_table_spec.js
+++ b/spec/frontend/alert_management/components/alert_management_table_spec.js
@@ -2,6 +2,7 @@ import { GlTable, GlAlert, GlLoadingIcon, GlDropdown, GlIcon, GlAvatar } from '@
import { mount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import mockAlerts from 'jest/vue_shared/alert_details/mocks/alerts.json';
@@ -169,7 +170,7 @@ describe('AlertManagementTable', () => {
loading: false,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlTable).exists()).toBe(true);
expect(findAlertsTable().find(GlIcon).classes('icon-critical')).toBe(true);
diff --git a/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
index acd80ed8db2..152061790e5 100644
--- a/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
+++ b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
@@ -1,7 +1,7 @@
import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql';
import updateHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql';
@@ -70,7 +70,7 @@ describe('AlertsSettingsWrapper', () => {
async function destroyHttpIntegration(localWrapper) {
await jest.runOnlyPendingTimers();
- await localWrapper.vm.$nextTick();
+ await nextTick();
localWrapper
.find(IntegrationsList)
diff --git a/spec/frontend/analytics/shared/components/daterange_spec.js b/spec/frontend/analytics/shared/components/daterange_spec.js
index 854582abb82..75061dc8934 100644
--- a/spec/frontend/analytics/shared/components/daterange_spec.js
+++ b/spec/frontend/analytics/shared/components/daterange_spec.js
@@ -1,5 +1,6 @@
import { GlDaterangePicker } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { useFakeDate } from 'helpers/fake_date';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import Daterange from '~/analytics/shared/components/daterange.vue';
@@ -48,7 +49,7 @@ describe('Daterange component', () => {
});
describe('with a minDate being set', () => {
- it('emits the change event with the minDate when the user enters a start date before the minDate', () => {
+ it('emits the change event with the minDate when the user enters a start date before the minDate', async () => {
const startDate = new Date('2019-09-01');
const endDate = new Date('2019-09-30');
const minDate = new Date('2019-06-01');
@@ -60,9 +61,8 @@ describe('Daterange component', () => {
input.setValue('2019-01-01');
input.trigger('change');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().change).toEqual([[{ startDate: minDate, endDate }]]);
- });
+ await nextTick();
+ expect(wrapper.emitted().change).toEqual([[{ startDate: minDate, endDate }]]);
});
});
diff --git a/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js b/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js
index 28d7ebe28df..386fb4eb616 100644
--- a/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js
+++ b/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js
@@ -1,4 +1,5 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { stubComponent } from 'helpers/stub_component';
import { TEST_HOST } from 'helpers/test_constants';
@@ -99,9 +100,9 @@ describe('ProjectsDropdownFilter component', () => {
const findDropdownFullPathAtIndex = (index) =>
findDropdownAtIndex(index).find('[data-testid="project-full-path"]');
- const selectDropdownItemAtIndex = (index) => {
+ const selectDropdownItemAtIndex = async (index) => {
findDropdownAtIndex(index).find('button').trigger('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
};
// NOTE: Selected items are now visually separated from unselected items
@@ -132,16 +133,15 @@ describe('ProjectsDropdownFilter component', () => {
expect(spyQuery).toHaveBeenCalledTimes(1);
- await wrapper.vm.$nextTick(() => {
- expect(spyQuery).toHaveBeenCalledWith({
- query: getProjects,
- variables: {
- search: 'gitlab',
- groupFullPath: wrapper.vm.groupNamespace,
- first: 50,
- includeSubgroups: true,
- },
- });
+ await nextTick();
+ expect(spyQuery).toHaveBeenCalledWith({
+ query: getProjects,
+ variables: {
+ search: 'gitlab',
+ groupFullPath: wrapper.vm.groupNamespace,
+ first: 50,
+ includeSubgroups: true,
+ },
});
});
});
@@ -193,7 +193,7 @@ describe('ProjectsDropdownFilter component', () => {
expect(wrapper.text()).toContain('2 projects selected');
findClearAllButton().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.text()).not.toContain('2 projects selected');
expect(wrapper.text()).toContain('Select projects');
@@ -366,9 +366,8 @@ describe('ProjectsDropdownFilter component', () => {
selectDropdownItemAtIndex(0);
selectDropdownItemAtIndex(1);
- await wrapper.vm.$nextTick().then(() => {
- expect(findDropdownButton().text()).toBe('2 projects selected');
- });
+ await nextTick();
+ expect(findDropdownButton().text()).toBe('2 projects selected');
});
});
});
diff --git a/spec/frontend/analytics/usage_trends/components/usage_trends_count_chart_spec.js b/spec/frontend/analytics/usage_trends/components/usage_trends_count_chart_spec.js
index f86f2eacf50..9696e069b9c 100644
--- a/spec/frontend/analytics/usage_trends/components/usage_trends_count_chart_spec.js
+++ b/spec/frontend/analytics/usage_trends/components/usage_trends_count_chart_spec.js
@@ -1,7 +1,7 @@
import { GlAlert } from '@gitlab/ui';
import { GlLineChart } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -164,7 +164,7 @@ describe('UsageTrendsCountChart', () => {
.spyOn(wrapper.vm.$apollo.queries[identifier], 'fetchMore')
.mockImplementation(jest.fn().mockRejectedValue());
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('calls fetchMore', () => {
diff --git a/spec/frontend/analytics/usage_trends/components/users_chart_spec.js b/spec/frontend/analytics/usage_trends/components/users_chart_spec.js
index 04ea25a02d5..0cd605ca1bf 100644
--- a/spec/frontend/analytics/usage_trends/components/users_chart_spec.js
+++ b/spec/frontend/analytics/usage_trends/components/users_chart_spec.js
@@ -1,7 +1,7 @@
import { GlAlert } from '@gitlab/ui';
import { GlAreaChart } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import UsersChart from '~/analytics/usage_trends/components/users_chart.vue';
@@ -67,7 +67,7 @@ describe('UsersChart', () => {
describe('without data', () => {
beforeEach(async () => {
wrapper = createComponent({ users: [] });
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders an no data message', () => {
@@ -86,7 +86,7 @@ describe('UsersChart', () => {
describe('with data', () => {
beforeEach(async () => {
wrapper = createComponent({ users: mockCountsData2 });
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('hides the skeleton loader', () => {
@@ -107,7 +107,7 @@ describe('UsersChart', () => {
describe('with errors', () => {
beforeEach(async () => {
wrapper = createComponent({ loadingError: true });
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders an error message', () => {
@@ -134,7 +134,7 @@ describe('UsersChart', () => {
});
jest.spyOn(wrapper.vm.$apollo.queries.users, 'fetchMore');
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('requests data twice', () => {
@@ -147,7 +147,7 @@ describe('UsersChart', () => {
});
describe('when the fetchMore query throws an error', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper = createComponent({
users: mockCountsData2,
additionalData: mockCountsData1,
@@ -156,7 +156,7 @@ describe('UsersChart', () => {
jest
.spyOn(wrapper.vm.$apollo.queries.users, 'fetchMore')
.mockImplementation(jest.fn().mockRejectedValue());
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('calls fetchMore', () => {
diff --git a/spec/frontend/artifacts_settings/components/keep_latest_artifact_checkbox_spec.js b/spec/frontend/artifacts_settings/components/keep_latest_artifact_checkbox_spec.js
index 3ba0280deb3..2f3ff2b22f2 100644
--- a/spec/frontend/artifacts_settings/components/keep_latest_artifact_checkbox_spec.js
+++ b/spec/frontend/artifacts_settings/components/keep_latest_artifact_checkbox_spec.js
@@ -1,6 +1,6 @@
import { GlFormCheckbox, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import UpdateKeepLatestArtifactProjectSetting from '~/artifacts_settings/graphql/mutations/update_keep_latest_artifact_project_setting.mutation.graphql';
@@ -109,13 +109,13 @@ describe('Keep latest artifact checkbox', () => {
});
it('sets correct setting value in checkbox with query result', async () => {
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.element).toMatchSnapshot();
});
it('checkbox is enabled when application setting is enabled', async () => {
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findCheckbox().attributes('disabled')).toBeUndefined();
});
diff --git a/spec/frontend/badges/components/badge_settings_spec.js b/spec/frontend/badges/components/badge_settings_spec.js
index 8ac5d2c72d9..79cf5f3e4ff 100644
--- a/spec/frontend/badges/components/badge_settings_spec.js
+++ b/spec/frontend/badges/components/badge_settings_spec.js
@@ -1,6 +1,6 @@
import { GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import BadgeList from '~/badges/components/badge_list.vue';
import BadgeListRow from '~/badges/components/badge_list_row.vue';
@@ -40,7 +40,7 @@ describe('BadgeSettings component', () => {
const button = wrapper.find('[data-testid="delete-badge"]');
button.vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
const modal = wrapper.find(GlModal);
expect(modal.isVisible()).toBe(true);
diff --git a/spec/frontend/batch_comments/components/draft_note_spec.js b/spec/frontend/batch_comments/components/draft_note_spec.js
index 9c0d59c4443..f93daf91c2f 100644
--- a/spec/frontend/batch_comments/components/draft_note_spec.js
+++ b/spec/frontend/batch_comments/components/draft_note_spec.js
@@ -1,5 +1,6 @@
import { getByRole } from '@testing-library/dom';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { stubComponent } from 'helpers/stub_component';
import DraftNote from '~/batch_comments/components/draft_note.vue';
import { createStore } from '~/batch_comments/stores';
@@ -71,45 +72,37 @@ describe('Batch comments draft note component', () => {
);
});
- it('sets as loading when draft is publishing', (done) => {
+ it('sets as loading when draft is publishing', async () => {
createComponent();
wrapper.vm.$store.state.batchComments.currentlyPublishingDrafts.push(1);
- wrapper.vm.$nextTick(() => {
- const publishNowButton = wrapper.find({ ref: 'publishNowButton' });
-
- expect(publishNowButton.props().loading).toBe(true);
+ await nextTick();
+ const publishNowButton = wrapper.find({ ref: 'publishNowButton' });
- done();
- });
+ expect(publishNowButton.props().loading).toBe(true);
});
});
describe('update', () => {
- it('dispatches updateDraft', (done) => {
+ it('dispatches updateDraft', async () => {
createComponent();
const note = wrapper.find(NoteableNote);
note.vm.$emit('handleEdit');
- wrapper.vm
- .$nextTick()
- .then(() => {
- const formData = {
- note: draft,
- noteText: 'a',
- resolveDiscussion: false,
- };
-
- note.vm.$emit('handleUpdateNote', formData);
-
- expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith(
- 'batchComments/updateDraft',
- formData,
- );
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ const formData = {
+ note: draft,
+ noteText: 'a',
+ resolveDiscussion: false,
+ };
+
+ note.vm.$emit('handleUpdateNote', formData);
+
+ expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith(
+ 'batchComments/updateDraft',
+ formData,
+ );
});
});
@@ -127,7 +120,7 @@ describe('Batch comments draft note component', () => {
});
describe('quick actions', () => {
- it('renders referenced commands', (done) => {
+ it('renders referenced commands', async () => {
createComponent();
wrapper.setProps({
draft: {
@@ -138,14 +131,11 @@ describe('Batch comments draft note component', () => {
},
});
- wrapper.vm.$nextTick(() => {
- const referencedCommands = wrapper.find('.referenced-commands');
+ await nextTick();
+ const referencedCommands = wrapper.find('.referenced-commands');
- expect(referencedCommands.exists()).toBe(true);
- expect(referencedCommands.text()).toContain('test command');
-
- done();
- });
+ expect(referencedCommands.exists()).toBe(true);
+ expect(referencedCommands.text()).toContain('test command');
});
});
diff --git a/spec/frontend/batch_comments/components/drafts_count_spec.js b/spec/frontend/batch_comments/components/drafts_count_spec.js
index 5f74de9c014..390ef21929c 100644
--- a/spec/frontend/batch_comments/components/drafts_count_spec.js
+++ b/spec/frontend/batch_comments/components/drafts_count_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import DraftsCount from '~/batch_comments/components/drafts_count.vue';
import { createStore } from '~/batch_comments/stores';
@@ -27,17 +27,14 @@ describe('Batch comments drafts count component', () => {
expect(vm.$el.textContent).toContain('1');
});
- it('renders screen reader text', (done) => {
+ it('renders screen reader text', async () => {
const el = vm.$el.querySelector('.sr-only');
expect(el.textContent).toContain('draft');
vm.$store.state.batchComments.drafts.push('comment 2');
- vm.$nextTick(() => {
- expect(el.textContent).toContain('drafts');
-
- done();
- });
+ await nextTick();
+ expect(el.textContent).toContain('drafts');
});
});
diff --git a/spec/frontend/batch_comments/components/publish_button_spec.js b/spec/frontend/batch_comments/components/publish_button_spec.js
index eca424814b4..9a782ec09b6 100644
--- a/spec/frontend/batch_comments/components/publish_button_spec.js
+++ b/spec/frontend/batch_comments/components/publish_button_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import PublishButton from '~/batch_comments/components/publish_button.vue';
import { createStore } from '~/batch_comments/stores';
@@ -29,13 +29,10 @@ describe('Batch comments publish button component', () => {
expect(vm.$store.dispatch).toHaveBeenCalledWith('batchComments/publishReview', undefined);
});
- it('sets loading when isPublishing is true', (done) => {
+ it('sets loading when isPublishing is true', async () => {
vm.$store.state.batchComments.isPublishing = true;
- vm.$nextTick(() => {
- expect(vm.$el.getAttribute('disabled')).toBe('disabled');
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.getAttribute('disabled')).toBe('disabled');
});
});
diff --git a/spec/frontend/blob/components/blob_edit_header_spec.js b/spec/frontend/blob/components/blob_edit_header_spec.js
index 910fc5c946d..b1ce0e9a4c5 100644
--- a/spec/frontend/blob/components/blob_edit_header_spec.js
+++ b/spec/frontend/blob/components/blob_edit_header_spec.js
@@ -1,5 +1,6 @@
import { GlFormInput, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import BlobEditHeader from '~/blob/components/blob_edit_header.vue';
describe('Blob Header Editing', () => {
@@ -40,7 +41,7 @@ describe('Blob Header Editing', () => {
});
describe('functionality', () => {
- it('emits input event when the blob name is changed', () => {
+ it('emits input event when the blob name is changed', async () => {
const inputComponent = wrapper.find(GlFormInput);
const newValue = 'bar.txt';
@@ -51,9 +52,8 @@ describe('Blob Header Editing', () => {
});
inputComponent.vm.$emit('change');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().input[0]).toEqual([newValue]);
- });
+ await nextTick();
+ expect(wrapper.emitted().input[0]).toEqual([newValue]);
});
});
diff --git a/spec/frontend/blob/components/blob_header_spec.js b/spec/frontend/blob/components/blob_header_spec.js
index bd81b1594bf..8e1b03c6126 100644
--- a/spec/frontend/blob/components/blob_header_spec.js
+++ b/spec/frontend/blob/components/blob_header_spec.js
@@ -1,4 +1,5 @@
import { shallowMount, mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import BlobHeader from '~/blob/components/blob_header.vue';
import DefaultActions from '~/blob/components/blob_header_default_actions.vue';
import BlobFilepath from '~/blob/components/blob_header_filepath.vue';
@@ -139,26 +140,24 @@ describe('Blob Header Default Actions', () => {
expect(wrapper.vm.viewer).toBe(null);
});
- it('watches the changes in viewer data and emits event when the change is registered', () => {
+ it('watches the changes in viewer data and emits event when the change is registered', async () => {
factory();
jest.spyOn(wrapper.vm, '$emit');
wrapper.vm.viewer = newViewer;
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('viewer-changed', newViewer);
- });
+ await nextTick();
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('viewer-changed', newViewer);
});
- it('does not emit event if the switcher is not rendered', () => {
+ it('does not emit event if the switcher is not rendered', async () => {
factory(true);
expect(wrapper.vm.showViewerSwitcher).toBe(false);
jest.spyOn(wrapper.vm, '$emit');
wrapper.vm.viewer = newViewer;
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.$emit).not.toHaveBeenCalled();
- });
+ await nextTick();
+ expect(wrapper.vm.$emit).not.toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js b/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js
index 9a560ec11f7..91baaf3ea69 100644
--- a/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js
+++ b/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js
@@ -1,5 +1,6 @@
import { GlButtonGroup, GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import BlobHeaderViewerSwitcher from '~/blob/components/blob_header_viewer_switcher.vue';
import {
RICH_BLOB_VIEWER,
@@ -72,26 +73,24 @@ describe('Blob Header Viewer Switcher', () => {
expect(wrapper.vm.$emit).not.toHaveBeenCalled();
});
- it('emits an event when a Rich Viewer button is clicked', () => {
+ it('emits an event when a Rich Viewer button is clicked', async () => {
factory();
expect(wrapper.vm.value).toBe(SIMPLE_BLOB_VIEWER);
richBtn.vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', RICH_BLOB_VIEWER);
- });
+ await nextTick();
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', RICH_BLOB_VIEWER);
});
- it('emits an event when a Simple Viewer button is clicked', () => {
+ it('emits an event when a Simple Viewer button is clicked', async () => {
factory({
value: RICH_BLOB_VIEWER,
});
simpleBtn.vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', SIMPLE_BLOB_VIEWER);
- });
+ await nextTick();
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', SIMPLE_BLOB_VIEWER);
});
});
});
diff --git a/spec/frontend/boards/board_card_inner_spec.js b/spec/frontend/boards/board_card_inner_spec.js
index e0446811f64..6de549718d9 100644
--- a/spec/frontend/boards/board_card_inner_spec.js
+++ b/spec/frontend/boards/board_card_inner_spec.js
@@ -1,6 +1,7 @@
import { GlLabel, GlLoadingIcon, GlTooltip } from '@gitlab/ui';
import { range } from 'lodash';
import Vuex from 'vuex';
+import { nextTick } from 'vue';
import setWindowLocation from 'helpers/set_window_location_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { mountExtended } from 'helpers/vue_test_utils_helper';
@@ -261,7 +262,7 @@ describe('Board card component', () => {
],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('.board-card-assignee img').attributes('src')).toBe(
'test_image_from_avatar_url?width=24',
@@ -376,7 +377,7 @@ describe('Board card component', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('.board-card-assignee .avatar-counter').text().trim()).toEqual('99+');
});
@@ -399,7 +400,7 @@ describe('Board card component', () => {
it('does not render label if label does not have an ID', async () => {
wrapper.setProps({ item: { ...issue, labels: [label1, { title: 'closed' }] } });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.findAll(GlLabel).length).toBe(1);
expect(wrapper.text()).not.toContain('closed');
diff --git a/spec/frontend/boards/board_list_spec.js b/spec/frontend/boards/board_list_spec.js
index e29c19d6e97..d07173c8411 100644
--- a/spec/frontend/boards/board_list_spec.js
+++ b/spec/frontend/boards/board_list_spec.js
@@ -1,4 +1,5 @@
import Draggable from 'vuedraggable';
+import { nextTick } from 'vue';
import { DraggableItemTypes } from 'ee_else_ce/boards/constants';
import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_frame';
import waitForPromises from 'helpers/wait_for_promises';
@@ -65,14 +66,14 @@ describe('Board list component', () => {
it('shows new issue form', async () => {
wrapper.vm.toggleForm();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('.board-new-issue-form').exists()).toBe(true);
});
it('shows new issue form after eventhub event', async () => {
eventHub.$emit(`toggle-issue-form-${wrapper.vm.list.id}`);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('.board-new-issue-form').exists()).toBe(true);
});
@@ -86,7 +87,7 @@ describe('Board list component', () => {
it('shows count list item', async () => {
wrapper.vm.showCount = true;
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('.board-list-count').exists()).toBe(true);
expect(wrapper.find('.board-list-count').text()).toBe('Showing all issues');
@@ -95,7 +96,7 @@ describe('Board list component', () => {
it('sets data attribute with invalid id', async () => {
wrapper.vm.showCount = true;
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('.board-list-count').attributes('data-issue-id')).toBe('-1');
});
});
@@ -127,7 +128,7 @@ describe('Board list component', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findIssueCountLoadingIcon().exists()).toBe(true);
});
@@ -139,9 +140,9 @@ describe('Board list component', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('.board-list-count').text()).toBe('Showing 1 of 20 issues');
});
@@ -158,7 +159,7 @@ describe('Board list component', () => {
it('sets background to bg-danger-100', async () => {
wrapper.setProps({ list: { issuesCount: 4, maxIssueCount: 3 } });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('.bg-danger-100').exists()).toBe(true);
});
});
diff --git a/spec/frontend/boards/components/board_add_new_column_trigger_spec.js b/spec/frontend/boards/components/board_add_new_column_trigger_spec.js
index c35f2463f69..7dd02bf1d35 100644
--- a/spec/frontend/boards/components/board_add_new_column_trigger_spec.js
+++ b/spec/frontend/boards/components/board_add_new_column_trigger_spec.js
@@ -1,5 +1,5 @@
import { GlButton } from '@gitlab/ui';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_trigger.vue';
@@ -49,7 +49,7 @@ describe('BoardAddNewColumnTrigger', () => {
it('shows the tooltip', async () => {
wrapper.find(GlButton).vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
const tooltip = findTooltipText();
diff --git a/spec/frontend/boards/components/board_blocked_icon_spec.js b/spec/frontend/boards/components/board_blocked_icon_spec.js
index 7b04942f056..7a5c49bd488 100644
--- a/spec/frontend/boards/components/board_blocked_icon_spec.js
+++ b/spec/frontend/boards/components/board_blocked_icon_spec.js
@@ -1,6 +1,6 @@
import { GlIcon, GlLink, GlPopover, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
@@ -39,7 +39,7 @@ describe('BoardBlockedIcon', () => {
const mouseenter = async () => {
findGlIcon().vm.$emit('mouseenter');
- await wrapper.vm.$nextTick();
+ await nextTick();
await waitForApollo();
};
diff --git a/spec/frontend/boards/components/board_card_spec.js b/spec/frontend/boards/components/board_card_spec.js
index 3af173aa18c..aad89cf8261 100644
--- a/spec/frontend/boards/components/board_card_spec.js
+++ b/spec/frontend/boards/components/board_card_spec.js
@@ -1,6 +1,6 @@
import { GlLabel } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import BoardCard from '~/boards/components/board_card.vue';
@@ -65,12 +65,12 @@ describe('Board card', () => {
const selectCard = async () => {
wrapper.trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
};
const multiSelectCard = async () => {
wrapper.trigger('click', { ctrlKey: true });
- await wrapper.vm.$nextTick();
+ await nextTick();
};
beforeEach(() => {
diff --git a/spec/frontend/boards/components/board_list_header_spec.js b/spec/frontend/boards/components/board_list_header_spec.js
index 8cc0ad5f30c..14870ec76a2 100644
--- a/spec/frontend/boards/components/board_list_header_spec.js
+++ b/spec/frontend/boards/components/board_list_header_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import Vuex from 'vuex';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -148,7 +148,7 @@ describe('Board List Header Component', () => {
findCaret().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(toggleListCollapsedSpy).toHaveBeenCalledTimes(1);
});
@@ -156,7 +156,7 @@ describe('Board List Header Component', () => {
createComponent({ withLocalStorage: false, currentUserId: 1 });
findCaret().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(updateListSpy).toHaveBeenCalledTimes(1);
expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.collapsed`)).toBe(null);
@@ -168,7 +168,7 @@ describe('Board List Header Component', () => {
});
findCaret().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(updateListSpy).not.toHaveBeenCalled();
expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.collapsed`)).toBe(String(isCollapsed()));
diff --git a/spec/frontend/boards/components/board_new_issue_spec.js b/spec/frontend/boards/components/board_new_issue_spec.js
index 12bee842c85..08ef119aad8 100644
--- a/spec/frontend/boards/components/board_new_issue_spec.js
+++ b/spec/frontend/boards/components/board_new_issue_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 BoardNewIssue from '~/boards/components/board_new_issue.vue';
import BoardNewItem from '~/boards/components/board_new_item.vue';
@@ -45,7 +45,7 @@ describe('Issue boards new issue form', () => {
beforeEach(async () => {
wrapper = createComponent();
- await wrapper.vm.$nextTick();
+ await nextTick();
});
afterEach(() => {
@@ -66,7 +66,7 @@ describe('Issue boards new issue form', () => {
it('calls addListNewIssue action when `board-new-item` emits form-submit event', async () => {
findBoardNewItem().vm.$emit('form-submit', { title: 'Foo' });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(addListNewIssuesSpy).toHaveBeenCalledWith(expect.any(Object), {
list: mockList,
issueInput: {
@@ -83,7 +83,7 @@ describe('Issue boards new issue form', () => {
jest.spyOn(eventHub, '$emit').mockImplementation();
findBoardNewItem().vm.$emit('form-cancel');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(eventHub.$emit).toHaveBeenCalledWith(`toggle-issue-form-${mockList.id}`);
});
diff --git a/spec/frontend/boards/components/board_new_item_spec.js b/spec/frontend/boards/components/board_new_item_spec.js
index ef83fc925bd..86cebc8a719 100644
--- a/spec/frontend/boards/components/board_new_item_spec.js
+++ b/spec/frontend/boards/components/board_new_item_spec.js
@@ -1,4 +1,5 @@
import { GlForm, GlFormInput, GlButton } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import BoardNewItem from '~/boards/components/board_new_item.vue';
@@ -44,7 +45,7 @@ describe('BoardNewItem', () => {
expect(wrapper.findByTestId('create-button').props('disabled')).toBe(true);
wrapper.find(GlFormInput).vm.$emit('input', 'hello');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.findByTestId('create-button').props('disabled')).toBe(false);
});
@@ -54,7 +55,7 @@ describe('BoardNewItem', () => {
it('disables the Create Issue button', async () => {
wrapper.find(GlFormInput).vm.$emit('input', ' ');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.findByTestId('create-button').props('disabled')).toBe(true);
});
@@ -124,7 +125,7 @@ describe('BoardNewItem', () => {
it('emits `form-cancel` event and clears title value when `reset` is triggered on gl-form', async () => {
titleInput().setValue('Foo');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(titleInput().element.value).toBe('Foo');
await glForm().trigger('reset');
diff --git a/spec/frontend/boards/components/board_settings_sidebar_spec.js b/spec/frontend/boards/components/board_settings_sidebar_spec.js
index 46dd109ffb1..842c8fd131e 100644
--- a/spec/frontend/boards/components/board_settings_sidebar_spec.js
+++ b/spec/frontend/boards/components/board_settings_sidebar_spec.js
@@ -1,7 +1,7 @@
import { GlDrawer, GlLabel } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { MountingPortal } from 'portal-vue';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { stubComponent } from 'helpers/stub_component';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
@@ -86,7 +86,7 @@ describe('BoardSettingsSidebar', () => {
findDrawer().vm.$emit('close');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlDrawer).exists()).toBe(false);
});
@@ -96,7 +96,7 @@ describe('BoardSettingsSidebar', () => {
sidebarEventHub.$emit('sidebar.closeAll');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlDrawer).exists()).toBe(false);
});
diff --git a/spec/frontend/boards/components/sidebar/board_editable_item_spec.js b/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
index 12e9a9ba365..0c76c711b3a 100644
--- a/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
@@ -1,5 +1,6 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import BoardSidebarItem from '~/boards/components/sidebar/board_editable_item.vue';
describe('boards sidebar remove issue', () => {
@@ -79,17 +80,16 @@ describe('boards sidebar remove issue', () => {
createComponent({ canUpdate: true, slots });
findEditButton().vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(findCollapsed().isVisible()).toBe(false);
- expect(findExpanded().isVisible()).toBe(true);
- });
+ await nextTick();
+ expect(findCollapsed().isVisible()).toBe(false);
+ expect(findExpanded().isVisible()).toBe(true);
});
it('hides the header while editing if `toggleHeader` is true', async () => {
createComponent({ canUpdate: true, props: { toggleHeader: true } });
findEditButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findEditButton().isVisible()).toBe(false);
expect(findTitle().isVisible()).toBe(false);
@@ -101,14 +101,14 @@ describe('boards sidebar remove issue', () => {
beforeEach(async () => {
createComponent({ canUpdate: true });
findEditButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('hides expanded section and displays collapsed section', async () => {
expect(findExpanded().isVisible()).toBe(true);
document.body.click();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findCollapsed().isVisible()).toBe(true);
expect(findExpanded().isVisible()).toBe(false);
@@ -117,7 +117,7 @@ describe('boards sidebar remove issue', () => {
it('emits events', async () => {
document.body.click();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted().close).toHaveLength(1);
expect(wrapper.emitted()['off-click']).toHaveLength(1);
@@ -129,7 +129,7 @@ describe('boards sidebar remove issue', () => {
findEditButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted().open.length).toBe(1);
});
@@ -139,7 +139,7 @@ describe('boards sidebar remove issue', () => {
findEditButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
wrapper.vm.collapse({ emitEvent: false });
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js
index 4a8eda298f2..5364d929c38 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js
@@ -1,5 +1,6 @@
import { GlAlert, GlFormInput, GlForm } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { createStore } from '~/boards/stores';
@@ -75,7 +76,7 @@ describe('~/boards/components/sidebar/board_sidebar_title.vue', () => {
});
findFormInput().vm.$emit('input', TEST_TITLE);
findForm().vm.$emit('submit', { preventDefault: () => {} });
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('collapses sidebar and renders new title', () => {
@@ -98,7 +99,7 @@ describe('~/boards/components/sidebar/board_sidebar_title.vue', () => {
jest.spyOn(wrapper.vm, 'setActiveItemTitle').mockImplementation(() => {});
findFormInput().vm.$emit('input', '');
findForm().vm.$emit('submit', { preventDefault: () => {} });
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('commits change to the server', () => {
@@ -113,7 +114,7 @@ describe('~/boards/components/sidebar/board_sidebar_title.vue', () => {
wrapper.vm.$refs.sidebarItem.expand();
findFormInput().vm.$emit('input', TEST_TITLE);
findEditableItem().vm.$emit('off-click');
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('does not collapses sidebar and shows alert', () => {
@@ -148,7 +149,7 @@ describe('~/boards/components/sidebar/board_sidebar_title.vue', () => {
});
findFormInput().vm.$emit('input', TEST_TITLE);
findCancelButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('collapses sidebar and render former title', () => {
@@ -168,7 +169,7 @@ describe('~/boards/components/sidebar/board_sidebar_title.vue', () => {
jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
findFormInput().vm.$emit('input', 'Invalid title');
findForm().vm.$emit('submit', { preventDefault: () => {} });
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('collapses sidebar and renders former item title', () => {
diff --git a/spec/frontend/boards/project_select_spec.js b/spec/frontend/boards/project_select_spec.js
index de823094630..05dc7d28eaa 100644
--- a/spec/frontend/boards/project_select_spec.js
+++ b/spec/frontend/boards/project_select_spec.js
@@ -1,6 +1,6 @@
import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import ProjectSelect from '~/boards/components/project_select.vue';
import defaultState from '~/boards/stores/state';
@@ -88,7 +88,7 @@ describe('ProjectSelect component', () => {
expect(findGlDropdownLoadingIcon().exists()).toBe(true);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findGlDropdownLoadingIcon().exists()).toBe(false);
});
diff --git a/spec/frontend/ci_lint/components/ci_lint_spec.js b/spec/frontend/ci_lint/components/ci_lint_spec.js
index c4b2927764e..0ad6ed56b0e 100644
--- a/spec/frontend/ci_lint/components/ci_lint_spec.js
+++ b/spec/frontend/ci_lint/components/ci_lint_spec.js
@@ -1,5 +1,6 @@
import { GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import CiLint from '~/ci_lint/components/ci_lint.vue';
import CiLintResults from '~/pipeline_editor/components/lint/ci_lint_results.vue';
@@ -81,7 +82,7 @@ describe('CI Lint', () => {
it('validation displays results', async () => {
findValidateBtn().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findValidateBtn().props('loading')).toBe(true);
@@ -96,7 +97,7 @@ describe('CI Lint', () => {
findValidateBtn().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findValidateBtn().props('loading')).toBe(true);
diff --git a/spec/frontend/ci_settings_pipeline_triggers/components/triggers_list_spec.js b/spec/frontend/ci_settings_pipeline_triggers/components/triggers_list_spec.js
index 41af257ad89..6bf28a67300 100644
--- a/spec/frontend/ci_settings_pipeline_triggers/components/triggers_list_spec.js
+++ b/spec/frontend/ci_settings_pipeline_triggers/components/triggers_list_spec.js
@@ -1,5 +1,6 @@
import { GlTable, GlBadge } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import TriggersList from '~/ci_settings_pipeline_triggers/components/triggers_list.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
@@ -25,10 +26,10 @@ describe('TriggersList', () => {
const findEditBtn = (i) => findRowAt(i).find('[data-testid="edit-btn"]');
const findRevokeBtn = (i) => findRowAt(i).find('[data-testid="trigger_revoke_button"]');
- beforeEach(() => {
+ beforeEach(async () => {
createComponent();
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays a table with expected headers', () => {
diff --git a/spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js b/spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js
index f4a0eef1129..e7e4897abfa 100644
--- a/spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js
+++ b/spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js
@@ -1,6 +1,6 @@
import { GlDropdown, GlDropdownItem, GlIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import CiEnvironmentsDropdown from '~/ci_variable_list/components/ci_environments_dropdown.vue';
@@ -73,7 +73,7 @@ describe('Ci environments dropdown', () => {
describe('Environments found', () => {
beforeEach(async () => {
createComponent('prod');
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders only the environment searched for', () => {
@@ -110,7 +110,7 @@ describe('Ci environments dropdown', () => {
it('should emit createClicked if an environment is clicked', async () => {
createComponent('newscope');
- await wrapper.vm.$nextTick();
+ await nextTick();
findDropdownItemByIndex(1).vm.$emit('click');
expect(wrapper.emitted('createClicked')).toEqual([['newscope']]);
});
diff --git a/spec/frontend/ci_variable_list/components/ci_variable_table_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_table_spec.js
index 2469569b436..ecbbd78b87d 100644
--- a/spec/frontend/ci_variable_list/components/ci_variable_table_spec.js
+++ b/spec/frontend/ci_variable_list/components/ci_variable_table_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import CiVariableTable from '~/ci_variable_list/components/ci_variable_table.vue';
import createStore from '~/ci_variable_list/store';
@@ -41,13 +41,12 @@ describe('Ci variable table', () => {
expect(findEmptyVariablesPlaceholder().exists()).toBe(true);
});
- it('displays correct amount of variables present and no empty message', () => {
+ it('displays correct amount of variables present and no empty message', async () => {
store.state.variables = mockData.mockVariables;
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.findAll('.js-ci-variable-row').length).toBe(1);
- expect(findEmptyVariablesPlaceholder().exists()).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.findAll('.js-ci-variable-row').length).toBe(1);
+ expect(findEmptyVariablesPlaceholder().exists()).toBe(false);
});
});
diff --git a/spec/frontend/clusters/components/new_cluster_spec.js b/spec/frontend/clusters/components/new_cluster_spec.js
index e4bca5eaaa5..b73442f6ec3 100644
--- a/spec/frontend/clusters/components/new_cluster_spec.js
+++ b/spec/frontend/clusters/components/new_cluster_spec.js
@@ -1,5 +1,6 @@
import { GlLink, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import NewCluster from '~/clusters/components/new_cluster.vue';
import createClusterStore from '~/clusters/stores/new_cluster';
@@ -7,10 +8,10 @@ describe('NewCluster', () => {
let store;
let wrapper;
- const createWrapper = () => {
+ const createWrapper = async () => {
store = createClusterStore({ clusterConnectHelpPath: '/some/help/path' });
wrapper = shallowMount(NewCluster, { store, stubs: { GlLink, GlSprintf } });
- return wrapper.vm.$nextTick();
+ await nextTick();
};
const findDescription = () => wrapper.find(GlSprintf);
diff --git a/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js b/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js
index 41bd492148e..173fefe6167 100644
--- a/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js
+++ b/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js
@@ -1,5 +1,6 @@
import { GlModal, GlSprintf } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { stubComponent } from 'helpers/stub_component';
import RemoveClusterConfirmation from '~/clusters/components/remove_cluster_confirmation.vue';
import SplitButton from '~/vue_shared/components/split_button.vue';
@@ -43,7 +44,7 @@ describe('Remove cluster confirmation modal', () => {
it('opens modal with "cleanup" option', async () => {
findSplitButton().vm.$emit('remove-cluster-and-cleanup');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findModal().vm.show).toHaveBeenCalled();
expect(wrapper.vm.confirmCleanup).toEqual(true);
@@ -55,7 +56,7 @@ describe('Remove cluster confirmation modal', () => {
it('opens modal without "cleanup" option', async () => {
findSplitButton().vm.$emit('remove-cluster');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findModal().vm.show).toHaveBeenCalled();
expect(wrapper.vm.confirmCleanup).toEqual(false);
diff --git a/spec/frontend/clusters/forms/components/integration_form_spec.js b/spec/frontend/clusters/forms/components/integration_form_spec.js
index a9fb5c205fd..dd278bcd2ce 100644
--- a/spec/frontend/clusters/forms/components/integration_form_spec.js
+++ b/spec/frontend/clusters/forms/components/integration_form_spec.js
@@ -1,6 +1,6 @@
import { GlToggle, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import IntegrationForm from '~/clusters/forms/components/integration_form.vue';
import { createStore } from '~/clusters/forms/stores/index';
@@ -75,32 +75,22 @@ describe('ClusterIntegrationForm', () => {
describe('reactivity', () => {
beforeEach(() => createWrapper());
- it('enables the submit button on changing toggle to different value', () => {
- return wrapper.vm
- .$nextTick()
- .then(() => {
- // setData is a bad approach because it changes the internal implementation which we should not touch
- // but our GlFormInput lacks the ability to set a new value.
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ toggleEnabled: !defaultStoreValues.enabled });
- })
- .then(() => {
- expect(findSubmitButton().props('disabled')).toBe(false);
- });
+ it('enables the submit button on changing toggle to different value', async () => {
+ await nextTick();
+ // setData is a bad approach because it changes the internal implementation which we should not touch
+ // but our GlFormInput lacks the ability to set a new value.
+ // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
+ // eslint-disable-next-line no-restricted-syntax
+ await wrapper.setData({ toggleEnabled: !defaultStoreValues.enabled });
+ expect(findSubmitButton().props('disabled')).toBe(false);
});
- it('enables the submit button on changing input values', () => {
- return wrapper.vm
- .$nextTick()
- .then(() => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ envScope: `${defaultStoreValues.environmentScope}1` });
- })
- .then(() => {
- expect(findSubmitButton().props('disabled')).toBe(false);
- });
+ it('enables the submit button on changing input values', async () => {
+ await nextTick();
+ // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
+ // eslint-disable-next-line no-restricted-syntax
+ await wrapper.setData({ envScope: `${defaultStoreValues.environmentScope}1` });
+ expect(findSubmitButton().props('disabled')).toBe(false);
});
});
});
diff --git a/spec/frontend/clusters_list/components/agent_options_spec.js b/spec/frontend/clusters_list/components/agent_options_spec.js
index f20a7b39a97..1d973ab39cc 100644
--- a/spec/frontend/clusters_list/components/agent_options_spec.js
+++ b/spec/frontend/clusters_list/components/agent_options_spec.js
@@ -1,5 +1,5 @@
import { GlDropdown, GlDropdownItem, GlModal, GlFormInput } from '@gitlab/ui';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { ENTER_KEY } from '~/lib/utils/keys';
@@ -54,7 +54,7 @@ describe('AgentOptions', () => {
});
};
- const createWrapper = ({ mutationResponse = mockDeleteResponse } = {}) => {
+ const createWrapper = async ({ mutationResponse = mockDeleteResponse } = {}) => {
apolloProvider = createMockApolloProvider({ mutationResponse });
const provide = {
projectPath,
@@ -77,7 +77,7 @@ describe('AgentOptions', () => {
wrapper.vm.$refs.modal.hide = jest.fn();
writeQuery();
- return wrapper.vm.$nextTick();
+ await nextTick();
};
const submitAgentToDelete = async () => {
diff --git a/spec/frontend/clusters_list/components/agents_spec.js b/spec/frontend/clusters_list/components/agents_spec.js
index c9ca10f6bf7..5d7a6a251d2 100644
--- a/spec/frontend/clusters_list/components/agents_spec.js
+++ b/spec/frontend/clusters_list/components/agents_spec.js
@@ -1,6 +1,7 @@
import { GlAlert, GlKeysetPagination, GlLoadingIcon } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
+import { nextTick } from 'vue';
import AgentEmptyState from '~/clusters_list/components/agent_empty_state.vue';
import AgentTable from '~/clusters_list/components/agent_table.vue';
import Agents from '~/clusters_list/components/agents.vue';
@@ -21,7 +22,13 @@ describe('Agents', () => {
projectPath: 'path/to/project',
};
- const createWrapper = ({ props = {}, agents = [], pageInfo = null, trees = [], count = 0 }) => {
+ const createWrapper = async ({
+ props = {},
+ agents = [],
+ pageInfo = null,
+ trees = [],
+ count = 0,
+ }) => {
const provide = provideData;
const apolloQueryResponse = {
data: {
@@ -47,7 +54,7 @@ describe('Agents', () => {
provide: provideData,
});
- return wrapper.vm.$nextTick();
+ await nextTick();
};
const findAgentTable = () => wrapper.find(AgentTable);
@@ -239,14 +246,14 @@ describe('Agents', () => {
},
};
- beforeEach(() => {
+ beforeEach(async () => {
wrapper = shallowMount(Agents, {
mocks,
propsData: defaultProps,
provide: provideData,
});
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays a loading icon', () => {
diff --git a/spec/frontend/clusters_list/components/ancestor_notice_spec.js b/spec/frontend/clusters_list/components/ancestor_notice_spec.js
index c7ee2a00f5b..a9f11e6fdf8 100644
--- a/spec/frontend/clusters_list/components/ancestor_notice_spec.js
+++ b/spec/frontend/clusters_list/components/ancestor_notice_spec.js
@@ -1,5 +1,6 @@
import { GlLink, GlSprintf, GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import AncestorNotice from '~/clusters_list/components/ancestor_notice.vue';
import ClusterStore from '~/clusters_list/store';
@@ -7,10 +8,10 @@ describe('ClustersAncestorNotice', () => {
let store;
let wrapper;
- const createWrapper = () => {
+ const createWrapper = async () => {
store = ClusterStore({ ancestorHelperPath: '/some/ancestor/path' });
wrapper = shallowMount(AncestorNotice, { store, stubs: { GlSprintf, GlAlert } });
- return wrapper.vm.$nextTick();
+ await nextTick();
};
beforeEach(() => {
@@ -22,9 +23,9 @@ describe('ClustersAncestorNotice', () => {
});
describe('when cluster does not have ancestors', () => {
- beforeEach(() => {
+ beforeEach(async () => {
store.state.hasAncestorClusters = false;
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays no notice', () => {
@@ -33,9 +34,9 @@ describe('ClustersAncestorNotice', () => {
});
describe('when cluster has ancestors', () => {
- beforeEach(() => {
+ beforeEach(async () => {
store.state.hasAncestorClusters = true;
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays notice text', () => {
diff --git a/spec/frontend/clusters_list/components/clusters_spec.js b/spec/frontend/clusters_list/components/clusters_spec.js
index 9af25a534d8..82e667093aa 100644
--- a/spec/frontend/clusters_list/components/clusters_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_spec.js
@@ -7,6 +7,7 @@ import {
import * as Sentry from '@sentry/browser';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import Clusters from '~/clusters_list/components/clusters.vue';
import ClustersEmptyState from '~/clusters_list/components/clusters_empty_state.vue';
import ClusterStore from '~/clusters_list/store';
@@ -176,9 +177,9 @@ describe('Clusters', () => {
});
describe('nodes finish loading', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.vm.$store.state.loadingNodes = false;
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it.each`
diff --git a/spec/frontend/clusters_list/components/node_error_help_text_spec.js b/spec/frontend/clusters_list/components/node_error_help_text_spec.js
index 18d27f3fd80..8187ab75c58 100644
--- a/spec/frontend/clusters_list/components/node_error_help_text_spec.js
+++ b/spec/frontend/clusters_list/components/node_error_help_text_spec.js
@@ -1,13 +1,14 @@
import { GlPopover } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import NodeErrorHelpText from '~/clusters_list/components/node_error_help_text.vue';
describe('NodeErrorHelpText', () => {
let wrapper;
- const createWrapper = (propsData) => {
+ const createWrapper = async (propsData) => {
wrapper = shallowMount(NodeErrorHelpText, { propsData, stubs: { GlPopover } });
- return wrapper.vm.$nextTick();
+ await nextTick();
};
const findPopover = () => wrapper.find(GlPopover);
diff --git a/spec/frontend/commit/commit_pipeline_status_component_spec.js b/spec/frontend/commit/commit_pipeline_status_component_spec.js
index 3a549e66eb7..43db6db00c1 100644
--- a/spec/frontend/commit/commit_pipeline_status_component_spec.js
+++ b/spec/frontend/commit/commit_pipeline_status_component_spec.js
@@ -1,6 +1,7 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Visibility from 'visibilityjs';
+import { nextTick } from 'vue';
import fixture from 'test_fixtures/pipelines/pipelines.json';
import createFlash from '~/flash';
import Poll from '~/lib/utils/poll';
@@ -112,7 +113,7 @@ describe('Commit pipeline status component', () => {
createComponent();
});
- it('shows the loading icon at start', () => {
+ it('shows the loading icon at start', async () => {
createComponent();
expect(findLoader().exists()).toBe(true);
@@ -120,17 +121,16 @@ describe('Commit pipeline status component', () => {
data: { pipelines: [] },
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findLoader().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findLoader().exists()).toBe(false);
});
describe('is successful', () => {
- beforeEach(() => {
+ beforeEach(async () => {
pollConfig.successCallback({
data: { pipelines: [{ details: { status: mockCiStatus } }] },
});
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('does not render loader', () => {
diff --git a/spec/frontend/commit/pipelines/pipelines_table_spec.js b/spec/frontend/commit/pipelines/pipelines_table_spec.js
index e209f628aa2..ac432593d2f 100644
--- a/spec/frontend/commit/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/commit/pipelines/pipelines_table_spec.js
@@ -1,6 +1,7 @@
import { GlEmptyState, GlLoadingIcon, GlModal, GlTableLite } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import fixture from 'test_fixtures/pipelines/pipelines.json';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -238,7 +239,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
it('on desktop, shows a security warning modal', async () => {
await findRunPipelineBtn().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findModal()).not.toBeNull();
});
diff --git a/spec/frontend/contributors/component/contributors_spec.js b/spec/frontend/contributors/component/contributors_spec.js
index cb7e13b9fed..bdf3b3636ed 100644
--- a/spec/frontend/contributors/component/contributors_spec.js
+++ b/spec/frontend/contributors/component/contributors_spec.js
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import ContributorsCharts from '~/contributors/components/contributors.vue';
import { createStore } from '~/contributors/stores';
import axios from '~/lib/utils/axios_utils';
@@ -49,20 +49,18 @@ describe('Contributors charts', () => {
expect(axios.get).toHaveBeenCalledWith(endpoint);
});
- it('should display loader whiled loading data', () => {
+ it('should display loader whiled loading data', async () => {
wrapper.vm.$store.state.loading = true;
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find('.contributors-loader').exists()).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find('.contributors-loader').exists()).toBe(true);
});
- it('should render charts when loading completed and there is chart data', () => {
+ it('should render charts when loading completed and there is chart data', async () => {
wrapper.vm.$store.state.loading = false;
wrapper.vm.$store.state.chartData = chartData;
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find('.contributors-loader').exists()).toBe(false);
- expect(wrapper.find('.contributors-charts').exists()).toBe(true);
- expect(wrapper.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(wrapper.find('.contributors-loader').exists()).toBe(false);
+ expect(wrapper.find('.contributors-charts').exists()).toBe(true);
+ expect(wrapper.element).toMatchSnapshot();
});
});
diff --git a/spec/frontend/create_cluster/components/cluster_form_dropdown_spec.js b/spec/frontend/create_cluster/components/cluster_form_dropdown_spec.js
index 4e92fa1df16..2f835867f5f 100644
--- a/spec/frontend/create_cluster/components/cluster_form_dropdown_spec.js
+++ b/spec/frontend/create_cluster/components/cluster_form_dropdown_spec.js
@@ -2,6 +2,7 @@ import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import $ from 'jquery';
+import { nextTick } from 'vue';
import ClusterFormDropdown from '~/create_cluster/components/cluster_form_dropdown.vue';
import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
@@ -18,35 +19,31 @@ describe('ClusterFormDropdown', () => {
afterEach(() => wrapper.destroy());
describe('when initial value is provided', () => {
- it('sets selectedItem to initial value', () => {
+ it('sets selectedItem to initial value', async () => {
wrapper.setProps({ items, value: secondItem.value });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DropdownButton).props('toggleText')).toEqual(secondItem.name);
- });
+ await nextTick();
+ expect(wrapper.find(DropdownButton).props('toggleText')).toEqual(secondItem.name);
});
});
describe('when no item is selected', () => {
- it('displays placeholder text', () => {
+ it('displays placeholder text', async () => {
const placeholder = 'placeholder';
wrapper.setProps({ placeholder });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DropdownButton).props('toggleText')).toEqual(placeholder);
- });
+ await nextTick();
+ expect(wrapper.find(DropdownButton).props('toggleText')).toEqual(placeholder);
});
});
describe('when an item is selected', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.setProps({ items });
-
- return wrapper.vm.$nextTick().then(() => {
- wrapper.findAll('.js-dropdown-item').at(1).trigger('click');
- return wrapper.vm.$nextTick();
- });
+ await nextTick();
+ wrapper.findAll('.js-dropdown-item').at(1).trigger('click');
+ await nextTick();
});
it('emits input event with selected item', () => {
@@ -57,18 +54,16 @@ describe('ClusterFormDropdown', () => {
describe('when multiple items are selected', () => {
const value = ['1'];
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.setProps({ items, multiple: true, value });
- return wrapper.vm
- .$nextTick()
- .then(() => {
- wrapper.findAll('.js-dropdown-item').at(0).trigger('click');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- wrapper.findAll('.js-dropdown-item').at(1).trigger('click');
- return wrapper.vm.$nextTick();
- });
+
+ await nextTick();
+ wrapper.findAll('.js-dropdown-item').at(0).trigger('click');
+
+ await nextTick();
+ wrapper.findAll('.js-dropdown-item').at(1).trigger('click');
+
+ await nextTick();
});
it('emits input event with an array of selected items', () => {
@@ -77,9 +72,9 @@ describe('ClusterFormDropdown', () => {
});
describe('when multiple items can be selected', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.setProps({ items, multiple: true, value: firstItem.value });
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays a checked GlIcon next to the item', () => {
@@ -89,19 +84,18 @@ describe('ClusterFormDropdown', () => {
});
describe('when multiple values can be selected and initial value is null', () => {
- it('emits input event with an array of a single selected item', () => {
+ it('emits input event with an array of a single selected item', async () => {
wrapper.setProps({ items, multiple: true, value: null });
- return wrapper.vm.$nextTick().then(() => {
- wrapper.findAll('.js-dropdown-item').at(0).trigger('click');
+ await nextTick();
+ wrapper.findAll('.js-dropdown-item').at(0).trigger('click');
- expect(wrapper.emitted('input')[0]).toEqual([[firstItem.value]]);
- });
+ expect(wrapper.emitted('input')[0]).toEqual([[firstItem.value]]);
});
});
describe('when an item is selected and has a custom label property', () => {
- it('displays selected item custom label', () => {
+ it('displays selected item custom label', async () => {
const labelProperty = 'customLabel';
const label = 'Name';
const currentValue = '1';
@@ -109,9 +103,8 @@ describe('ClusterFormDropdown', () => {
wrapper.setProps({ labelProperty, items: customLabelItems, value: currentValue });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DropdownButton).props('toggleText')).toEqual(label);
- });
+ await nextTick();
+ expect(wrapper.find(DropdownButton).props('toggleText')).toEqual(label);
});
});
@@ -123,86 +116,79 @@ describe('ClusterFormDropdown', () => {
});
describe('when loading and loadingText is provided', () => {
- it('uses loading text as toggle button text', () => {
+ it('uses loading text as toggle button text', async () => {
const loadingText = 'loading text';
wrapper.setProps({ loading: true, loadingText });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DropdownButton).props('toggleText')).toEqual(loadingText);
- });
+ await nextTick();
+ expect(wrapper.find(DropdownButton).props('toggleText')).toEqual(loadingText);
});
});
describe('when disabled', () => {
- it('dropdown button isDisabled', () => {
+ it('dropdown button isDisabled', async () => {
wrapper.setProps({ disabled: true });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DropdownButton).props('isDisabled')).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find(DropdownButton).props('isDisabled')).toBe(true);
});
});
describe('when disabled and disabledText is provided', () => {
- it('uses disabled text as toggle button text', () => {
+ it('uses disabled text as toggle button text', async () => {
const disabledText = 'disabled text';
wrapper.setProps({ disabled: true, disabledText });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DropdownButton).props('toggleText')).toBe(disabledText);
- });
+ await nextTick();
+ expect(wrapper.find(DropdownButton).props('toggleText')).toBe(disabledText);
});
});
describe('when has errors', () => {
- it('sets border-danger class selector to dropdown toggle', () => {
+ it('sets border-danger class selector to dropdown toggle', async () => {
wrapper.setProps({ hasErrors: true });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DropdownButton).classes('border-danger')).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find(DropdownButton).classes('border-danger')).toBe(true);
});
});
describe('when has errors and an error message', () => {
- it('displays error message', () => {
+ it('displays error message', async () => {
const errorMessage = 'error message';
wrapper.setProps({ hasErrors: true, errorMessage });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find('.js-eks-dropdown-error-message').text()).toEqual(errorMessage);
- });
+ await nextTick();
+ expect(wrapper.find('.js-eks-dropdown-error-message').text()).toEqual(errorMessage);
});
});
describe('when no results are available', () => {
- it('displays empty text', () => {
+ it('displays empty text', async () => {
const emptyText = 'error message';
wrapper.setProps({ items: [], emptyText });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find('.js-empty-text').text()).toEqual(emptyText);
- });
+ await nextTick();
+ expect(wrapper.find('.js-empty-text').text()).toEqual(emptyText);
});
});
- it('displays search field placeholder', () => {
+ it('displays search field placeholder', async () => {
const searchFieldPlaceholder = 'Placeholder';
wrapper.setProps({ searchFieldPlaceholder });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DropdownSearchInput).props('placeholderText')).toEqual(
- searchFieldPlaceholder,
- );
- });
+ await nextTick();
+ expect(wrapper.find(DropdownSearchInput).props('placeholderText')).toEqual(
+ searchFieldPlaceholder,
+ );
});
- it('it filters results by search query', () => {
+ it('it filters results by search query', async () => {
const searchQuery = secondItem.name;
wrapper.setProps({ items });
@@ -210,21 +196,19 @@ describe('ClusterFormDropdown', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ searchQuery });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.findAll('.js-dropdown-item').length).toEqual(1);
- expect(wrapper.find('.js-dropdown-item').text()).toEqual(secondItem.name);
- });
+ await nextTick();
+ expect(wrapper.findAll('.js-dropdown-item').length).toEqual(1);
+ expect(wrapper.find('.js-dropdown-item').text()).toEqual(secondItem.name);
});
- it('focuses dropdown search input when dropdown is displayed', () => {
+ it('focuses dropdown search input when dropdown is displayed', async () => {
const dropdownEl = wrapper.find('.dropdown').element;
expect(wrapper.find(DropdownSearchInput).props('focused')).toBe(false);
$(dropdownEl).trigger('shown.bs.dropdown');
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find(DropdownSearchInput).props('focused')).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find(DropdownSearchInput).props('focused')).toBe(true);
});
});
diff --git a/spec/frontend/create_cluster/eks_cluster/components/service_credentials_form_spec.js b/spec/frontend/create_cluster/eks_cluster/components/service_credentials_form_spec.js
index a5ac7d5187b..0d823a18012 100644
--- a/spec/frontend/create_cluster/eks_cluster/components/service_credentials_form_spec.js
+++ b/spec/frontend/create_cluster/eks_cluster/components/service_credentials_form_spec.js
@@ -1,6 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import ServiceCredentialsForm from '~/create_cluster/eks_cluster/components/service_credentials_form.vue';
import eksClusterState from '~/create_cluster/eks_cluster/store/state';
@@ -65,14 +65,13 @@ describe('ServiceCredentialsForm', () => {
expect(findSubmitButton().attributes('disabled')).toBeTruthy();
});
- it('enables submit button when role ARN is not provided', () => {
+ it('enables submit button when role ARN is not provided', async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
vm.setData({ roleArn: '123' });
- return vm.vm.$nextTick().then(() => {
- expect(findSubmitButton().attributes('disabled')).toBeFalsy();
- });
+ await nextTick();
+ expect(findSubmitButton().attributes('disabled')).toBeFalsy();
});
it('dispatches createRole action when submit button is clicked', () => {
@@ -86,14 +85,14 @@ describe('ServiceCredentialsForm', () => {
});
describe('when is creating role', () => {
- beforeEach(() => {
+ beforeEach(async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
vm.setData({ roleArn: '123' }); // set role ARN to enable button
state.isCreatingRole = true;
- return vm.vm.$nextTick();
+ await nextTick();
});
it('disables submit button', () => {
diff --git a/spec/frontend/create_cluster/gke_cluster/components/gke_machine_type_dropdown_spec.js b/spec/frontend/create_cluster/gke_cluster/components/gke_machine_type_dropdown_spec.js
index c2cd2104551..f46b84da939 100644
--- a/spec/frontend/create_cluster/gke_cluster/components/gke_machine_type_dropdown_spec.js
+++ b/spec/frontend/create_cluster/gke_cluster/components/gke_machine_type_dropdown_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 GkeMachineTypeDropdown from '~/create_cluster/gke_cluster/components/gke_machine_type_dropdown.vue';
import createState from '~/create_cluster/gke_cluster/store/state';
@@ -73,7 +73,7 @@ describe('GkeMachineTypeDropdown', () => {
expect(dropdownButtonLabel()).toBe(LABELS.DISABLED_NO_ZONE);
});
- it('returns loading toggle text', () => {
+ it('returns loading toggle text', async () => {
store = createStore();
wrapper = createComponent(store);
@@ -81,9 +81,8 @@ describe('GkeMachineTypeDropdown', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ isLoading: true });
- return wrapper.vm.$nextTick().then(() => {
- expect(dropdownButtonLabel()).toBe(LABELS.LOADING);
- });
+ await nextTick();
+ expect(dropdownButtonLabel()).toBe(LABELS.LOADING);
});
it('returns default toggle text', () => {
@@ -113,7 +112,7 @@ describe('GkeMachineTypeDropdown', () => {
});
describe('form input', () => {
- it('reflects new value when dropdown item is clicked', () => {
+ it('reflects new value when dropdown item is clicked', async () => {
store = createStore({
machineTypes: gapiMachineTypesResponseMock.items,
});
@@ -123,9 +122,8 @@ describe('GkeMachineTypeDropdown', () => {
wrapper.find('.dropdown-content button').trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(setMachineType).toHaveBeenCalledWith(expect.anything(), selectedMachineTypeMock);
- });
+ await nextTick();
+ expect(setMachineType).toHaveBeenCalledWith(expect.anything(), selectedMachineTypeMock);
});
});
});
diff --git a/spec/frontend/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js b/spec/frontend/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js
index 84ae263f2c5..36f8d4bd1e8 100644
--- a/spec/frontend/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js
+++ b/spec/frontend/create_cluster/gke_cluster/components/gke_project_id_dropdown_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 GkeProjectIdDropdown from '~/create_cluster/gke_cluster/components/gke_project_id_dropdown.vue';
import createState from '~/create_cluster/gke_cluster/store/state';
@@ -78,19 +78,18 @@ describe('GkeProjectIdDropdown', () => {
expect(dropdownButtonLabel()).toBe(LABELS.VALIDATING_PROJECT_BILLING);
});
- it('returns default toggle text', () => {
+ it('returns default toggle text', async () => {
bootstrap();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ isLoading: false });
- return wrapper.vm.$nextTick().then(() => {
- expect(dropdownButtonLabel()).toBe(LABELS.DEFAULT);
- });
+ await nextTick();
+ expect(dropdownButtonLabel()).toBe(LABELS.DEFAULT);
});
- it('returns project name if project selected', () => {
+ it('returns project name if project selected', async () => {
bootstrap(
{
selectedProject: selectedProjectMock,
@@ -103,12 +102,11 @@ describe('GkeProjectIdDropdown', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ isLoading: false });
- return wrapper.vm.$nextTick().then(() => {
- expect(dropdownButtonLabel()).toBe(selectedProjectMock.name);
- });
+ await nextTick();
+ expect(dropdownButtonLabel()).toBe(selectedProjectMock.name);
});
- it('returns empty toggle text', () => {
+ it('returns empty toggle text', async () => {
bootstrap({
projects: null,
});
@@ -116,26 +114,24 @@ describe('GkeProjectIdDropdown', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ isLoading: false });
- return wrapper.vm.$nextTick().then(() => {
- expect(dropdownButtonLabel()).toBe(LABELS.EMPTY);
- });
+ await nextTick();
+ expect(dropdownButtonLabel()).toBe(LABELS.EMPTY);
});
});
describe('selectItem', () => {
- it('reflects new value when dropdown item is clicked', () => {
+ it('reflects new value when dropdown item is clicked', async () => {
bootstrap({ projects: gapiProjectsResponseMock.projects });
expect(dropdownHiddenInputValue()).toBe('');
wrapper.find('.dropdown-content button').trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(setProject).toHaveBeenCalledWith(
- expect.anything(),
- gapiProjectsResponseMock.projects[0],
- );
- });
+ await nextTick();
+ expect(setProject).toHaveBeenCalledWith(
+ expect.anything(),
+ gapiProjectsResponseMock.projects[0],
+ );
});
});
});
diff --git a/spec/frontend/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js b/spec/frontend/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js
index 22fc681f863..7b4c228b879 100644
--- a/spec/frontend/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js
+++ b/spec/frontend/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import GkeZoneDropdown from '~/create_cluster/gke_cluster/components/gke_zone_dropdown.vue';
import { createStore } from '~/create_cluster/gke_cluster/store';
import {
@@ -46,11 +47,11 @@ describe('GkeZoneDropdown', () => {
});
describe('isLoading', () => {
- beforeEach(() => {
+ beforeEach(async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ isLoading: true });
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('returns loading toggle text', () => {
@@ -59,10 +60,10 @@ describe('GkeZoneDropdown', () => {
});
describe('project is set', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.vm.$store.commit(SET_PROJECT, selectedProjectMock);
wrapper.vm.$store.commit(SET_PROJECT_BILLING_STATUS, true);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('returns default toggle text', () => {
@@ -71,9 +72,9 @@ describe('GkeZoneDropdown', () => {
});
describe('project is selected', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.vm.setItem(selectedZoneMock);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('returns project name if project selected', () => {
@@ -83,21 +84,20 @@ describe('GkeZoneDropdown', () => {
});
describe('selectItem', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.vm.$store.commit(SET_ZONES, gapiZonesResponseMock.items);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
- it('reflects new value when dropdown item is clicked', () => {
+ it('reflects new value when dropdown item is clicked', async () => {
const dropdown = wrapper.find(DropdownHiddenInput);
expect(dropdown.attributes('value')).toBe('');
wrapper.find('.dropdown-content button').trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(dropdown.attributes('value')).toBe(selectedZoneMock);
- });
+ await nextTick();
+ expect(dropdown.attributes('value')).toBe(selectedZoneMock);
});
});
});
diff --git a/spec/frontend/cycle_analytics/filter_bar_spec.js b/spec/frontend/cycle_analytics/filter_bar_spec.js
index 5112b038e7c..36933790cf7 100644
--- a/spec/frontend/cycle_analytics/filter_bar_spec.js
+++ b/spec/frontend/cycle_analytics/filter_bar_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import Vuex from 'vuex';
@@ -42,7 +42,7 @@ const defaultParams = {
};
async function shouldMergeUrlParams(wrapper, result) {
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(urlUtils.mergeUrlParams).toHaveBeenCalledWith(result, window.location.href, {
spreadArrays: true,
});
diff --git a/spec/frontend/cycle_analytics/stage_table_spec.js b/spec/frontend/cycle_analytics/stage_table_spec.js
index 9605dce2668..107fe5fc865 100644
--- a/spec/frontend/cycle_analytics/stage_table_spec.js
+++ b/spec/frontend/cycle_analytics/stage_table_spec.js
@@ -1,5 +1,6 @@
import { GlEmptyState, GlLoadingIcon, GlTable } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import StageTable from '~/cycle_analytics/components/stage_table.vue';
@@ -263,7 +264,7 @@ describe('StageTable', () => {
expect(wrapper.emitted('handleUpdatePagination')).toBeUndefined();
findPagination().vm.$emit('input', 2);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('handleUpdatePagination')[0]).toEqual([{ page: 2 }]);
});
diff --git a/spec/frontend/cycle_analytics/value_stream_metrics_spec.js b/spec/frontend/cycle_analytics/value_stream_metrics_spec.js
index 128654f2a51..5a519a07192 100644
--- a/spec/frontend/cycle_analytics/value_stream_metrics_spec.js
+++ b/spec/frontend/cycle_analytics/value_stream_metrics_spec.js
@@ -1,6 +1,7 @@
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import metricsData from 'test_fixtures/projects/analytics/value_stream_analytics/summary.json';
import waitForPromises from 'helpers/wait_for_promises';
import { METRIC_TYPE_SUMMARY } from '~/api/analytics_api';
@@ -55,7 +56,7 @@ describe('ValueStreamMetrics', () => {
});
it('will display a loader with pending requests', async () => {
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.findComponent(GlSkeletonLoading).exists()).toBe(true);
});
@@ -67,7 +68,7 @@ describe('ValueStreamMetrics', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ isLoading: true });
- await wrapper.vm.$nextTick();
+ await nextTick();
const components = findMetrics();
diff --git a/spec/frontend/deploy_freeze/components/deploy_freeze_table_spec.js b/spec/frontend/deploy_freeze/components/deploy_freeze_table_spec.js
index ada819b7e64..137776edfab 100644
--- a/spec/frontend/deploy_freeze/components/deploy_freeze_table_spec.js
+++ b/spec/frontend/deploy_freeze/components/deploy_freeze_table_spec.js
@@ -1,6 +1,6 @@
import { GlModal } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import DeployFreezeTable from '~/deploy_freeze/components/deploy_freeze_table.vue';
import createStore from '~/deploy_freeze/store';
@@ -56,7 +56,7 @@ describe('Deploy freeze table', () => {
describe('with data', () => {
beforeEach(async () => {
store.commit(RECEIVE_FREEZE_PERIODS_SUCCESS, freezePeriodsFixture);
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays data', () => {
@@ -68,7 +68,7 @@ describe('Deploy freeze table', () => {
it('allows user to edit deploy freeze', async () => {
findEditDeployFreezeButton().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(store.dispatch).toHaveBeenCalledWith(
'setFreezePeriod',
diff --git a/spec/frontend/deploy_keys/components/action_btn_spec.js b/spec/frontend/deploy_keys/components/action_btn_spec.js
index 6ac68061518..c4c7a9aea2d 100644
--- a/spec/frontend/deploy_keys/components/action_btn_spec.js
+++ b/spec/frontend/deploy_keys/components/action_btn_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import data from 'test_fixtures/deploy_keys/keys.json';
import actionBtn from '~/deploy_keys/components/action_btn.vue';
import eventHub from '~/deploy_keys/eventhub';
@@ -37,21 +38,19 @@ describe('Deploy keys action btn', () => {
});
});
- it('sends eventHub event with btn type', () => {
+ it('sends eventHub event with btn type', async () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
findButton().vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(eventHub.$emit).toHaveBeenCalledWith('enable.key', deployKey, expect.anything());
- });
+ await nextTick();
+ expect(eventHub.$emit).toHaveBeenCalledWith('enable.key', deployKey, expect.anything());
});
- it('shows loading spinner after click', () => {
+ it('shows loading spinner after click', async () => {
findButton().vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(findButton().props('loading')).toBe(true);
- });
+ await nextTick();
+ expect(findButton().props('loading')).toBe(true);
});
});
diff --git a/spec/frontend/deploy_keys/components/app_spec.js b/spec/frontend/deploy_keys/components/app_spec.js
index 598b7a0f173..79a9aaa9184 100644
--- a/spec/frontend/deploy_keys/components/app_spec.js
+++ b/spec/frontend/deploy_keys/components/app_spec.js
@@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import data from 'test_fixtures/deploy_keys/keys.json';
import waitForPromises from 'helpers/wait_for_promises';
import { TEST_HOST } from 'spec/test_constants';
@@ -39,20 +40,18 @@ describe('Deploy keys app component', () => {
const findKeyPanels = () => wrapper.findAll('.deploy-keys .gl-tabs-nav li');
const findModal = () => wrapper.findComponent(ConfirmModal);
- it('renders loading icon while waiting for request', () => {
+ it('renders loading icon while waiting for request', async () => {
mock.onGet(TEST_ENDPOINT).reply(() => new Promise());
mountComponent();
- return wrapper.vm.$nextTick().then(() => {
- expect(findLoadingIcon().exists()).toBe(true);
- });
+ await nextTick();
+ expect(findLoadingIcon().exists()).toBe(true);
});
- it('renders keys panels', () => {
- return mountComponent().then(() => {
- expect(findKeyPanels().length).toBe(3);
- });
+ it('renders keys panels', async () => {
+ await mountComponent();
+ expect(findKeyPanels().length).toBe(3);
});
it.each`
@@ -75,72 +74,55 @@ describe('Deploy keys app component', () => {
});
});
- it('re-fetches deploy keys when enabling a key', () => {
+ it('re-fetches deploy keys when enabling a key', async () => {
const key = data.public_keys[0];
- return mountComponent()
- .then(() => {
- jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {});
- jest.spyOn(wrapper.vm.service, 'enableKey').mockImplementation(() => Promise.resolve());
-
- eventHub.$emit('enable.key', key);
-
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.vm.service.enableKey).toHaveBeenCalledWith(key.id);
- expect(wrapper.vm.service.getKeys).toHaveBeenCalled();
- });
+ await mountComponent();
+ jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {});
+ jest.spyOn(wrapper.vm.service, 'enableKey').mockImplementation(() => Promise.resolve());
+
+ eventHub.$emit('enable.key', key);
+
+ await nextTick();
+ expect(wrapper.vm.service.enableKey).toHaveBeenCalledWith(key.id);
+ expect(wrapper.vm.service.getKeys).toHaveBeenCalled();
});
- it('re-fetches deploy keys when disabling a key', () => {
+ it('re-fetches deploy keys when disabling a key', async () => {
const key = data.public_keys[0];
- return mountComponent()
- .then(() => {
- jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {});
- jest.spyOn(wrapper.vm.service, 'disableKey').mockImplementation(() => Promise.resolve());
-
- eventHub.$emit('disable.key', key, () => {});
-
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findModal().props('visible')).toBe(true);
- findModal().vm.$emit('remove');
-
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.vm.service.disableKey).toHaveBeenCalledWith(key.id);
- expect(wrapper.vm.service.getKeys).toHaveBeenCalled();
- });
+ await mountComponent();
+ jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {});
+ jest.spyOn(wrapper.vm.service, 'disableKey').mockImplementation(() => Promise.resolve());
+
+ eventHub.$emit('disable.key', key, () => {});
+
+ await nextTick();
+ expect(findModal().props('visible')).toBe(true);
+ findModal().vm.$emit('remove');
+
+ await nextTick();
+ expect(wrapper.vm.service.disableKey).toHaveBeenCalledWith(key.id);
+ expect(wrapper.vm.service.getKeys).toHaveBeenCalled();
});
- it('calls disableKey when removing a key', () => {
+ it('calls disableKey when removing a key', async () => {
const key = data.public_keys[0];
- return mountComponent()
- .then(() => {
- jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {});
- jest.spyOn(wrapper.vm.service, 'disableKey').mockImplementation(() => Promise.resolve());
-
- eventHub.$emit('remove.key', key, () => {});
-
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findModal().props('visible')).toBe(true);
- findModal().vm.$emit('remove');
-
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.vm.service.disableKey).toHaveBeenCalledWith(key.id);
- expect(wrapper.vm.service.getKeys).toHaveBeenCalled();
- });
+ await mountComponent();
+ jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {});
+ jest.spyOn(wrapper.vm.service, 'disableKey').mockImplementation(() => Promise.resolve());
+
+ eventHub.$emit('remove.key', key, () => {});
+
+ await nextTick();
+ expect(findModal().props('visible')).toBe(true);
+ findModal().vm.$emit('remove');
+
+ await nextTick();
+ expect(wrapper.vm.service.disableKey).toHaveBeenCalledWith(key.id);
+ expect(wrapper.vm.service.getKeys).toHaveBeenCalled();
});
- it('hasKeys returns true when there are keys', () => {
- return mountComponent().then(() => {
- expect(wrapper.vm.hasKeys).toEqual(3);
- });
+ it('hasKeys returns true when there are keys', async () => {
+ await mountComponent();
+ expect(wrapper.vm.hasKeys).toEqual(3);
});
});
diff --git a/spec/frontend/deploy_keys/components/key_spec.js b/spec/frontend/deploy_keys/components/key_spec.js
index 51c120d8213..8599c55c908 100644
--- a/spec/frontend/deploy_keys/components/key_spec.js
+++ b/spec/frontend/deploy_keys/components/key_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import data from 'test_fixtures/deploy_keys/keys.json';
import key from '~/deploy_keys/components/key.vue';
import DeployKeysStore from '~/deploy_keys/store';
@@ -95,18 +96,17 @@ describe('Deploy keys key', () => {
expect(labels.at(1).attributes('title')).toContain('Expand');
});
- it('expands all project labels after click', () => {
+ it('expands all project labels after click', async () => {
createComponent({ deployKey });
const { length } = deployKey.deploy_keys_projects;
wrapper.findAll('.deploy-project-label').at(1).trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- const labels = wrapper.findAll('.deploy-project-label');
+ await nextTick();
+ const labels = wrapper.findAll('.deploy-project-label');
- expect(labels.length).toBe(length);
- expect(labels.at(1).text()).not.toContain(`+${length} others`);
- expect(labels.at(1).attributes('title')).not.toContain('Expand');
- });
+ expect(labels.length).toBe(length);
+ expect(labels.at(1).text()).not.toContain(`+${length} others`);
+ expect(labels.at(1).attributes('title')).not.toContain('Expand');
});
it('shows two projects', () => {
diff --git a/spec/frontend/design_management/components/delete_button_spec.js b/spec/frontend/design_management/components/delete_button_spec.js
index f5a841d35b8..e3907fdbe15 100644
--- a/spec/frontend/design_management/components/delete_button_spec.js
+++ b/spec/frontend/design_management/components/delete_button_spec.js
@@ -1,5 +1,6 @@
import { GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import BatchDeleteButton from '~/design_management/components/delete_button.vue';
describe('Batch delete button component', () => {
@@ -36,18 +37,15 @@ describe('Batch delete button component', () => {
expect(findButton().attributes('disabled')).toBeTruthy();
});
- it('emits `delete-selected-designs` event on modal ok click', () => {
+ it('emits `delete-selected-designs` event on modal ok click', async () => {
createComponent();
findButton().vm.$emit('click');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findModal().vm.$emit('ok');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.emitted('delete-selected-designs')).toBeTruthy();
- });
+
+ await nextTick();
+ findModal().vm.$emit('ok');
+
+ await nextTick();
+ expect(wrapper.emitted('delete-selected-designs')).toBeTruthy();
});
it('renders slot content', () => {
diff --git a/spec/frontend/design_management/components/design_notes/design_discussion_spec.js b/spec/frontend/design_management/components/design_notes/design_discussion_spec.js
index e816a05ba53..bbf2190ad47 100644
--- a/spec/frontend/design_management/components/design_notes/design_discussion_spec.js
+++ b/spec/frontend/design_management/components/design_notes/design_discussion_spec.js
@@ -1,6 +1,7 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { ApolloMutation } from 'vue-apollo';
+import { nextTick } from 'vue';
import DesignDiscussion from '~/design_management/components/design_notes/design_discussion.vue';
import DesignNote from '~/design_management/components/design_notes/design_note.vue';
import DesignNoteSignedOut from '~/design_management/components/design_notes/design_note_signed_out.vue';
@@ -119,12 +120,11 @@ describe('Design discussions component', () => {
expect(findResolveIcon().exists()).toBe(false);
});
- it('does not render a checkbox in reply form', () => {
+ it('does not render a checkbox in reply form', async () => {
findReplyPlaceholder().vm.$emit('focus');
- return wrapper.vm.$nextTick().then(() => {
- expect(findResolveCheckbox().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findResolveCheckbox().exists()).toBe(false);
});
});
@@ -150,13 +150,12 @@ describe('Design discussions component', () => {
expect(findResolveIcon().props('name')).toBe('check-circle');
});
- it('renders a checkbox with Resolve thread text in reply form', () => {
+ it('renders a checkbox with Resolve thread text in reply form', async () => {
findReplyPlaceholder().vm.$emit('focus');
wrapper.setProps({ discussionWithOpenForm: defaultMockDiscussion.id });
- return wrapper.vm.$nextTick().then(() => {
- expect(findResolveCheckbox().text()).toBe('Resolve thread');
- });
+ await nextTick();
+ expect(findResolveCheckbox().text()).toBe('Resolve thread');
});
it('does not render resolved message', () => {
@@ -216,7 +215,7 @@ describe('Design discussions component', () => {
findReplyForm().vm.$emit('submitForm');
await mutate();
- await wrapper.vm.$nextTick();
+ await nextTick();
const dispatchedEvent = dispatchEventSpy.mock.calls[0][0];
@@ -226,9 +225,9 @@ describe('Design discussions component', () => {
});
describe('when replies are expanded', () => {
- beforeEach(() => {
+ beforeEach(async () => {
findRepliesWidget().vm.$emit('toggle');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders replies widget with collapsed prop equal to false', () => {
@@ -243,26 +242,24 @@ describe('Design discussions component', () => {
expect(findReplyPlaceholder().isVisible()).toBe(true);
});
- it('renders a checkbox with Unresolve thread text in reply form', () => {
+ it('renders a checkbox with Unresolve thread text in reply form', async () => {
findReplyPlaceholder().vm.$emit('focus');
wrapper.setProps({ discussionWithOpenForm: defaultMockDiscussion.id });
- return wrapper.vm.$nextTick().then(() => {
- expect(findResolveCheckbox().text()).toBe('Unresolve thread');
- });
+ await nextTick();
+ expect(findResolveCheckbox().text()).toBe('Unresolve thread');
});
});
});
- it('hides reply placeholder and opens form on placeholder click', () => {
+ it('hides reply placeholder and opens form on placeholder click', async () => {
createComponent();
findReplyPlaceholder().vm.$emit('focus');
wrapper.setProps({ discussionWithOpenForm: defaultMockDiscussion.id });
- return wrapper.vm.$nextTick().then(() => {
- expect(findReplyPlaceholder().exists()).toBe(false);
- expect(findReplyForm().exists()).toBe(true);
- });
+ await nextTick();
+ expect(findReplyPlaceholder().exists()).toBe(false);
+ expect(findReplyForm().exists()).toBe(true);
});
it('calls mutation on submitting form and closes the form', async () => {
@@ -275,28 +272,24 @@ describe('Design discussions component', () => {
expect(mutate).toHaveBeenCalledWith(mutationVariables);
await mutate();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findReplyForm().exists()).toBe(false);
});
- it('clears the discussion comment on closing comment form', () => {
+ it('clears the discussion comment on closing comment form', async () => {
createComponent(
{ discussionWithOpenForm: defaultMockDiscussion.id },
{ discussionComment: 'test', isFormRendered: true },
);
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findReplyForm().vm.$emit('cancel-form');
+ await nextTick();
+ findReplyForm().vm.$emit('cancel-form');
- expect(wrapper.vm.discussionComment).toBe('');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findReplyForm().exists()).toBe(false);
- });
+ expect(wrapper.vm.discussionComment).toBe('');
+
+ await nextTick();
+ expect(findReplyForm().exists()).toBe(false);
});
describe('when any note from a discussion is active', () => {
@@ -322,7 +315,7 @@ describe('Design discussions component', () => {
);
});
- it('calls toggleResolveDiscussion mutation on resolve thread button click', () => {
+ it('calls toggleResolveDiscussion mutation on resolve thread button click', async () => {
createComponent();
findResolveButton().trigger('click');
expect(mutate).toHaveBeenCalledWith({
@@ -332,9 +325,8 @@ describe('Design discussions component', () => {
resolve: true,
},
});
- return wrapper.vm.$nextTick(() => {
- expect(findResolveLoadingIcon().exists()).toBe(true);
- });
+ await nextTick();
+ expect(findResolveLoadingIcon().exists()).toBe(true);
});
it('calls toggleResolveDiscussion mutation after adding a note if checkbox was checked', () => {
diff --git a/spec/frontend/design_management/components/design_notes/design_note_spec.js b/spec/frontend/design_management/components/design_notes/design_note_spec.js
index 3f5f5bcdfa7..35fd1273270 100644
--- a/spec/frontend/design_management/components/design_notes/design_note_spec.js
+++ b/spec/frontend/design_management/components/design_notes/design_note_spec.js
@@ -1,5 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { ApolloMutation } from 'vue-apollo';
+import { nextTick } from 'vue';
import DesignNote from '~/design_management/components/design_notes/design_note.vue';
import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
@@ -96,7 +97,7 @@ describe('Design note component', () => {
});
describe('when user has a permission to edit note', () => {
- it('should open an edit form on edit button click', () => {
+ it('should open an edit form on edit button click', async () => {
createComponent({
note: {
...note,
@@ -108,10 +109,9 @@ describe('Design note component', () => {
findEditButton().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(findReplyForm().exists()).toBe(true);
- expect(findNoteContent().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findReplyForm().exists()).toBe(true);
+ expect(findNoteContent().exists()).toBe(false);
});
describe('when edit form is rendered', () => {
@@ -134,27 +134,22 @@ describe('Design note component', () => {
expect(findReplyForm().exists()).toBe(true);
});
- it('hides the form on cancel-form event', () => {
+ it('hides the form on cancel-form event', async () => {
findReplyForm().vm.$emit('cancel-form');
- return wrapper.vm.$nextTick().then(() => {
- expect(findReplyForm().exists()).toBe(false);
- expect(findNoteContent().exists()).toBe(true);
- });
+ await nextTick();
+ expect(findReplyForm().exists()).toBe(false);
+ expect(findNoteContent().exists()).toBe(true);
});
- it('calls a mutation on submit-form event and hides a form', () => {
+ it('calls a mutation on submit-form event and hides a form', async () => {
findReplyForm().vm.$emit('submit-form');
expect(mutate).toHaveBeenCalled();
- return mutate()
- .then(() => {
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findReplyForm().exists()).toBe(false);
- expect(findNoteContent().exists()).toBe(true);
- });
+ await mutate();
+ await nextTick();
+ expect(findReplyForm().exists()).toBe(false);
+ expect(findNoteContent().exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js
index a338a5ef200..d2d1fe6b2d8 100644
--- a/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js
+++ b/spec/frontend/design_management/components/design_notes/design_reply_form_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue';
const showModal = jest.fn();
@@ -64,24 +65,22 @@ describe('Design reply form component', () => {
expect(findSubmitButton().attributes().disabled).toBeTruthy();
});
- it('does not emit submitForm event on textarea ctrl+enter keydown', () => {
+ it('does not emit submitForm event on textarea ctrl+enter keydown', async () => {
findTextarea().trigger('keydown.enter', {
ctrlKey: true,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('submit-form')).toBeFalsy();
- });
+ await nextTick();
+ expect(wrapper.emitted('submit-form')).toBeFalsy();
});
- it('does not emit submitForm event on textarea meta+enter keydown', () => {
+ it('does not emit submitForm event on textarea meta+enter keydown', async () => {
findTextarea().trigger('keydown.enter', {
metaKey: true,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('submit-form')).toBeFalsy();
- });
+ await nextTick();
+ expect(wrapper.emitted('submit-form')).toBeFalsy();
});
it('emits cancelForm event on pressing escape button on textarea', () => {
@@ -108,40 +107,36 @@ describe('Design reply form component', () => {
expect(findSubmitButton().attributes().disabled).toBeFalsy();
});
- it('emits submitForm event on Comment button click', () => {
+ it('emits submitForm event on Comment button click', async () => {
findSubmitButton().vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('submit-form')).toBeTruthy();
- });
+ await nextTick();
+ expect(wrapper.emitted('submit-form')).toBeTruthy();
});
- it('emits submitForm event on textarea ctrl+enter keydown', () => {
+ it('emits submitForm event on textarea ctrl+enter keydown', async () => {
findTextarea().trigger('keydown.enter', {
ctrlKey: true,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('submit-form')).toBeTruthy();
- });
+ await nextTick();
+ expect(wrapper.emitted('submit-form')).toBeTruthy();
});
- it('emits submitForm event on textarea meta+enter keydown', () => {
+ it('emits submitForm event on textarea meta+enter keydown', async () => {
findTextarea().trigger('keydown.enter', {
metaKey: true,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('submit-form')).toBeTruthy();
- });
+ await nextTick();
+ expect(wrapper.emitted('submit-form')).toBeTruthy();
});
- it('emits input event on changing textarea content', () => {
+ it('emits input event on changing textarea content', async () => {
findTextarea().setValue('test2');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('input')).toBeTruthy();
- });
+ await nextTick();
+ expect(wrapper.emitted('input')).toBeTruthy();
});
it('emits cancelForm event on Escape key if text was not changed', () => {
@@ -150,13 +145,12 @@ describe('Design reply form component', () => {
expect(wrapper.emitted('cancel-form')).toBeTruthy();
});
- it('opens confirmation modal on Escape key when text has changed', () => {
+ it('opens confirmation modal on Escape key when text has changed', async () => {
wrapper.setProps({ value: 'test2' });
- return wrapper.vm.$nextTick().then(() => {
- findTextarea().trigger('keyup.esc');
- expect(showModal).toHaveBeenCalled();
- });
+ await nextTick();
+ findTextarea().trigger('keyup.esc');
+ expect(showModal).toHaveBeenCalled();
});
it('emits cancelForm event on Cancel button click if text was not changed', () => {
@@ -165,13 +159,12 @@ describe('Design reply form component', () => {
expect(wrapper.emitted('cancel-form')).toBeTruthy();
});
- it('opens confirmation modal on Cancel button click when text has changed', () => {
+ it('opens confirmation modal on Cancel button click when text has changed', async () => {
wrapper.setProps({ value: 'test2' });
- return wrapper.vm.$nextTick().then(() => {
- findCancelButton().trigger('click');
- expect(showModal).toHaveBeenCalled();
- });
+ await nextTick();
+ findCancelButton().trigger('click');
+ expect(showModal).toHaveBeenCalled();
});
it('emits cancelForm event on modal Ok button click', () => {
diff --git a/spec/frontend/design_management/components/design_overlay_spec.js b/spec/frontend/design_management/components/design_overlay_spec.js
index 4bda5054090..056959425a6 100644
--- a/spec/frontend/design_management/components/design_overlay_spec.js
+++ b/spec/frontend/design_management/components/design_overlay_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DesignOverlay from '~/design_management/components/design_overlay.vue';
import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '~/design_management/constants';
import updateActiveDiscussion from '~/design_management/graphql/mutations/update_active_discussion.mutation.graphql';
@@ -17,12 +18,11 @@ describe('Design overlay component', () => {
const findFirstBadge = () => findBadgeAtIndex(0);
const findSecondBadge = () => findBadgeAtIndex(1);
- const clickAndDragBadge = (elem, fromPoint, toPoint) => {
+ const clickAndDragBadge = async (elem, fromPoint, toPoint) => {
elem.trigger('mousedown', { clientX: fromPoint.x, clientY: fromPoint.y });
- return wrapper.vm.$nextTick().then(() => {
- elem.trigger('mousemove', { clientX: toPoint.x, clientY: toPoint.y });
- return wrapper.vm.$nextTick();
- });
+ await nextTick();
+ elem.trigger('mousemove', { clientX: toPoint.x, clientY: toPoint.y });
+ await nextTick();
};
function createComponent(props = {}, data = {}) {
@@ -59,7 +59,7 @@ describe('Design overlay component', () => {
expect(wrapper.attributes().style).toBe('width: 100px; height: 100px; top: 0px; left: 0px;');
});
- it('should emit `openCommentForm` when clicking on overlay', () => {
+ it('should emit `openCommentForm` when clicking on overlay', async () => {
createComponent();
const newCoordinates = {
x: 10,
@@ -69,11 +69,10 @@ describe('Design overlay component', () => {
wrapper
.find('[data-qa-selector="design_image_button"]')
.trigger('mouseup', { offsetX: newCoordinates.x, offsetY: newCoordinates.y });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('openCommentForm')).toEqual([
- [{ x: newCoordinates.x, y: newCoordinates.y }],
- ]);
- });
+ await nextTick();
+ expect(wrapper.emitted('openCommentForm')).toEqual([
+ [{ x: newCoordinates.x, y: newCoordinates.y }],
+ ]);
});
describe('with notes', () => {
@@ -116,7 +115,7 @@ describe('Design overlay component', () => {
describe('when a discussion is active', () => {
it.each([notes[0].discussion.notes.nodes[1], notes[0].discussion.notes.nodes[0]])(
'should not apply inactive class to the pin for the active discussion',
- (note) => {
+ async (note) => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
@@ -126,13 +125,12 @@ describe('Design overlay component', () => {
},
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findBadgeAtIndex(0).classes()).not.toContain('inactive');
- });
+ await nextTick();
+ expect(findBadgeAtIndex(0).classes()).not.toContain('inactive');
},
);
- it('should apply inactive class to all pins besides the active one', () => {
+ it('should apply inactive class to all pins besides the active one', async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
@@ -142,15 +140,14 @@ describe('Design overlay component', () => {
},
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findSecondBadge().classes()).toContain('inactive');
- expect(findFirstBadge().classes()).not.toContain('inactive');
- });
+ await nextTick();
+ expect(findSecondBadge().classes()).toContain('inactive');
+ expect(findFirstBadge().classes()).not.toContain('inactive');
});
});
});
- it('should recalculate badges positions on window resize', () => {
+ it('should recalculate badges positions on window resize', async () => {
createComponent({
notes,
dimensions: {
@@ -168,12 +165,11 @@ describe('Design overlay component', () => {
},
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findFirstBadge().attributes().style).toBe('left: 20px; top: 30px;');
- });
+ await nextTick();
+ expect(findFirstBadge().attributes().style).toBe('left: 20px; top: 30px;');
});
- it('should call an update active discussion mutation when clicking a note without moving it', () => {
+ it('should call an update active discussion mutation when clicking a note without moving it', async () => {
const note = notes[0];
const { position } = note;
const mutationVariables = {
@@ -186,31 +182,25 @@ describe('Design overlay component', () => {
findFirstBadge().trigger('mousedown', { clientX: position.x, clientY: position.y });
- return wrapper.vm.$nextTick().then(() => {
- findFirstBadge().trigger('mouseup', { clientX: position.x, clientY: position.y });
- expect(mutate).toHaveBeenCalledWith(mutationVariables);
- });
+ await nextTick();
+ findFirstBadge().trigger('mouseup', { clientX: position.x, clientY: position.y });
+ expect(mutate).toHaveBeenCalledWith(mutationVariables);
});
});
describe('when moving notes', () => {
- it('should update badge style when note is being moved', () => {
+ it('should update badge style when note is being moved', async () => {
createComponent({
notes,
});
const { position } = notes[0];
- return clickAndDragBadge(
- findFirstBadge(),
- { x: position.x, y: position.y },
- { x: 20, y: 20 },
- ).then(() => {
- expect(findFirstBadge().attributes().style).toBe('left: 20px; top: 20px;');
- });
+ await clickAndDragBadge(findFirstBadge(), { x: position.x, y: position.y }, { x: 20, y: 20 });
+ expect(findFirstBadge().attributes().style).toBe('left: 20px; top: 20px;');
});
- it('should emit `moveNote` event when note-moving action ends', () => {
+ it('should emit `moveNote` event when note-moving action ends', async () => {
createComponent({ notes });
const note = notes[0];
const { position } = note;
@@ -231,22 +221,19 @@ describe('Design overlay component', () => {
});
const badge = findFirstBadge();
- return clickAndDragBadge(badge, { x: position.x, y: position.y }, newCoordinates)
- .then(() => {
- badge.trigger('mouseup');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.emitted('moveNote')).toEqual([
- [
- {
- noteId: notes[0].id,
- discussionId: notes[0].discussion.id,
- coordinates: newCoordinates,
- },
- ],
- ]);
- });
+ await clickAndDragBadge(badge, { x: position.x, y: position.y }, newCoordinates);
+ badge.trigger('mouseup');
+
+ await nextTick();
+ expect(wrapper.emitted('moveNote')).toEqual([
+ [
+ {
+ noteId: notes[0].id,
+ discussionId: notes[0].discussion.id,
+ coordinates: newCoordinates,
+ },
+ ],
+ ]);
});
describe('without [repositionNote] permission', () => {
@@ -262,19 +249,18 @@ describe('Design overlay component', () => {
y: mockNoteNotAuthorised.position.y,
};
- it('should be unable to move a note', () => {
+ it('should be unable to move a note', async () => {
createComponent({
dimensions: mockDimensions,
notes: [mockNoteNotAuthorised],
});
const badge = findAllNotes().at(0);
- return clickAndDragBadge(badge, { ...mockNoteCoordinates }, { x: 20, y: 20 }).then(() => {
- // note position should not change after a click-and-drag attempt
- expect(findFirstBadge().attributes().style).toContain(
- `left: ${mockNoteCoordinates.x}px; top: ${mockNoteCoordinates.y}px;`,
- );
- });
+ await clickAndDragBadge(badge, { ...mockNoteCoordinates }, { x: 20, y: 20 });
+ // note position should not change after a click-and-drag attempt
+ expect(findFirstBadge().attributes().style).toContain(
+ `left: ${mockNoteCoordinates.x}px; top: ${mockNoteCoordinates.y}px;`,
+ );
});
});
});
@@ -292,7 +278,7 @@ describe('Design overlay component', () => {
});
describe('when moving the comment badge', () => {
- it('should update badge style to reflect new position', () => {
+ it('should update badge style to reflect new position', async () => {
const { position } = notes[0];
createComponent({
@@ -301,16 +287,15 @@ describe('Design overlay component', () => {
},
});
- return clickAndDragBadge(
+ await clickAndDragBadge(
findCommentBadge(),
{ x: position.x, y: position.y },
{ x: 20, y: 20 },
- ).then(() => {
- expect(findCommentBadge().attributes().style).toBe('left: 20px; top: 20px;');
- });
+ );
+ expect(findCommentBadge().attributes().style).toBe('left: 20px; top: 20px;');
});
- it('should update badge style when note-moving action ends', () => {
+ it('should update badge style when note-moving action ends', async () => {
const { position } = notes[0];
createComponent({
currentCommentForm: {
@@ -321,19 +306,16 @@ describe('Design overlay component', () => {
const commentBadge = findCommentBadge();
const toPoint = { x: 20, y: 20 };
- return clickAndDragBadge(commentBadge, { x: position.x, y: position.y }, toPoint)
- .then(() => {
- commentBadge.trigger('mouseup');
- // simulates the currentCommentForm being updated in index.vue component, and
- // propagated back down to this prop
- wrapper.setProps({
- currentCommentForm: { height: position.height, width: position.width, ...toPoint },
- });
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(commentBadge.attributes().style).toBe('left: 20px; top: 20px;');
- });
+ await clickAndDragBadge(commentBadge, { x: position.x, y: position.y }, toPoint);
+ commentBadge.trigger('mouseup');
+ // simulates the currentCommentForm being updated in index.vue component, and
+ // propagated back down to this prop
+ wrapper.setProps({
+ currentCommentForm: { height: position.height, width: position.width, ...toPoint },
+ });
+
+ await nextTick();
+ expect(commentBadge.attributes().style).toBe('left: 20px; top: 20px;');
});
it.each`
@@ -342,7 +324,7 @@ describe('Design overlay component', () => {
${'comment badge'} | ${findCommentBadge} | ${'mouseup'}
`(
'should emit `openCommentForm` event when $event fired on $element element',
- ({ getElementFunc, event }) => {
+ async ({ getElementFunc, event }) => {
createComponent({
notes,
currentCommentForm: {
@@ -364,9 +346,8 @@ describe('Design overlay component', () => {
});
getElementFunc().trigger(event);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('openCommentForm')).toEqual([[newCoordinates]]);
- });
+ await nextTick();
+ expect(wrapper.emitted('openCommentForm')).toEqual([[newCoordinates]]);
},
);
});
diff --git a/spec/frontend/design_management/components/design_presentation_spec.js b/spec/frontend/design_management/components/design_presentation_spec.js
index adec9ef469d..d79dde84d46 100644
--- a/spec/frontend/design_management/components/design_presentation_spec.js
+++ b/spec/frontend/design_management/components/design_presentation_spec.js
@@ -74,7 +74,7 @@ describe('Design management design presentation component', () => {
.mockReturnValue((childDimensions.height - viewportDimensions.height) * scrollTopPerc);
}
- function clickDragExplore(startCoords, endCoords, { useTouchEvents, mouseup } = {}) {
+ async function clickDragExplore(startCoords, endCoords, { useTouchEvents, mouseup } = {}) {
const event = useTouchEvents
? {
mousedown: 'touchstart',
@@ -96,24 +96,17 @@ describe('Design management design presentation component', () => {
clientX: startCoords.clientX,
clientY: startCoords.clientY,
});
- return wrapper.vm
- .$nextTick()
- .then(() => {
- addCommentOverlay.trigger(event.mousemove, {
- clientX: endCoords.clientX,
- clientY: endCoords.clientY,
- });
-
- return nextTick();
- })
- .then(() => {
- if (mouseup) {
- addCommentOverlay.trigger(event.mouseup);
- return nextTick();
- }
+ await nextTick();
+ addCommentOverlay.trigger(event.mousemove, {
+ clientX: endCoords.clientX,
+ clientY: endCoords.clientY,
+ });
- return undefined;
- });
+ await nextTick();
+ if (mouseup) {
+ addCommentOverlay.trigger(event.mouseup);
+ await nextTick();
+ }
}
beforeEach(() => {
@@ -125,7 +118,7 @@ describe('Design management design presentation component', () => {
window.gon = originalGon;
});
- it('renders image and overlay when image provided', () => {
+ it('renders image and overlay when image provided', async () => {
createComponent(
{
image: 'test.jpg',
@@ -134,20 +127,18 @@ describe('Design management design presentation component', () => {
mockOverlayData,
);
- return nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(wrapper.element).toMatchSnapshot();
});
- it('renders empty state when no image provided', () => {
+ it('renders empty state when no image provided', async () => {
createComponent();
- return nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(wrapper.element).toMatchSnapshot();
});
- it('openCommentForm event emits correct data', () => {
+ it('openCommentForm event emits correct data', async () => {
createComponent(
{
image: 'test.jpg',
@@ -158,15 +149,14 @@ describe('Design management design presentation component', () => {
wrapper.vm.openCommentForm({ x: 1, y: 1 });
- return nextTick().then(() => {
- expect(wrapper.emitted('openCommentForm')).toEqual([
- [{ ...mockOverlayData.overlayDimensions, x: 1, y: 1 }],
- ]);
- });
+ await nextTick();
+ expect(wrapper.emitted('openCommentForm')).toEqual([
+ [{ ...mockOverlayData.overlayDimensions, x: 1, y: 1 }],
+ ]);
});
describe('currentCommentForm', () => {
- it('is null when isAnnotating is false', () => {
+ it('is null when isAnnotating is false', async () => {
createComponent(
{
image: 'test.jpg',
@@ -175,13 +165,12 @@ describe('Design management design presentation component', () => {
mockOverlayData,
);
- return nextTick().then(() => {
- expect(wrapper.vm.currentCommentForm).toBeNull();
- expect(wrapper.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(wrapper.vm.currentCommentForm).toBeNull();
+ expect(wrapper.element).toMatchSnapshot();
});
- it('is null when isAnnotating is true but annotation position is falsey', () => {
+ it('is null when isAnnotating is true but annotation position is falsey', async () => {
createComponent(
{
image: 'test.jpg',
@@ -191,13 +180,12 @@ describe('Design management design presentation component', () => {
mockOverlayData,
);
- return nextTick().then(() => {
- expect(wrapper.vm.currentCommentForm).toBeNull();
- expect(wrapper.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(wrapper.vm.currentCommentForm).toBeNull();
+ expect(wrapper.element).toMatchSnapshot();
});
- it('is equal to current annotation position when isAnnotating is true', () => {
+ it('is equal to current annotation position when isAnnotating is true', async () => {
createComponent(
{
image: 'test.jpg',
@@ -215,15 +203,14 @@ describe('Design management design presentation component', () => {
},
);
- return nextTick().then(() => {
- expect(wrapper.vm.currentCommentForm).toEqual({
- x: 1,
- y: 1,
- width: 100,
- height: 100,
- });
- expect(wrapper.element).toMatchSnapshot();
+ await nextTick();
+ expect(wrapper.vm.currentCommentForm).toEqual({
+ x: 1,
+ y: 1,
+ width: 100,
+ height: 100,
});
+ expect(wrapper.element).toMatchSnapshot();
});
});
@@ -388,7 +375,7 @@ describe('Design management design presentation component', () => {
});
describe('onImageResize', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createComponent(
{
image: 'test.jpg',
@@ -401,7 +388,7 @@ describe('Design management design presentation component', () => {
jest.spyOn(wrapper.vm, 'scaleZoomFocalPoint');
jest.spyOn(wrapper.vm, 'scrollToFocalPoint');
wrapper.vm.onImageResize({ width: 10, height: 10 });
- return nextTick();
+ await nextTick();
});
it('sets zoom focal point on initial load', () => {
@@ -409,12 +396,11 @@ describe('Design management design presentation component', () => {
expect(wrapper.vm.initialLoad).toBe(false);
});
- it('calls scaleZoomFocalPoint and scrollToFocalPoint after initial load', () => {
+ it('calls scaleZoomFocalPoint and scrollToFocalPoint after initial load', async () => {
wrapper.vm.onImageResize({ width: 10, height: 10 });
- return nextTick().then(() => {
- expect(wrapper.vm.scaleZoomFocalPoint).toHaveBeenCalled();
- expect(wrapper.vm.scrollToFocalPoint).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(wrapper.vm.scaleZoomFocalPoint).toHaveBeenCalled();
+ expect(wrapper.vm.scrollToFocalPoint).toHaveBeenCalled();
});
});
@@ -498,7 +484,7 @@ describe('Design management design presentation component', () => {
);
});
- it('opens a comment form if design was not dragged', () => {
+ it('opens a comment form if design was not dragged', async () => {
const addCommentOverlay = findOverlayCommentButton();
const startCoords = {
clientX: 1,
@@ -510,15 +496,10 @@ describe('Design management design presentation component', () => {
clientY: startCoords.clientY,
});
- return wrapper.vm
- .$nextTick()
- .then(() => {
- addCommentOverlay.trigger('mouseup');
- return nextTick();
- })
- .then(() => {
- expect(wrapper.emitted('openCommentForm')).toBeDefined();
- });
+ await nextTick();
+ addCommentOverlay.trigger('mouseup');
+ await nextTick();
+ expect(wrapper.emitted('openCommentForm')).toBeDefined();
});
describe('when clicking and dragging', () => {
diff --git a/spec/frontend/design_management/components/design_scaler_spec.js b/spec/frontend/design_management/components/design_scaler_spec.js
index 095c070e5e8..a04e2ebda5b 100644
--- a/spec/frontend/design_management/components/design_scaler_spec.js
+++ b/spec/frontend/design_management/components/design_scaler_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DesignScaler from '~/design_management/components/design_scaler.vue';
describe('Design management design scaler component', () => {
@@ -32,7 +33,7 @@ describe('Design management design scaler component', () => {
describe('when `scale` value is greater than 1', () => {
beforeEach(async () => {
setScale(1.6);
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('emits @scale event when "reset" button clicked', () => {
@@ -68,11 +69,11 @@ describe('Design management design scaler component', () => {
it('computes & increments correct stepSize based on maxScale', async () => {
wrapper.setProps({ maxScale: 11 });
- await wrapper.vm.$nextTick();
+ await nextTick();
getIncreaseScaleButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted().scale[0][0]).toBe(3);
});
@@ -96,7 +97,7 @@ describe('Design management design scaler component', () => {
describe('when `scale` value is maximum', () => {
beforeEach(async () => {
setScale(2);
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('disables the "increment" button', () => {
diff --git a/spec/frontend/design_management/components/design_sidebar_spec.js b/spec/frontend/design_management/components/design_sidebar_spec.js
index 4cd71bdb7f3..f690974db1b 100644
--- a/spec/frontend/design_management/components/design_sidebar_spec.js
+++ b/spec/frontend/design_management/components/design_sidebar_spec.js
@@ -1,6 +1,7 @@
import { GlCollapse, GlPopover } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Cookies from 'js-cookie';
+import { nextTick } from 'vue';
import DesignDiscussion from '~/design_management/components/design_notes/design_discussion.vue';
import DesignNoteSignedOut from '~/design_management/components/design_notes/design_note_signed_out.vue';
import DesignSidebar from '~/design_management/components/design_sidebar.vue';
@@ -138,14 +139,13 @@ describe('Design management design sidebar component', () => {
expect(wrapper.emitted('toggleResolvedComments')).toHaveLength(1);
});
- it('opens a collapsible when resolvedDiscussionsExpanded prop changes to true', () => {
+ it('opens a collapsible when resolvedDiscussionsExpanded prop changes to true', async () => {
expect(findCollapsible().attributes('visible')).toBeUndefined();
wrapper.setProps({
resolvedDiscussionsExpanded: true,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findCollapsible().attributes('visible')).toBe('true');
- });
+ await nextTick();
+ expect(findCollapsible().attributes('visible')).toBe('true');
});
it('does not popover about resolved comments', () => {
@@ -182,12 +182,11 @@ describe('Design management design sidebar component', () => {
expect(wrapper.emitted('resolveDiscussionError')).toEqual([['payload']]);
});
- it('changes prop correctly on opening discussion form', () => {
+ it('changes prop correctly on opening discussion form', async () => {
findFirstDiscussion().vm.$emit('open-form', 'some-id');
- return wrapper.vm.$nextTick().then(() => {
- expect(findFirstDiscussion().props('discussionWithOpenForm')).toBe('some-id');
- });
+ await nextTick();
+ expect(findFirstDiscussion().props('discussionWithOpenForm')).toBe('some-id');
});
});
@@ -246,11 +245,10 @@ describe('Design management design sidebar component', () => {
expect(scrollIntoViewMock).toHaveBeenCalled();
});
- it('dismisses a popover on the outside click', () => {
+ it('dismisses a popover on the outside click', async () => {
wrapper.trigger('click');
- return wrapper.vm.$nextTick(() => {
- expect(findPopover().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findPopover().exists()).toBe(false);
});
it(`sets a ${cookieKey} cookie on clicking outside the popover`, () => {
diff --git a/spec/frontend/design_management/components/design_todo_button_spec.js b/spec/frontend/design_management/components/design_todo_button_spec.js
index 757bf50c527..73661c9fcb0 100644
--- a/spec/frontend/design_management/components/design_todo_button_spec.js
+++ b/spec/frontend/design_management/components/design_todo_button_spec.js
@@ -1,4 +1,5 @@
import { shallowMount, mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DesignTodoButton from '~/design_management/components/design_todo_button.vue';
import createDesignTodoMutation from '~/design_management/graphql/mutations/create_design_todo.mutation.graphql';
import todoMarkDoneMutation from '~/graphql_shared/mutations/todo_mark_done.mutation.graphql';
@@ -71,7 +72,7 @@ describe('Design management design todo button', () => {
describe('when clicked', () => {
let dispatchEventSpy;
- beforeEach(() => {
+ beforeEach(async () => {
dispatchEventSpy = jest.spyOn(document, 'dispatchEvent');
jest.spyOn(document, 'querySelector').mockReturnValue({
innerText: 2,
@@ -79,7 +80,7 @@ describe('Design management design todo button', () => {
createComponent({ design: mockDesignWithPendingTodos }, { mountFn: mount });
wrapper.trigger('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('calls `$apollo.mutate` with the `todoMarkDone` mutation and variables containing `id`', async () => {
@@ -117,7 +118,7 @@ describe('Design management design todo button', () => {
describe('when clicked', () => {
let dispatchEventSpy;
- beforeEach(() => {
+ beforeEach(async () => {
dispatchEventSpy = jest.spyOn(document, 'dispatchEvent');
jest.spyOn(document, 'querySelector').mockReturnValue({
innerText: 2,
@@ -125,7 +126,7 @@ describe('Design management design todo button', () => {
createComponent({}, { mountFn: mount });
wrapper.trigger('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('calls `$apollo.mutate` with the `createDesignTodoMutation` mutation and variables containing `issuable_id`, `issue_id`, & `projectPath`', async () => {
diff --git a/spec/frontend/design_management/components/image_spec.js b/spec/frontend/design_management/components/image_spec.js
index ac3afc73c86..e27b2bc9fa5 100644
--- a/spec/frontend/design_management/components/image_spec.js
+++ b/spec/frontend/design_management/components/image_spec.js
@@ -1,5 +1,6 @@
import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DesignImage from '~/design_management/components/image.vue';
describe('Design management large image component', () => {
@@ -36,7 +37,7 @@ describe('Design management large image component', () => {
expect(wrapper.element).toMatchSnapshot();
});
- it('sets correct classes and styles if imageStyle is set', () => {
+ it('sets correct classes and styles if imageStyle is set', async () => {
createComponent(
{
isLoading: false,
@@ -50,12 +51,11 @@ describe('Design management large image component', () => {
},
},
);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(wrapper.element).toMatchSnapshot();
});
- it('renders media broken icon on error', () => {
+ it('renders media broken icon on error', async () => {
createComponent({
isLoading: false,
image: 'test.jpg',
@@ -64,10 +64,9 @@ describe('Design management large image component', () => {
const image = wrapper.find('img');
image.trigger('error');
- return wrapper.vm.$nextTick().then(() => {
- expect(image.isVisible()).toBe(false);
- expect(wrapper.find(GlIcon).element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(image.isVisible()).toBe(false);
+ expect(wrapper.find(GlIcon).element).toMatchSnapshot();
});
describe('zoom', () => {
@@ -99,37 +98,34 @@ describe('Design management large image component', () => {
.mockReturnValue(baseImageHeight);
});
- it('emits @resize event on zoom', () => {
+ it('emits @resize event on zoom', async () => {
const zoomAmount = 2;
wrapper.vm.zoom(zoomAmount);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('resize')).toEqual([
- [{ width: baseImageWidth * zoomAmount, height: baseImageHeight * zoomAmount }],
- ]);
- });
+ await nextTick();
+ expect(wrapper.emitted('resize')).toEqual([
+ [{ width: baseImageWidth * zoomAmount, height: baseImageHeight * zoomAmount }],
+ ]);
});
- it('emits @resize event with base image size when scale=1', () => {
+ it('emits @resize event with base image size when scale=1', async () => {
wrapper.vm.zoom(1);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('resize')).toEqual([
- [{ width: baseImageWidth, height: baseImageHeight }],
- ]);
- });
+ await nextTick();
+ expect(wrapper.emitted('resize')).toEqual([
+ [{ width: baseImageWidth, height: baseImageHeight }],
+ ]);
});
- it('sets image style when zoomed', () => {
+ it('sets image style when zoomed', async () => {
const zoomAmount = 2;
wrapper.vm.zoom(zoomAmount);
expect(wrapper.vm.imageStyle).toEqual({
width: `${baseImageWidth * zoomAmount}px`,
height: `${baseImageHeight * zoomAmount}px`,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(wrapper.element).toMatchSnapshot();
});
});
});
diff --git a/spec/frontend/design_management/components/list/item_spec.js b/spec/frontend/design_management/components/list/item_spec.js
index 92cb35bd8d6..e00dda2015e 100644
--- a/spec/frontend/design_management/components/list/item_spec.js
+++ b/spec/frontend/design_management/components/list/item_spec.js
@@ -1,6 +1,6 @@
import { GlIcon, GlLoadingIcon, GlIntersectionObserver } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import VueRouter from 'vue-router';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import Item from '~/design_management/components/list/item.vue';
@@ -71,13 +71,13 @@ describe('Design management list item component', () => {
let image;
let glIntersectionObserver;
- beforeEach(() => {
+ beforeEach(async () => {
createComponent();
image = wrapper.find('img');
glIntersectionObserver = wrapper.find(GlIntersectionObserver);
glIntersectionObserver.vm.$emit('appear');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders a tooltip', () => {
@@ -91,9 +91,9 @@ describe('Design management list item component', () => {
});
describe('after image is loaded', () => {
- beforeEach(() => {
+ beforeEach(async () => {
image.trigger('load');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders an image', () => {
@@ -101,29 +101,27 @@ describe('Design management list item component', () => {
expect(image.isVisible()).toBe(true);
});
- it('renders media broken icon when image onerror triggered', () => {
+ it('renders media broken icon when image onerror triggered', async () => {
image.trigger('error');
- return wrapper.vm.$nextTick().then(() => {
- expect(image.isVisible()).toBe(false);
- expect(wrapper.find(GlIcon).element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(image.isVisible()).toBe(false);
+ expect(wrapper.find(GlIcon).element).toMatchSnapshot();
});
describe('when imageV432x230 and image provided', () => {
- it('renders imageV432x230 image', () => {
+ it('renders imageV432x230 image', async () => {
const mockSrc = 'mock-imageV432x230-url';
wrapper.setProps({ imageV432x230: mockSrc });
- return wrapper.vm.$nextTick().then(() => {
- expect(image.attributes('src')).toBe(mockSrc);
- });
+ await nextTick();
+ expect(image.attributes('src')).toBe(mockSrc);
});
});
describe('when image disappears from view and then reappears', () => {
- beforeEach(() => {
+ beforeEach(async () => {
glIntersectionObserver.vm.$emit('appear');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders an image', () => {
diff --git a/spec/frontend/design_management/components/toolbar/design_navigation_spec.js b/spec/frontend/design_management/components/toolbar/design_navigation_spec.js
index 6e0592984a2..38a7fadee79 100644
--- a/spec/frontend/design_management/components/toolbar/design_navigation_spec.js
+++ b/spec/frontend/design_management/components/toolbar/design_navigation_spec.js
@@ -1,6 +1,7 @@
/* global Mousetrap */
import 'mousetrap';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DesignNavigation from '~/design_management/components/toolbar/design_navigation.vue';
import { DESIGN_ROUTE_NAME } from '~/design_management/router/constants';
@@ -41,16 +42,15 @@ describe('Design management pagination component', () => {
expect(wrapper.element).toMatchSnapshot();
});
- it('renders navigation buttons', () => {
+ it('renders navigation buttons', async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
designCollection: { designs: [{ id: '1' }, { id: '2' }] },
});
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(wrapper.element).toMatchSnapshot();
});
describe('keyboard buttons navigation', () => {
diff --git a/spec/frontend/design_management/components/toolbar/index_spec.js b/spec/frontend/design_management/components/toolbar/index_spec.js
index 804b55bda38..412f3de911e 100644
--- a/spec/frontend/design_management/components/toolbar/index_spec.js
+++ b/spec/frontend/design_management/components/toolbar/index_spec.js
@@ -1,6 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import VueRouter from 'vue-router';
import DeleteButton from '~/design_management/components/delete_button.vue';
import Toolbar from '~/design_management/components/toolbar/index.vue';
@@ -60,60 +60,54 @@ describe('Design management toolbar component', () => {
wrapper.destroy();
});
- it('renders design and updated data', () => {
+ it('renders design and updated data', async () => {
createComponent();
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(wrapper.element).toMatchSnapshot();
});
- it('links back to designs list', () => {
+ it('links back to designs list', async () => {
createComponent();
- return wrapper.vm.$nextTick().then(() => {
- const link = wrapper.find('a');
+ await nextTick();
+ const link = wrapper.find('a');
- expect(link.props('to')).toEqual({
- name: DESIGNS_ROUTE_NAME,
- query: {
- version: undefined,
- },
- });
+ expect(link.props('to')).toEqual({
+ name: DESIGNS_ROUTE_NAME,
+ query: {
+ version: undefined,
+ },
});
});
- it('renders delete button on latest designs version with logged in user', () => {
+ it('renders delete button on latest designs version with logged in user', async () => {
createComponent();
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DeleteButton).exists()).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find(DeleteButton).exists()).toBe(true);
});
- it('does not render delete button on non-latest version', () => {
+ it('does not render delete button on non-latest version', async () => {
createComponent(false, true, { isLatestVersion: false });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DeleteButton).exists()).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.find(DeleteButton).exists()).toBe(false);
});
- it('does not render delete button when user is not logged in', () => {
+ it('does not render delete button when user is not logged in', async () => {
createComponent(false, false);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DeleteButton).exists()).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.find(DeleteButton).exists()).toBe(false);
});
- it('emits `delete` event on deleteButton `delete-selected-designs` event', () => {
+ it('emits `delete` event on deleteButton `delete-selected-designs` event', async () => {
createComponent();
- return wrapper.vm.$nextTick().then(() => {
- wrapper.find(DeleteButton).vm.$emit('delete-selected-designs');
- expect(wrapper.emitted().delete).toBeTruthy();
- });
+ await nextTick();
+ wrapper.find(DeleteButton).vm.$emit('delete-selected-designs');
+ expect(wrapper.emitted().delete).toBeTruthy();
});
it('renders download button with correct link', () => {
diff --git a/spec/frontend/design_management/components/upload/design_version_dropdown_spec.js b/spec/frontend/design_management/components/upload/design_version_dropdown_spec.js
index a4fb671ae13..ec5db04bb80 100644
--- a/spec/frontend/design_management/components/upload/design_version_dropdown_spec.js
+++ b/spec/frontend/design_management/components/upload/design_version_dropdown_spec.js
@@ -1,5 +1,6 @@
import { GlDropdown, GlDropdownItem, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DesignVersionDropdown from '~/design_management/components/upload/design_version_dropdown.vue';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import mockAllVersions from './mock_data/all_versions';
@@ -47,77 +48,69 @@ describe('Design management design version dropdown component', () => {
const findVersionLink = (index) => wrapper.findAll(GlDropdownItem).at(index);
- it('renders design version dropdown button', () => {
+ it('renders design version dropdown button', async () => {
createComponent();
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(wrapper.element).toMatchSnapshot();
});
- it('renders design version list', () => {
+ it('renders design version list', async () => {
createComponent();
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(wrapper.element).toMatchSnapshot();
});
describe('selected version name', () => {
- it('has "latest" on most recent version item', () => {
+ it('has "latest" on most recent version item', async () => {
createComponent();
- return wrapper.vm.$nextTick().then(() => {
- expect(findVersionLink(0).text()).toContain('latest');
- });
+ await nextTick();
+ expect(findVersionLink(0).text()).toContain('latest');
});
});
describe('versions list', () => {
- it('displays latest version text by default', () => {
+ it('displays latest version text by default', async () => {
createComponent();
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlDropdown).attributes('text')).toBe('Showing latest version');
- });
+ await nextTick();
+ expect(wrapper.find(GlDropdown).attributes('text')).toBe('Showing latest version');
});
- it('displays latest version text when only 1 version is present', () => {
+ it('displays latest version text when only 1 version is present', async () => {
createComponent({ maxVersions: 1 });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlDropdown).attributes('text')).toBe('Showing latest version');
- });
+ await nextTick();
+ expect(wrapper.find(GlDropdown).attributes('text')).toBe('Showing latest version');
});
- it('displays version text when the current version is not the latest', () => {
+ it('displays version text when the current version is not the latest', async () => {
createComponent({ $route: designRouteFactory(PREVIOUS_VERSION_ID) });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlDropdown).attributes('text')).toBe(`Showing version #1`);
- });
+ await nextTick();
+ expect(wrapper.find(GlDropdown).attributes('text')).toBe(`Showing version #1`);
});
- it('displays latest version text when the current version is the latest', () => {
+ it('displays latest version text when the current version is the latest', async () => {
createComponent({ $route: designRouteFactory(LATEST_VERSION_ID) });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlDropdown).attributes('text')).toBe('Showing latest version');
- });
+ await nextTick();
+ expect(wrapper.find(GlDropdown).attributes('text')).toBe('Showing latest version');
});
- it('should have the same length as apollo query', () => {
+ it('should have the same length as apollo query', async () => {
createComponent();
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.findAll(GlDropdownItem)).toHaveLength(wrapper.vm.allVersions.length);
- });
+ await nextTick();
+ expect(wrapper.findAll(GlDropdownItem)).toHaveLength(wrapper.vm.allVersions.length);
});
it('should render TimeAgo', async () => {
createComponent();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.findAllComponents(TimeAgo)).toHaveLength(wrapper.vm.allVersions.length);
});
diff --git a/spec/frontend/design_management/pages/design/index_spec.js b/spec/frontend/design_management/pages/design/index_spec.js
index 4477bf4b8e6..55d0fabe402 100644
--- a/spec/frontend/design_management/pages/design/index_spec.js
+++ b/spec/frontend/design_management/pages/design/index_spec.js
@@ -1,6 +1,6 @@
import { GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { ApolloMutation } from 'vue-apollo';
import VueRouter from 'vue-router';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
@@ -153,11 +153,11 @@ describe('Design management design index page', () => {
jest.spyOn(utils, 'getPageLayoutElement').mockReturnValue(mockPageLayoutElement);
createComponent({ loading: false }, { data: { design, scale: 2 } });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findDesignPresentation().props('scale')).toBe(2);
DesignIndex.beforeRouteUpdate.call(wrapper.vm, {}, {}, jest.fn());
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findDesignPresentation().props('scale')).toBe(1);
});
@@ -200,7 +200,7 @@ describe('Design management design index page', () => {
});
});
- it('opens a new discussion form', () => {
+ it('opens a new discussion form', async () => {
createComponent(
{ loading: false },
{
@@ -212,9 +212,8 @@ describe('Design management design index page', () => {
findDesignPresentation().vm.$emit('openCommentForm', { x: 0, y: 0 });
- return wrapper.vm.$nextTick().then(() => {
- expect(findDiscussionForm().exists()).toBe(true);
- });
+ await nextTick();
+ expect(findDiscussionForm().exists()).toBe(true);
});
it('keeps new discussion form focused', () => {
@@ -233,7 +232,7 @@ describe('Design management design index page', () => {
expect(focusInput).toHaveBeenCalled();
});
- it('sends a mutation on submitting form and closes form', () => {
+ it('sends a mutation on submitting form and closes form', async () => {
createComponent(
{ loading: false },
{
@@ -248,17 +247,12 @@ describe('Design management design index page', () => {
findDiscussionForm().vm.$emit('submit-form');
expect(mutate).toHaveBeenCalledWith(createDiscussionMutationVariables);
- return wrapper.vm
- .$nextTick()
- .then(() => {
- return mutate({ variables: createDiscussionMutationVariables });
- })
- .then(() => {
- expect(findDiscussionForm().exists()).toBe(false);
- });
+ await nextTick();
+ await mutate({ variables: createDiscussionMutationVariables });
+ expect(findDiscussionForm().exists()).toBe(false);
});
- it('closes the form and clears the comment on canceling form', () => {
+ it('closes the form and clears the comment on canceling form', async () => {
createComponent(
{ loading: false },
{
@@ -274,9 +268,8 @@ describe('Design management design index page', () => {
expect(wrapper.vm.comment).toBe('');
- return wrapper.vm.$nextTick().then(() => {
- expect(findDiscussionForm().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findDiscussionForm().exists()).toBe(false);
});
describe('with error', () => {
@@ -299,22 +292,21 @@ describe('Design management design index page', () => {
describe('onDesignQueryResult', () => {
describe('with no designs', () => {
- it('redirects to /designs', () => {
+ it('redirects to /designs', async () => {
createComponent({ loading: true });
router.push = jest.fn();
wrapper.vm.onDesignQueryResult({ data: mockResponseNoDesigns, loading: false });
- return wrapper.vm.$nextTick().then(() => {
- expect(createFlash).toHaveBeenCalledTimes(1);
- expect(createFlash).toHaveBeenCalledWith({ message: DESIGN_NOT_FOUND_ERROR });
- expect(router.push).toHaveBeenCalledTimes(1);
- expect(router.push).toHaveBeenCalledWith({ name: DESIGNS_ROUTE_NAME });
- });
+ await nextTick();
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith({ message: DESIGN_NOT_FOUND_ERROR });
+ expect(router.push).toHaveBeenCalledTimes(1);
+ expect(router.push).toHaveBeenCalledWith({ name: DESIGNS_ROUTE_NAME });
});
});
describe('when no design exists for given version', () => {
- it('redirects to /designs', () => {
+ it('redirects to /designs', async () => {
createComponent({ loading: true });
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -327,12 +319,11 @@ describe('Design management design index page', () => {
router.push = jest.fn();
wrapper.vm.onDesignQueryResult({ data: mockResponseWithDesigns, loading: false });
- return wrapper.vm.$nextTick().then(() => {
- expect(createFlash).toHaveBeenCalledTimes(1);
- expect(createFlash).toHaveBeenCalledWith({ message: DESIGN_VERSION_NOT_EXIST_ERROR });
- expect(router.push).toHaveBeenCalledTimes(1);
- expect(router.push).toHaveBeenCalledWith({ name: DESIGNS_ROUTE_NAME });
- });
+ await nextTick();
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createFlash).toHaveBeenCalledWith({ message: DESIGN_VERSION_NOT_EXIST_ERROR });
+ expect(router.push).toHaveBeenCalledTimes(1);
+ expect(router.push).toHaveBeenCalledWith({ name: DESIGNS_ROUTE_NAME });
});
});
});
diff --git a/spec/frontend/diffs/components/settings_dropdown_spec.js b/spec/frontend/diffs/components/settings_dropdown_spec.js
index 2dd35519464..693fc5bfd8f 100644
--- a/spec/frontend/diffs/components/settings_dropdown_spec.js
+++ b/spec/frontend/diffs/components/settings_dropdown_spec.js
@@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import SettingsDropdown from '~/diffs/components/settings_dropdown.vue';
@@ -10,7 +11,6 @@ import createDiffsStore from '../create_diffs_store';
describe('Diff settings dropdown component', () => {
let wrapper;
- let vm;
let store;
function createComponent(extendStore = () => {}) {
@@ -23,7 +23,6 @@ describe('Diff settings dropdown component', () => {
store,
}),
);
- vm = wrapper.vm;
}
function getFileByFileCheckbox(vueWrapper) {
@@ -142,7 +141,7 @@ describe('Diff settings dropdown component', () => {
checkbox.trigger('click');
- await vm.$nextTick();
+ await nextTick();
expect(store.dispatch).toHaveBeenCalledWith('diffs/setShowWhitespace', {
showWhitespace: !checked,
@@ -185,7 +184,7 @@ describe('Diff settings dropdown component', () => {
getFileByFileCheckbox(wrapper).trigger('click');
- await vm.$nextTick();
+ await nextTick();
expect(store.dispatch).toHaveBeenCalledWith('diffs/setFileByFile', {
fileByFile: setting,
diff --git a/spec/frontend/filtered_search/recent_searches_root_spec.js b/spec/frontend/filtered_search/recent_searches_root_spec.js
index fa3267c98a1..59c428fb3fa 100644
--- a/spec/frontend/filtered_search/recent_searches_root_spec.js
+++ b/spec/frontend/filtered_search/recent_searches_root_spec.js
@@ -1,3 +1,4 @@
+import { nextTick } from 'vue';
import { setHTMLFixture } from 'helpers/fixtures';
import RecentSearchesRoot from '~/filtered_search/recent_searches_root';
@@ -10,7 +11,7 @@ describe('RecentSearchesRoot', () => {
let vm;
let containerEl;
- beforeEach(() => {
+ beforeEach(async () => {
setHTMLFixture(`
<div id="${containerId}">
<div id="${dropdownElementId}"></div>
@@ -33,7 +34,7 @@ describe('RecentSearchesRoot', () => {
RecentSearchesRoot.prototype.render.call(recentSearchesRootMockInstance);
vm = recentSearchesRootMockInstance.vm;
- return vm.$nextTick();
+ await nextTick();
});
afterEach(() => {
diff --git a/spec/frontend/groups/components/app_spec.js b/spec/frontend/groups/components/app_spec.js
index bc8c6460cf4..630878f80f3 100644
--- a/spec/frontend/groups/components/app_spec.js
+++ b/spec/frontend/groups/components/app_spec.js
@@ -1,7 +1,7 @@
import { GlModal, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import appComponent from '~/groups/components/app.vue';
@@ -58,7 +58,7 @@ describe('AppComponent', () => {
wrapper = null;
});
- beforeEach(() => {
+ beforeEach(async () => {
mock = new AxiosMockAdapter(axios);
mock.onGet('/dashboard/groups.json').reply(200, mockGroups);
Vue.component('GroupFolder', groupFolderComponent);
@@ -66,7 +66,7 @@ describe('AppComponent', () => {
createShallowComponent();
getGroupsSpy = jest.spyOn(vm.service, 'getGroups');
- return vm.$nextTick();
+ await nextTick();
});
describe('computed', () => {
@@ -397,66 +397,60 @@ describe('AppComponent', () => {
});
describe('created', () => {
- it('should bind event listeners on eventHub', () => {
+ it('should bind event listeners on eventHub', async () => {
jest.spyOn(eventHub, '$on').mockImplementation(() => {});
createShallowComponent();
- return vm.$nextTick().then(() => {
- expect(eventHub.$on).toHaveBeenCalledWith('fetchPage', expect.any(Function));
- expect(eventHub.$on).toHaveBeenCalledWith('toggleChildren', expect.any(Function));
- expect(eventHub.$on).toHaveBeenCalledWith('showLeaveGroupModal', expect.any(Function));
- expect(eventHub.$on).toHaveBeenCalledWith('updatePagination', expect.any(Function));
- expect(eventHub.$on).toHaveBeenCalledWith('updateGroups', expect.any(Function));
- });
+ await nextTick();
+ expect(eventHub.$on).toHaveBeenCalledWith('fetchPage', expect.any(Function));
+ expect(eventHub.$on).toHaveBeenCalledWith('toggleChildren', expect.any(Function));
+ expect(eventHub.$on).toHaveBeenCalledWith('showLeaveGroupModal', expect.any(Function));
+ expect(eventHub.$on).toHaveBeenCalledWith('updatePagination', expect.any(Function));
+ expect(eventHub.$on).toHaveBeenCalledWith('updateGroups', expect.any(Function));
});
- it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `false`', () => {
+ it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `false`', async () => {
createShallowComponent();
- return vm.$nextTick().then(() => {
- expect(vm.searchEmptyMessage).toBe('No groups or projects matched your search');
- });
+ await nextTick();
+ expect(vm.searchEmptyMessage).toBe('No groups or projects matched your search');
});
- it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `true`', () => {
+ it('should initialize `searchEmptyMessage` prop with correct string when `hideProjects` is `true`', async () => {
createShallowComponent(true);
- return vm.$nextTick().then(() => {
- expect(vm.searchEmptyMessage).toBe('No groups matched your search');
- });
+ await nextTick();
+ expect(vm.searchEmptyMessage).toBe('No groups matched your search');
});
});
describe('beforeDestroy', () => {
- it('should unbind event listeners on eventHub', () => {
+ it('should unbind event listeners on eventHub', async () => {
jest.spyOn(eventHub, '$off').mockImplementation(() => {});
createShallowComponent();
wrapper.destroy();
- return vm.$nextTick().then(() => {
- expect(eventHub.$off).toHaveBeenCalledWith('fetchPage', expect.any(Function));
- expect(eventHub.$off).toHaveBeenCalledWith('toggleChildren', expect.any(Function));
- expect(eventHub.$off).toHaveBeenCalledWith('showLeaveGroupModal', expect.any(Function));
- expect(eventHub.$off).toHaveBeenCalledWith('updatePagination', expect.any(Function));
- expect(eventHub.$off).toHaveBeenCalledWith('updateGroups', expect.any(Function));
- });
+ await nextTick();
+ expect(eventHub.$off).toHaveBeenCalledWith('fetchPage', expect.any(Function));
+ expect(eventHub.$off).toHaveBeenCalledWith('toggleChildren', expect.any(Function));
+ expect(eventHub.$off).toHaveBeenCalledWith('showLeaveGroupModal', expect.any(Function));
+ expect(eventHub.$off).toHaveBeenCalledWith('updatePagination', expect.any(Function));
+ expect(eventHub.$off).toHaveBeenCalledWith('updateGroups', expect.any(Function));
});
});
describe('template', () => {
- it('should render loading icon', () => {
+ it('should render loading icon', async () => {
vm.isLoading = true;
- return vm.$nextTick().then(() => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
- it('should render groups tree', () => {
+ it('should render groups tree', async () => {
vm.store.state.groups = [mockParentGroupItem];
vm.isLoading = false;
- return vm.$nextTick().then(() => {
- expect(vm.$el.querySelector('.groups-list-tree-container')).toBeDefined();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.groups-list-tree-container')).toBeDefined();
});
it('renders modal confirmation dialog', () => {
diff --git a/spec/frontend/groups/components/groups_spec.js b/spec/frontend/groups/components/groups_spec.js
index 0ec1ef5a49e..590b4fb3d57 100644
--- a/spec/frontend/groups/components/groups_spec.js
+++ b/spec/frontend/groups/components/groups_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import groupFolderComponent from '~/groups/components/group_folder.vue';
@@ -21,13 +21,13 @@ const createComponent = (searchEmpty = false) => {
describe('GroupsComponent', () => {
let vm;
- beforeEach(() => {
+ beforeEach(async () => {
Vue.component('GroupFolder', groupFolderComponent);
Vue.component('GroupItem', groupItemComponent);
vm = createComponent();
- return vm.$nextTick();
+ await nextTick();
});
afterEach(() => {
@@ -52,20 +52,18 @@ describe('GroupsComponent', () => {
});
describe('template', () => {
- it('should render component template correctly', () => {
- return vm.$nextTick().then(() => {
- expect(vm.$el.querySelector('.groups-list-tree-container')).toBeDefined();
- expect(vm.$el.querySelector('.group-list-tree')).toBeDefined();
- expect(vm.$el.querySelector('.gl-pagination')).toBeDefined();
- expect(vm.$el.querySelectorAll('.has-no-search-results').length).toBe(0);
- });
+ it('should render component template correctly', async () => {
+ await nextTick();
+ expect(vm.$el.querySelector('.groups-list-tree-container')).toBeDefined();
+ expect(vm.$el.querySelector('.group-list-tree')).toBeDefined();
+ expect(vm.$el.querySelector('.gl-pagination')).toBeDefined();
+ expect(vm.$el.querySelectorAll('.has-no-search-results').length).toBe(0);
});
- it('should render empty search message when `searchEmpty` is `true`', () => {
+ it('should render empty search message when `searchEmpty` is `true`', async () => {
vm.searchEmpty = true;
- return vm.$nextTick().then(() => {
- expect(vm.$el.querySelector('.has-no-search-results')).toBeDefined();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.has-no-search-results')).toBeDefined();
});
});
});
diff --git a/spec/frontend/ide/components/activity_bar_spec.js b/spec/frontend/ide/components/activity_bar_spec.js
index 657817eb3d8..39fe2c7e723 100644
--- a/spec/frontend/ide/components/activity_bar_spec.js
+++ b/spec/frontend/ide/components/activity_bar_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import ActivityBar from '~/ide/components/activity_bar.vue';
import { leftSidebarViews } from '~/ide/constants';
@@ -61,14 +61,11 @@ describe('IDE activity bar', () => {
expect(vm.$el.querySelector('.js-ide-edit-mode').classList).toContain('active');
});
- it('sets commit item active', (done) => {
+ it('sets commit item active', async () => {
vm.$store.state.currentActivityView = leftSidebarViews.commit.name;
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.js-ide-commit-mode').classList).toContain('active');
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.js-ide-commit-mode').classList).toContain('active');
});
});
diff --git a/spec/frontend/ide/components/commit_sidebar/actions_spec.js b/spec/frontend/ide/components/commit_sidebar/actions_spec.js
index ed9d11246ae..c9425f6c9cd 100644
--- a/spec/frontend/ide/components/commit_sidebar/actions_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/actions_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { projectData, branches } from 'jest/ide/mock_data';
import commitActions from '~/ide/components/commit_sidebar/actions.vue';
@@ -71,15 +71,12 @@ describe('IDE commit sidebar actions', () => {
expect(findText()).toContain('Commit to main branch');
});
- it('hides merge request option when project merge requests are disabled', (done) => {
+ it('hides merge request option when project merge requests are disabled', async () => {
createComponent({ hasMR: false });
- vm.$nextTick(() => {
- expect(findRadios().length).toBe(2);
- expect(findText()).not.toContain('Create a new branch and merge request');
-
- done();
- });
+ await nextTick();
+ expect(findRadios().length).toBe(2);
+ expect(findText()).not.toContain('Create a new branch and merge request');
});
describe('currentBranchText', () => {
@@ -105,22 +102,18 @@ describe('IDE commit sidebar actions', () => {
expect(vm.$store.dispatch).not.toHaveBeenCalled();
});
- it('calls again after staged changes', (done) => {
+ it('calls again after staged changes', async () => {
createComponent({ currentBranchId: null });
vm.$store.state.currentBranchId = 'main';
vm.$store.state.changedFiles.push({});
vm.$store.state.stagedFiles.push({});
- vm.$nextTick()
- .then(() => {
- expect(vm.$store.dispatch).toHaveBeenCalledWith(
- ACTION_UPDATE_COMMIT_ACTION,
- expect.anything(),
- );
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ expect.anything(),
+ );
});
it.each`
diff --git a/spec/frontend/ide/components/commit_sidebar/list_item_spec.js b/spec/frontend/ide/components/commit_sidebar/list_item_spec.js
index b91ee88e0d6..78810da7f2c 100644
--- a/spec/frontend/ide/components/commit_sidebar/list_item_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/list_item_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { trimText } from 'helpers/text_helper';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import listItem from '~/ide/components/commit_sidebar/list_item.vue';
@@ -41,26 +41,18 @@ describe('Multi-file editor commit sidebar list item', () => {
expect(findPathText()).toContain(f.path);
});
- it('correctly renders renamed entries', (done) => {
+ it('correctly renders renamed entries', async () => {
Vue.set(vm.file, 'prevName', 'Old name');
- vm.$nextTick()
- .then(() => {
- expect(findPathText()).toEqual(`Old name → ${f.name}`);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(findPathText()).toEqual(`Old name → ${f.name}`);
});
- it('correctly renders entry, the name of which did not change after rename (as within a folder)', (done) => {
+ it('correctly renders entry, the name of which did not change after rename (as within a folder)', async () => {
Vue.set(vm.file, 'prevName', f.name);
- vm.$nextTick()
- .then(() => {
- expect(findPathText()).toEqual(f.name);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(findPathText()).toEqual(f.name);
});
it('opens a closed file in the editor when clicking the file path', (done) => {
@@ -134,14 +126,11 @@ describe('Multi-file editor commit sidebar list item', () => {
expect(vm.$el.querySelector('.is-active')).toBe(null);
});
- it('adds active class when keys match', (done) => {
+ it('adds active class when keys match', async () => {
vm.keyPrefix = 'staged';
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.is-active')).not.toBe(null);
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.is-active')).not.toBe(null);
});
});
});
diff --git a/spec/frontend/ide/components/commit_sidebar/message_field_spec.js b/spec/frontend/ide/components/commit_sidebar/message_field_spec.js
index 1514fbc2c3b..e66de6bb0b0 100644
--- a/spec/frontend/ide/components/commit_sidebar/message_field_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/message_field_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import createComponent from 'helpers/vue_mount_component_helper';
import CommitMessageField from '~/ide/components/commit_sidebar/message_field.vue';
@@ -23,34 +23,23 @@ describe('IDE commit message field', () => {
vm.$destroy();
});
- it('adds is-focused class on focus', (done) => {
+ it('adds is-focused class on focus', async () => {
vm.$el.querySelector('textarea').focus();
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.is-focused')).not.toBeNull();
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.is-focused')).not.toBeNull();
});
- it('removed is-focused class on blur', (done) => {
+ it('removed is-focused class on blur', async () => {
vm.$el.querySelector('textarea').focus();
- vm.$nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.is-focused')).not.toBeNull();
-
- vm.$el.querySelector('textarea').blur();
+ await nextTick();
+ expect(vm.$el.querySelector('.is-focused')).not.toBeNull();
- return vm.$nextTick();
- })
- .then(() => {
- expect(vm.$el.querySelector('.is-focused')).toBeNull();
+ vm.$el.querySelector('textarea').blur();
- done();
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.querySelector('.is-focused')).toBeNull();
});
it('emits input event on input', () => {
@@ -66,105 +55,78 @@ describe('IDE commit message field', () => {
describe('highlights', () => {
describe('subject line', () => {
- it('does not highlight less than 50 characters', (done) => {
+ it('does not highlight less than 50 characters', async () => {
vm.text = 'text less than 50 chars';
- vm.$nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.highlights span').textContent).toContain(
- 'text less than 50 chars',
- );
+ await nextTick();
+ expect(vm.$el.querySelector('.highlights span').textContent).toContain(
+ 'text less than 50 chars',
+ );
- expect(vm.$el.querySelector('mark').style.display).toBe('none');
- })
- .then(done)
- .catch(done.fail);
+ expect(vm.$el.querySelector('mark').style.display).toBe('none');
});
- it('highlights characters over 50 length', (done) => {
+ it('highlights characters over 50 length', async () => {
vm.text =
'text less than 50 chars that should not highlighted. text more than 50 should be highlighted';
- vm.$nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.highlights span').textContent).toContain(
- 'text less than 50 chars that should not highlighte',
- );
-
- expect(vm.$el.querySelector('mark').style.display).not.toBe('none');
- expect(vm.$el.querySelector('mark').textContent).toBe(
- 'd. text more than 50 should be highlighted',
- );
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.querySelector('.highlights span').textContent).toContain(
+ 'text less than 50 chars that should not highlighte',
+ );
+
+ expect(vm.$el.querySelector('mark').style.display).not.toBe('none');
+ expect(vm.$el.querySelector('mark').textContent).toBe(
+ 'd. text more than 50 should be highlighted',
+ );
});
});
describe('body text', () => {
- it('does not highlight body text less tan 72 characters', (done) => {
+ it('does not highlight body text less tan 72 characters', async () => {
vm.text = 'subject line\nbody content';
- vm.$nextTick()
- .then(() => {
- expect(vm.$el.querySelectorAll('.highlights span').length).toBe(2);
- expect(vm.$el.querySelectorAll('mark')[1].style.display).toBe('none');
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.querySelectorAll('.highlights span').length).toBe(2);
+ expect(vm.$el.querySelectorAll('mark')[1].style.display).toBe('none');
});
- it('highlights body text more than 72 characters', (done) => {
+ it('highlights body text more than 72 characters', async () => {
vm.text =
'subject line\nbody content that will be highlighted when it is more than 72 characters in length';
- vm.$nextTick()
- .then(() => {
- expect(vm.$el.querySelectorAll('.highlights span').length).toBe(2);
- expect(vm.$el.querySelectorAll('mark')[1].style.display).not.toBe('none');
- expect(vm.$el.querySelectorAll('mark')[1].textContent).toBe(' in length');
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.querySelectorAll('.highlights span').length).toBe(2);
+ expect(vm.$el.querySelectorAll('mark')[1].style.display).not.toBe('none');
+ expect(vm.$el.querySelectorAll('mark')[1].textContent).toBe(' in length');
});
- it('highlights body text & subject line', (done) => {
+ it('highlights body text & subject line', async () => {
vm.text =
'text less than 50 chars that should not highlighted\nbody content that will be highlighted when it is more than 72 characters in length';
- vm.$nextTick()
- .then(() => {
- expect(vm.$el.querySelectorAll('.highlights span').length).toBe(2);
- expect(vm.$el.querySelectorAll('mark').length).toBe(2);
+ await nextTick();
+ expect(vm.$el.querySelectorAll('.highlights span').length).toBe(2);
+ expect(vm.$el.querySelectorAll('mark').length).toBe(2);
- expect(vm.$el.querySelectorAll('mark')[0].textContent).toContain('d');
- expect(vm.$el.querySelectorAll('mark')[1].textContent).toBe(' in length');
- })
- .then(done)
- .catch(done.fail);
+ expect(vm.$el.querySelectorAll('mark')[0].textContent).toContain('d');
+ expect(vm.$el.querySelectorAll('mark')[1].textContent).toBe(' in length');
});
});
});
describe('scrolling textarea', () => {
- it('updates transform of highlights', (done) => {
+ it('updates transform of highlights', async () => {
vm.text = 'subject line\n\n\n\n\n\n\n\n\n\n\nbody content';
- vm.$nextTick()
- .then(() => {
- vm.$el.querySelector('textarea').scrollTo(0, 50);
-
- vm.handleScroll();
- })
- .then(vm.$nextTick)
- .then(() => {
- expect(vm.scrollTop).toBe(50);
- expect(vm.$el.querySelector('.highlights').style.transform).toBe(
- 'translate3d(0, -50px, 0)',
- );
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ vm.$el.querySelector('textarea').scrollTo(0, 50);
+
+ vm.handleScroll();
+
+ await nextTick();
+ expect(vm.scrollTop).toBe(50);
+ expect(vm.$el.querySelector('.highlights').style.transform).toBe('translate3d(0, -50px, 0)');
});
});
});
diff --git a/spec/frontend/ide/components/commit_sidebar/new_merge_request_option_spec.js b/spec/frontend/ide/components/commit_sidebar/new_merge_request_option_spec.js
index 4474647552d..64b53264b4d 100644
--- a/spec/frontend/ide/components/commit_sidebar/new_merge_request_option_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/new_merge_request_option_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { projectData, branches } from 'jest/ide/mock_data';
import NewMergeRequestOption from '~/ide/components/commit_sidebar/new_merge_request_option.vue';
@@ -72,15 +72,11 @@ describe('create new MR checkbox', () => {
expect(vm.$el.textContent).not.toBe('');
});
- it('has new MR', (done) => {
+ it('has new MR', async () => {
setMR();
- vm.$nextTick()
- .then(() => {
- expect(vm.$el.textContent).not.toBe('');
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.textContent).not.toBe('');
});
});
@@ -96,15 +92,11 @@ describe('create new MR checkbox', () => {
expect(vm.$el.textContent).toBe('');
});
- it('has new MR', (done) => {
+ it('has new MR', async () => {
setMR();
- vm.$nextTick()
- .then(() => {
- expect(vm.$el.textContent).toBe('');
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.textContent).toBe('');
});
});
});
@@ -121,15 +113,11 @@ describe('create new MR checkbox', () => {
expect(vm.$el.textContent).not.toBe('');
});
- it('is rendered if MR exists', (done) => {
+ it('is rendered if MR exists', async () => {
setMR();
- vm.$nextTick()
- .then(() => {
- expect(vm.$el.textContent).not.toBe('');
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.textContent).not.toBe('');
});
});
@@ -144,15 +132,11 @@ describe('create new MR checkbox', () => {
expect(vm.$el.textContent).not.toBe('');
});
- it('is hidden if MR exists', (done) => {
+ it('is hidden if MR exists', async () => {
setMR();
- vm.$nextTick()
- .then(() => {
- expect(vm.$el.textContent).toBe('');
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.textContent).toBe('');
});
});
});
@@ -168,15 +152,11 @@ describe('create new MR checkbox', () => {
expect(vm.$el.textContent).not.toBe('');
});
- it('is hidden if MR exists', (done) => {
+ it('is hidden if MR exists', async () => {
setMR();
- vm.$nextTick()
- .then(() => {
- expect(vm.$el.textContent).toBe('');
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.textContent).toBe('');
});
it('shows enablded checkbox', () => {
diff --git a/spec/frontend/ide/components/file_row_extra_spec.js b/spec/frontend/ide/components/file_row_extra_spec.js
index 641407c7b77..5a7a1fe7db0 100644
--- a/spec/frontend/ide/components/file_row_extra_spec.js
+++ b/spec/frontend/ide/components/file_row_extra_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import FileRowExtra from '~/ide/components/file_row_extra.vue';
import { createStore } from '~/ide/stores';
@@ -70,28 +70,22 @@ describe('IDE extra file row component', () => {
expect(vm.$el.querySelector('.ide-tree-changes')).toBe(null);
});
- it('does not show when tree is open', (done) => {
+ it('does not show when tree is open', async () => {
vm.file.type = 'tree';
vm.file.opened = true;
changesCount = 1;
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.ide-tree-changes')).toBe(null);
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.ide-tree-changes')).toBe(null);
});
- it('shows for trees with changes', (done) => {
+ it('shows for trees with changes', async () => {
vm.file.type = 'tree';
vm.file.opened = false;
changesCount = 1;
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.ide-tree-changes')).not.toBe(null);
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.ide-tree-changes')).not.toBe(null);
});
});
@@ -100,55 +94,40 @@ describe('IDE extra file row component', () => {
expect(vm.$el.querySelector('.file-changed-icon')).toBe(null);
});
- it('shows when file is changed', (done) => {
+ it('shows when file is changed', async () => {
vm.file.changed = true;
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null);
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null);
});
- it('shows when file is staged', (done) => {
+ it('shows when file is staged', async () => {
vm.file.staged = true;
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null);
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null);
});
- it('shows when file is a tempFile', (done) => {
+ it('shows when file is a tempFile', async () => {
vm.file.tempFile = true;
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null);
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null);
});
- it('shows when file is renamed', (done) => {
+ it('shows when file is renamed', async () => {
vm.file.prevPath = 'original-file';
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null);
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null);
});
- it('hides when file is renamed', (done) => {
+ it('hides when file is renamed', async () => {
vm.file.prevPath = 'original-file';
vm.file.type = 'tree';
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.file-changed-icon')).toBe(null);
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.file-changed-icon')).toBe(null);
});
});
@@ -157,14 +136,11 @@ describe('IDE extra file row component', () => {
expect(vm.$el.querySelector('[data-testid="git-merge-icon"]')).toBe(null);
});
- it('shows when a merge request change', (done) => {
+ it('shows when a merge request change', async () => {
vm.file.mrChange = true;
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('[data-testid="git-merge-icon"]')).not.toBe(null);
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('[data-testid="git-merge-icon"]')).not.toBe(null);
});
});
});
diff --git a/spec/frontend/ide/components/file_templates/bar_spec.js b/spec/frontend/ide/components/file_templates/bar_spec.js
index 4ca99f8d055..e8ebfa78fe9 100644
--- a/spec/frontend/ide/components/file_templates/bar_spec.js
+++ b/spec/frontend/ide/components/file_templates/bar_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import Bar from '~/ide/components/file_templates/bar.vue';
import { createStore } from '~/ide/stores';
@@ -46,7 +46,7 @@ describe('IDE file templates bar component', () => {
});
describe('template dropdown', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
vm.$store.state.fileTemplates.templates = [
{
name: 'test',
@@ -57,7 +57,7 @@ describe('IDE file templates bar component', () => {
key: 'gitlab_ci_ymls',
};
- vm.$nextTick(done);
+ await nextTick();
});
it('renders dropdown component', () => {
@@ -75,14 +75,11 @@ describe('IDE file templates bar component', () => {
});
});
- it('shows undo button if updateSuccess is true', (done) => {
+ it('shows undo button if updateSuccess is true', async () => {
vm.$store.state.fileTemplates.updateSuccess = true;
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.btn-default').style.display).not.toBe('none');
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.btn-default').style.display).not.toBe('none');
});
it('calls undoFileTemplate when clicking undo button', () => {
@@ -93,7 +90,7 @@ describe('IDE file templates bar component', () => {
expect(vm.undoFileTemplate).toHaveBeenCalled();
});
- it('calls setSelectedTemplateType if activeFile name matches a template', (done) => {
+ it('calls setSelectedTemplateType if activeFile name matches a template', async () => {
const fileName = '.gitlab-ci.yml';
jest.spyOn(vm, 'setSelectedTemplateType').mockImplementation(() => {});
@@ -101,13 +98,10 @@ describe('IDE file templates bar component', () => {
vm.setInitialType();
- vm.$nextTick(() => {
- expect(vm.setSelectedTemplateType).toHaveBeenCalledWith({
- name: fileName,
- key: 'gitlab_ci_ymls',
- });
-
- done();
+ await nextTick();
+ expect(vm.setSelectedTemplateType).toHaveBeenCalledWith({
+ name: fileName,
+ key: 'gitlab_ci_ymls',
});
});
});
diff --git a/spec/frontend/ide/components/ide_status_bar_spec.js b/spec/frontend/ide/components/ide_status_bar_spec.js
index f1a0b64caf2..00ef75fcf3a 100644
--- a/spec/frontend/ide/components/ide_status_bar_spec.js
+++ b/spec/frontend/ide/components/ide_status_bar_spec.js
@@ -1,5 +1,5 @@
import _ from 'lodash';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import IdeStatusBar from '~/ide/components/ide_status_bar.vue';
@@ -73,7 +73,7 @@ describe('ideStatusBar', () => {
});
describe('pipeline status', () => {
- it('opens right sidebar on clicking icon', (done) => {
+ it('opens right sidebar on clicking icon', async () => {
jest.spyOn(vm, 'openRightPane').mockImplementation(() => {});
Vue.set(vm.$store.state.pipelines, 'latestPipeline', {
details: {
@@ -88,14 +88,10 @@ describe('ideStatusBar', () => {
},
});
- vm.$nextTick()
- .then(() => {
- vm.$el.querySelector('.ide-status-pipeline button').click();
+ await nextTick();
+ vm.$el.querySelector('.ide-status-pipeline button').click();
- expect(vm.openRightPane).toHaveBeenCalledWith(rightSidebarViews.pipelines);
- })
- .then(done)
- .catch(done.fail);
+ expect(vm.openRightPane).toHaveBeenCalledWith(rightSidebarViews.pipelines);
});
});
diff --git a/spec/frontend/ide/components/ide_tree_list_spec.js b/spec/frontend/ide/components/ide_tree_list_spec.js
index ace51204374..a85c52f5e86 100644
--- a/spec/frontend/ide/components/ide_tree_list_spec.js
+++ b/spec/frontend/ide/components/ide_tree_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 IdeTreeList from '~/ide/components/ide_tree_list.vue';
import { createStore } from '~/ide/stores';
@@ -48,15 +48,12 @@ describe('IDE tree list', () => {
expect(vm.$emit).toHaveBeenCalledWith('tree-ready');
});
- it('renders loading indicator', (done) => {
+ it('renders loading indicator', async () => {
store.state.trees['abcproject/main'].loading = true;
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.multi-file-loading-container')).not.toBeNull();
- expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toBe(3);
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.multi-file-loading-container')).not.toBeNull();
+ expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toBe(3);
});
it('renders list of files', () => {
diff --git a/spec/frontend/ide/components/jobs/detail_spec.js b/spec/frontend/ide/components/jobs/detail_spec.js
index 3634599f328..9122471d421 100644
--- a/spec/frontend/ide/components/jobs/detail_spec.js
+++ b/spec/frontend/ide/components/jobs/detail_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import JobDetail from '~/ide/components/jobs/detail.vue';
@@ -48,14 +48,11 @@ describe('IDE jobs detail view', () => {
expect(vm.$el.querySelector('.bash').textContent).toContain('testing');
});
- it('renders empty message output', (done) => {
+ it('renders empty message output', async () => {
vm.$store.state.pipelines.detailJob.output = '';
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.bash').textContent).toContain('No messages were logged');
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.bash').textContent).toContain('No messages were logged');
});
it('renders loading icon', () => {
@@ -68,14 +65,11 @@ describe('IDE jobs detail view', () => {
expect(vm.$el.querySelector('.bash').style.display).toBe('none');
});
- it('hide loading icon when isLoading is false', (done) => {
+ it('hide loading icon when isLoading is false', async () => {
vm.$store.state.pipelines.detailJob.isLoading = false;
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.build-loader-animation').style.display).toBe('none');
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.build-loader-animation').style.display).toBe('none');
});
it('resets detailJob when clicking header button', () => {
@@ -107,17 +101,16 @@ describe('IDE jobs detail view', () => {
fnName | btnName | scrollPos
${'scrollDown'} | ${'down'} | ${0}
${'scrollUp'} | ${'up'} | ${1}
- `('triggers $fnName when clicking $btnName button', ({ fnName, scrollPos }) => {
+ `('triggers $fnName when clicking $btnName button', async ({ fnName, scrollPos }) => {
jest.spyOn(vm, fnName).mockImplementation();
vm = vm.$mount();
vm.scrollPos = scrollPos;
- return vm.$nextTick().then(() => {
- vm.$el.querySelector('.btn-scroll:not([disabled])').click();
- expect(vm[fnName]).toHaveBeenCalled();
- });
+ await nextTick();
+ vm.$el.querySelector('.btn-scroll:not([disabled])').click();
+ expect(vm[fnName]).toHaveBeenCalled();
});
});
diff --git a/spec/frontend/ide/components/jobs/item_spec.js b/spec/frontend/ide/components/jobs/item_spec.js
index 7343fc80a03..c76760a5522 100644
--- a/spec/frontend/ide/components/jobs/item_spec.js
+++ b/spec/frontend/ide/components/jobs/item_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import JobItem from '~/ide/components/jobs/item.vue';
import { jobs } from '../../mock_data';
@@ -27,13 +27,10 @@ describe('IDE jobs item', () => {
expect(vm.$el.querySelector('[data-testid="status_success_borderless-icon"]')).not.toBe(null);
});
- it('does not render view logs button if not started', (done) => {
+ it('does not render view logs button if not started', async () => {
vm.job.started = false;
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.btn')).toBe(null);
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.btn')).toBe(null);
});
});
diff --git a/spec/frontend/ide/components/nav_dropdown_button_spec.js b/spec/frontend/ide/components/nav_dropdown_button_spec.js
index a02bfa5c391..1c14685df68 100644
--- a/spec/frontend/ide/components/nav_dropdown_button_spec.js
+++ b/spec/frontend/ide/components/nav_dropdown_button_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { trimText } from 'helpers/text_helper';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import NavDropdownButton from '~/ide/components/nav_dropdown_button.vue';
@@ -36,38 +36,26 @@ describe('NavDropdown', () => {
expect(trimText(vm.$el.textContent)).toEqual('- -');
});
- it('renders branch name, if state has currentBranchId', (done) => {
+ it('renders branch name, if state has currentBranchId', async () => {
vm.$store.state.currentBranchId = TEST_BRANCH_ID;
- vm.$nextTick()
- .then(() => {
- expect(trimText(vm.$el.textContent)).toEqual(`${TEST_BRANCH_ID} -`);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(trimText(vm.$el.textContent)).toEqual(`${TEST_BRANCH_ID} -`);
});
- it('renders mr id, if state has currentMergeRequestId', (done) => {
+ it('renders mr id, if state has currentMergeRequestId', async () => {
vm.$store.state.currentMergeRequestId = TEST_MR_ID;
- vm.$nextTick()
- .then(() => {
- expect(trimText(vm.$el.textContent)).toEqual(`- !${TEST_MR_ID}`);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(trimText(vm.$el.textContent)).toEqual(`- !${TEST_MR_ID}`);
});
- it('renders branch and mr, if state has both', (done) => {
+ it('renders branch and mr, if state has both', async () => {
vm.$store.state.currentBranchId = TEST_BRANCH_ID;
vm.$store.state.currentMergeRequestId = TEST_MR_ID;
- vm.$nextTick()
- .then(() => {
- expect(trimText(vm.$el.textContent)).toEqual(`${TEST_BRANCH_ID} !${TEST_MR_ID}`);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(trimText(vm.$el.textContent)).toEqual(`${TEST_BRANCH_ID} !${TEST_MR_ID}`);
});
it('shows icons', () => {
diff --git a/spec/frontend/ide/components/nav_dropdown_spec.js b/spec/frontend/ide/components/nav_dropdown_spec.js
index 6a1be7ee964..33e638843f5 100644
--- a/spec/frontend/ide/components/nav_dropdown_spec.js
+++ b/spec/frontend/ide/components/nav_dropdown_spec.js
@@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils';
import $ from 'jquery';
+import { nextTick } from 'vue';
import NavDropdown from '~/ide/components/nav_dropdown.vue';
import { PERMISSION_READ_MR } from '~/ide/constants';
import { createStore } from '~/ide/stores';
@@ -58,29 +59,19 @@ describe('IDE NavDropdown', () => {
expect(findNavForm().exists()).toBe(false);
});
- it('renders nav form when show.bs.dropdown', (done) => {
+ it('renders nav form when show.bs.dropdown', async () => {
showDropdown();
- wrapper.vm
- .$nextTick()
- .then(() => {
- expect(findNavForm().exists()).toBe(true);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(findNavForm().exists()).toBe(true);
});
- it('destroys nav form when closed', (done) => {
+ it('destroys nav form when closed', async () => {
showDropdown();
hideDropdown();
- wrapper.vm
- .$nextTick()
- .then(() => {
- expect(findNavForm().exists()).toBe(false);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(findNavForm().exists()).toBe(false);
});
it('renders merge request icon', () => {
diff --git a/spec/frontend/ide/components/new_dropdown/button_spec.js b/spec/frontend/ide/components/new_dropdown/button_spec.js
index 32fa2babcdb..298d7b810e1 100644
--- a/spec/frontend/ide/components/new_dropdown/button_spec.js
+++ b/spec/frontend/ide/components/new_dropdown/button_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import Button from '~/ide/components/new_dropdown/button.vue';
@@ -37,14 +37,11 @@ describe('IDE new entry dropdown button component', () => {
expect(vm.$emit).toHaveBeenCalledWith('click');
});
- it('hides label if showLabel is false', (done) => {
+ it('hides label if showLabel is false', async () => {
vm.showLabel = false;
- vm.$nextTick(() => {
- expect(vm.$el.textContent).not.toContain('Testing');
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.textContent).not.toContain('Testing');
});
describe('tooltipTitle', () => {
@@ -52,14 +49,11 @@ describe('IDE new entry dropdown button component', () => {
expect(vm.tooltipTitle).toBe('');
});
- it('returns label', (done) => {
+ it('returns label', async () => {
vm.showLabel = false;
- vm.$nextTick(() => {
- expect(vm.tooltipTitle).toBe('Testing');
-
- done();
- });
+ await nextTick();
+ expect(vm.tooltipTitle).toBe('Testing');
});
});
});
diff --git a/spec/frontend/ide/components/new_dropdown/modal_spec.js b/spec/frontend/ide/components/new_dropdown/modal_spec.js
index 41111f5dbb4..8134248bbf4 100644
--- a/spec/frontend/ide/components/new_dropdown/modal_spec.js
+++ b/spec/frontend/ide/components/new_dropdown/modal_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import createFlash from '~/flash';
import modal from '~/ide/components/new_dropdown/modal.vue';
@@ -19,14 +19,14 @@ describe('new file modal component', () => {
${'tree'} | ${'Create new directory'} | ${'Create directory'} | ${false}
${'blob'} | ${'Create new file'} | ${'Create file'} | ${true}
`('$entryType', ({ entryType, modalTitle, btnTitle, showsFileTemplates }) => {
- beforeEach((done) => {
+ beforeEach(async () => {
const store = createStore();
vm = createComponentWithStore(Component, store).$mount();
vm.open(entryType);
vm.name = 'testing';
- vm.$nextTick(done);
+ await nextTick();
});
afterEach(() => {
@@ -71,16 +71,13 @@ describe('new file modal component', () => {
${'blob'} | ${'Rename file'} | ${'Rename file'}
`(
'renders title and button for renaming $entryType',
- ({ entryType, modalTitle, btnTitle }, done) => {
+ async ({ entryType, modalTitle, btnTitle }) => {
vm.$store.state.entries['test-path'].type = entryType;
vm.open('rename', 'test-path');
- vm.$nextTick(() => {
- expect(document.querySelector('.modal-title').textContent.trim()).toBe(modalTitle);
- expect(document.querySelector('.btn-success').textContent.trim()).toBe(btnTitle);
-
- done();
- });
+ await nextTick();
+ expect(document.querySelector('.modal-title').textContent.trim()).toBe(modalTitle);
+ expect(document.querySelector('.btn-success').textContent.trim()).toBe(btnTitle);
},
);
diff --git a/spec/frontend/ide/components/repo_editor_spec.js b/spec/frontend/ide/components/repo_editor_spec.js
index 15af2d03704..96c9baeb328 100644
--- a/spec/frontend/ide/components/repo_editor_spec.js
+++ b/spec/frontend/ide/components/repo_editor_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { editor as monacoEditor, Range } from 'monaco-editor';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import '~/behaviors/markdown/render_gfm';
import waitForPromises from 'helpers/wait_for_promises';
@@ -367,17 +367,17 @@ describe('RepoEditor', () => {
expect(vm.$store.state.panelResizing).toBe(false); // default value
vm.$store.state.panelResizing = true;
- await vm.$nextTick();
+ await nextTick();
expect(updateDimensionsSpy).not.toHaveBeenCalled();
vm.$store.state.panelResizing = false;
- await vm.$nextTick();
+ await nextTick();
expect(updateDimensionsSpy).toHaveBeenCalledTimes(1);
vm.$store.state.panelResizing = true;
- await vm.$nextTick();
+ await nextTick();
expect(updateDimensionsSpy).toHaveBeenCalledTimes(1);
});
@@ -387,12 +387,12 @@ describe('RepoEditor', () => {
expect(vm.$store.state.rightPane.isOpen).toBe(false); // default value
vm.$store.state.rightPane.isOpen = true;
- await vm.$nextTick();
+ await nextTick();
expect(updateDimensionsSpy).toHaveBeenCalledTimes(1);
vm.$store.state.rightPane.isOpen = false;
- await vm.$nextTick();
+ await nextTick();
expect(updateDimensionsSpy).toHaveBeenCalledTimes(2);
});
@@ -411,7 +411,7 @@ describe('RepoEditor', () => {
`('tabs in $mode are $isVisible', async ({ mode, isVisible } = {}) => {
vm.$store.state.currentActivityView = leftSidebarViews[mode].name;
- await vm.$nextTick();
+ await nextTick();
expect(wrapper.find('.nav-links').exists()).toBe(isVisible);
});
});
@@ -436,7 +436,7 @@ describe('RepoEditor', () => {
});
changeViewMode(FILE_VIEW_MODE_PREVIEW);
- await vm.$nextTick();
+ await nextTick();
});
it('do not show the editor', () => {
@@ -448,7 +448,7 @@ describe('RepoEditor', () => {
expect(updateDimensionsSpy).not.toHaveBeenCalled();
changeViewMode(FILE_VIEW_MODE_EDITOR);
- await vm.$nextTick();
+ await nextTick();
expect(updateDimensionsSpy).toHaveBeenCalled();
});
@@ -460,7 +460,7 @@ describe('RepoEditor', () => {
jest.spyOn(vm, 'shouldHideEditor', 'get').mockReturnValue(true);
vm.initEditor();
- await vm.$nextTick();
+ await nextTick();
};
it('does not fetch file information for temp entries', async () => {
@@ -511,20 +511,20 @@ describe('RepoEditor', () => {
const origFile = vm.file;
vm.file.pending = true;
- await vm.$nextTick();
+ await nextTick();
wrapper.setProps({
file: file('testing'),
});
vm.file.content = 'foo'; // need to prevent full cycle of initEditor
- await vm.$nextTick();
+ await nextTick();
expect(vm.removePendingTab).toHaveBeenCalledWith(origFile);
});
it('does not call initEditor if the file did not change', async () => {
Vue.set(vm, 'file', vm.file);
- await vm.$nextTick();
+ await nextTick();
expect(vm.initEditor).not.toHaveBeenCalled();
});
@@ -538,7 +538,7 @@ describe('RepoEditor', () => {
key: 'new',
},
});
- await vm.$nextTick();
+ await nextTick();
expect(vm.initEditor).toHaveBeenCalled();
});
diff --git a/spec/frontend/ide/components/shared/tokened_input_spec.js b/spec/frontend/ide/components/shared/tokened_input_spec.js
index 837bfe6b574..a37c08af0a1 100644
--- a/spec/frontend/ide/components/shared/tokened_input_spec.js
+++ b/spec/frontend/ide/components/shared/tokened_input_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import TokenedInput from '~/ide/components/shared/tokened_input.vue';
@@ -54,15 +54,11 @@ describe('IDE shared/TokenedInput', () => {
expect(vm.$refs.input).toHaveValue(TEST_VALUE);
});
- it('renders placeholder, when tokens are empty', (done) => {
+ it('renders placeholder, when tokens are empty', async () => {
vm.tokens = [];
- vm.$nextTick()
- .then(() => {
- expect(vm.$refs.input).toHaveAttr('placeholder', TEST_PLACEHOLDER);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$refs.input).toHaveAttr('placeholder', TEST_PLACEHOLDER);
});
it('triggers "removeToken" on token click', () => {
diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js
index d39e00b9c9e..df6f7cb827d 100644
--- a/spec/frontend/issues/show/components/description_spec.js
+++ b/spec/frontend/issues/show/components/description_spec.js
@@ -1,21 +1,56 @@
import $ from 'jquery';
-import Vue from 'vue';
+import { nextTick } from 'vue';
import '~/behaviors/markdown/render_gfm';
+import { GlPopover, GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { stubComponent } from 'helpers/stub_component';
import { TEST_HOST } from 'helpers/test_constants';
-import mountComponent from 'helpers/vue_mount_component_helper';
import Description from '~/issues/show/components/description.vue';
import TaskList from '~/task_list';
-import { descriptionProps as props } from '../mock_data/mock_data';
+import CreateWorkItem from '~/work_items/pages/create_work_item.vue';
+import {
+ descriptionProps as initialProps,
+ descriptionHtmlWithCheckboxes,
+} from '../mock_data/mock_data';
jest.mock('~/task_list');
+const showModal = jest.fn();
+const hideModal = jest.fn();
+
describe('Description component', () => {
- let vm;
- let DescriptionComponent;
+ let wrapper;
+
+ const findGfmContent = () => wrapper.find('[data-testid="gfm-content"]');
+ const findTextarea = () => wrapper.find('[data-testid="textarea"]');
+ const findTaskActionButtons = () => wrapper.findAll('.js-add-task');
+ const findConvertToTaskButton = () => wrapper.find('[data-testid="convert-to-task"]');
+ const findTaskSvg = () => wrapper.find('[data-testid="issue-open-m-icon"]');
+
+ const findPopovers = () => wrapper.findAllComponents(GlPopover);
+ const findModal = () => wrapper.findComponent(GlModal);
+ const findCreateWorkItem = () => wrapper.findComponent(CreateWorkItem);
+
+ function createComponent({ props = {}, provide = {} } = {}) {
+ wrapper = shallowMount(Description, {
+ propsData: {
+ ...initialProps,
+ ...props,
+ },
+ provide,
+ stubs: {
+ GlModal: stubComponent(GlModal, {
+ methods: {
+ show: showModal,
+ hide: hideModal,
+ },
+ }),
+ GlPopover,
+ },
+ });
+ }
beforeEach(() => {
- DescriptionComponent = Vue.extend(Description);
-
if (!document.querySelector('.issuable-meta')) {
const metaData = document.createElement('div');
metaData.classList.add('issuable-meta');
@@ -24,91 +59,102 @@ describe('Description component', () => {
document.body.appendChild(metaData);
}
-
- vm = mountComponent(DescriptionComponent, props);
});
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
afterAll(() => {
$('.issuable-meta .flash-container').remove();
});
- it('doesnt animate first description changes', () => {
- vm.descriptionHtml = 'changed';
-
- return vm.$nextTick().then(() => {
- expect(
- vm.$el.querySelector('.md').classList.contains('issue-realtime-pre-pulse'),
- ).toBeFalsy();
- jest.runAllTimers();
- return vm.$nextTick();
+ it('doesnt animate first description changes', async () => {
+ createComponent();
+ await wrapper.setProps({
+ descriptionHtml: 'changed',
});
+
+ expect(findGfmContent().classes()).not.toContain('issue-realtime-pre-pulse');
});
- it('animates description changes on live update', () => {
- vm.descriptionHtml = 'changed';
- return vm
- .$nextTick()
- .then(() => {
- vm.descriptionHtml = 'changed second time';
- return vm.$nextTick();
- })
- .then(() => {
- expect(
- vm.$el.querySelector('.md').classList.contains('issue-realtime-pre-pulse'),
- ).toBeTruthy();
- jest.runAllTimers();
- return vm.$nextTick();
- })
- .then(() => {
- expect(
- vm.$el.querySelector('.md').classList.contains('issue-realtime-trigger-pulse'),
- ).toBeTruthy();
- });
+ it('animates description changes on live update', async () => {
+ createComponent();
+ await wrapper.setProps({
+ descriptionHtml: 'changed',
+ });
+
+ expect(findGfmContent().classes()).not.toContain('issue-realtime-pre-pulse');
+
+ await wrapper.setProps({
+ descriptionHtml: 'changed second time',
+ });
+
+ expect(findGfmContent().classes()).toContain('issue-realtime-pre-pulse');
+
+ await jest.runOnlyPendingTimers();
+
+ expect(findGfmContent().classes()).toContain('issue-realtime-trigger-pulse');
});
- it('applies syntax highlighting and math when description changed', () => {
- const vmSpy = jest.spyOn(vm, 'renderGFM');
+ it('applies syntax highlighting and math when description changed', async () => {
const prototypeSpy = jest.spyOn($.prototype, 'renderGFM');
- vm.descriptionHtml = 'changed';
+ createComponent();
- return vm.$nextTick().then(() => {
- expect(vm.$refs['gfm-content']).toBeDefined();
- expect(vmSpy).toHaveBeenCalled();
- expect(prototypeSpy).toHaveBeenCalled();
- expect($.prototype.renderGFM).toHaveBeenCalled();
+ await wrapper.setProps({
+ descriptionHtml: 'changed',
});
+
+ expect(findGfmContent().exists()).toBe(true);
+ expect(prototypeSpy).toHaveBeenCalled();
});
it('sets data-update-url', () => {
- expect(vm.$el.querySelector('textarea').dataset.updateUrl).toEqual(TEST_HOST);
+ createComponent();
+ expect(findTextarea().attributes('data-update-url')).toBe(TEST_HOST);
});
describe('TaskList', () => {
beforeEach(() => {
- vm.$destroy();
TaskList.mockClear();
- vm = mountComponent(DescriptionComponent, { ...props, issuableType: 'issuableType' });
});
it('re-inits the TaskList when description changed', () => {
- vm.descriptionHtml = 'changed';
+ createComponent({
+ props: {
+ issuableType: 'issuableType',
+ },
+ });
+ wrapper.setProps({
+ descriptionHtml: 'changed',
+ });
expect(TaskList).toHaveBeenCalled();
});
- it('does not re-init the TaskList when canUpdate is false', () => {
- vm.canUpdate = false;
- vm.descriptionHtml = 'changed';
+ it('does not re-init the TaskList when canUpdate is false', async () => {
+ createComponent({
+ props: {
+ issuableType: 'issuableType',
+ canUpdate: false,
+ },
+ });
+ wrapper.setProps({
+ descriptionHtml: 'changed',
+ });
- expect(TaskList).toHaveBeenCalledTimes(1);
+ expect(TaskList).not.toHaveBeenCalled();
});
it('calls with issuableType dataType', () => {
- vm.descriptionHtml = 'changed';
+ createComponent({
+ props: {
+ issuableType: 'issuableType',
+ },
+ });
+ wrapper.setProps({
+ descriptionHtml: 'changed',
+ });
expect(TaskList).toHaveBeenCalledWith({
dataType: 'issuableType',
@@ -123,65 +169,97 @@ describe('Description component', () => {
});
describe('taskStatus', () => {
- it('adds full taskStatus', () => {
- vm.taskStatus = '1 of 1';
-
- return vm.$nextTick().then(() => {
- expect(document.querySelector('.issuable-meta #task_status').textContent.trim()).toBe(
- '1 of 1',
- );
+ it('adds full taskStatus', async () => {
+ createComponent({
+ props: {
+ taskStatus: '1 of 1',
+ },
});
- });
+ await nextTick();
- it('adds short taskStatus', () => {
- vm.taskStatus = '1 of 1';
+ expect(document.querySelector('.issuable-meta #task_status').textContent.trim()).toBe(
+ '1 of 1',
+ );
+ });
- return vm.$nextTick().then(() => {
- expect(document.querySelector('.issuable-meta #task_status_short').textContent.trim()).toBe(
- '1/1 task',
- );
+ it('adds short taskStatus', async () => {
+ createComponent({
+ props: {
+ taskStatus: '1 of 1',
+ },
});
- });
+ await nextTick();
- it('clears task status text when no tasks are present', () => {
- vm.taskStatus = '0 of 0';
+ expect(document.querySelector('.issuable-meta #task_status_short').textContent.trim()).toBe(
+ '1/1 task',
+ );
+ });
- return vm.$nextTick().then(() => {
- expect(document.querySelector('.issuable-meta #task_status').textContent.trim()).toBe('');
+ it('clears task status text when no tasks are present', async () => {
+ createComponent({
+ props: {
+ taskStatus: '0 of 0',
+ },
});
+
+ await nextTick();
+
+ expect(document.querySelector('.issuable-meta #task_status').textContent.trim()).toBe('');
});
});
- describe('taskListUpdateStarted', () => {
- it('emits event to parent', () => {
- const spy = jest.spyOn(vm, '$emit');
-
- vm.taskListUpdateStarted();
+ describe('with work items feature flag is enabled', () => {
+ beforeEach(async () => {
+ createComponent({
+ props: {
+ descriptionHtml: descriptionHtmlWithCheckboxes,
+ },
+ provide: {
+ glFeatures: {
+ workItems: true,
+ },
+ },
+ });
+ await nextTick();
+ });
- expect(spy).toHaveBeenCalledWith('taskListUpdateStarted');
+ it('renders a list of hidden buttons corresponding to checkboxes in description HTML', () => {
+ expect(findTaskActionButtons()).toHaveLength(3);
});
- });
- describe('taskListUpdateSuccess', () => {
- it('emits event to parent', () => {
- const spy = jest.spyOn(vm, '$emit');
+ it('renders a list of popovers corresponding to checkboxes in description HTML', () => {
+ expect(findPopovers()).toHaveLength(3);
+ expect(findPopovers().at(0).props('target')).toBe(
+ findTaskActionButtons().at(0).attributes('id'),
+ );
+ });
- vm.taskListUpdateSuccess();
+ it('does not show a modal by default', () => {
+ expect(findModal().props('visible')).toBe(false);
+ });
- expect(spy).toHaveBeenCalledWith('taskListUpdateSucceeded');
+ it('opens a modal when a button on popover is clicked and displays correct title', async () => {
+ findConvertToTaskButton().vm.$emit('click');
+ expect(showModal).toHaveBeenCalled();
+ await nextTick();
+ expect(findCreateWorkItem().props('initialTitle').trim()).toBe('todo 1');
});
- });
- describe('taskListUpdateError', () => {
- it('should create flash notification and emit an event to parent', () => {
- const msg =
- 'Someone edited this issue at the same time you did. The description has been updated and you will need to make your changes again.';
- const spy = jest.spyOn(vm, '$emit');
+ it('closes the modal on `closeCreateTaskModal` event', () => {
+ findConvertToTaskButton().vm.$emit('click');
+ findCreateWorkItem().vm.$emit('closeModal');
+ expect(hideModal).toHaveBeenCalled();
+ });
- vm.taskListUpdateError();
+ it('updates description HTML on `onCreate` event', async () => {
+ const newTitle = 'New title';
+ findConvertToTaskButton().vm.$emit('click');
+ findCreateWorkItem().vm.$emit('onCreate', newTitle);
+ expect(hideModal).toHaveBeenCalled();
+ await nextTick();
- expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(msg);
- expect(spy).toHaveBeenCalledWith('taskListUpdateFailed');
+ expect(findTaskSvg().exists()).toBe(true);
+ expect(wrapper.text()).toContain(newTitle);
});
});
});
diff --git a/spec/frontend/issues/show/mock_data/mock_data.js b/spec/frontend/issues/show/mock_data/mock_data.js
index a73826954c3..89653ff82b2 100644
--- a/spec/frontend/issues/show/mock_data/mock_data.js
+++ b/spec/frontend/issues/show/mock_data/mock_data.js
@@ -58,3 +58,17 @@ export const appProps = {
zoomMeetingUrl,
publishedIncidentUrl,
};
+
+export const descriptionHtmlWithCheckboxes = `
+ <ul dir="auto" class="task-list" data-sourcepos"3:1-5:12">
+ <li class="task-list-item" data-sourcepos="3:1-3:11">
+ <input class="task-list-item-checkbox" type="checkbox"> todo 1
+ </li>
+ <li class="task-list-item" data-sourcepos="4:1-4:12">
+ <input class="task-list-item-checkbox" type="checkbox"> todo 2
+ </li>
+ <li class="task-list-item" data-sourcepos="5:1-5:12">
+ <input class="task-list-item-checkbox" type="checkbox"> todo 3
+ </li>
+ </ul>
+`;
diff --git a/spec/frontend/notebook/cells/code_spec.js b/spec/frontend/notebook/cells/code_spec.js
index 669bdc2f89a..6a51731c909 100644
--- a/spec/frontend/notebook/cells/code_spec.js
+++ b/spec/frontend/notebook/cells/code_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import fixture from 'test_fixtures/blob/notebook/basic.json';
import CodeComponent from '~/notebook/cells/code.vue';
@@ -58,12 +58,12 @@ describe('Code component', () => {
describe('with string for output', () => {
// NBFormat Version 4.1 allows outputs.text to be a string
- beforeEach(() => {
+ beforeEach(async () => {
const cell = json.cells[2];
cell.outputs[0].text = cell.outputs[0].text.join('');
vm = setupComponent(cell);
- return vm.$nextTick();
+ await nextTick();
});
it('does not render output prompt', () => {
@@ -76,12 +76,12 @@ describe('Code component', () => {
});
describe('with string for cell.source', () => {
- beforeEach(() => {
+ beforeEach(async () => {
const cell = json.cells[0];
cell.source = cell.source.join('');
vm = setupComponent(cell);
- return vm.$nextTick();
+ await nextTick();
});
it('renders the same input as when cell.source is an array', () => {
diff --git a/spec/frontend/notebook/cells/markdown_spec.js b/spec/frontend/notebook/cells/markdown_spec.js
index 36b1e91f15f..7dc6f90d202 100644
--- a/spec/frontend/notebook/cells/markdown_spec.js
+++ b/spec/frontend/notebook/cells/markdown_spec.js
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
import katex from 'katex';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import markdownTableJson from 'test_fixtures/blob/notebook/markdown-table.json';
import basicJson from 'test_fixtures/blob/notebook/basic.json';
import mathJson from 'test_fixtures/blob/notebook/math.json';
@@ -37,7 +37,7 @@ describe('Markdown component', () => {
let cell;
let json;
- beforeEach(() => {
+ beforeEach(async () => {
json = basicJson;
// eslint-disable-next-line prefer-destructuring
@@ -45,7 +45,7 @@ describe('Markdown component', () => {
vm = buildCellComponent(cell);
- return vm.$nextTick();
+ await nextTick();
});
it('does not render prompt', () => {
@@ -67,7 +67,7 @@ describe('Markdown component', () => {
],
});
- await vm.$nextTick();
+ await nextTick();
expect(vm.$el.querySelector('a').getAttribute('href')).toBeNull();
});
@@ -77,7 +77,7 @@ describe('Markdown component', () => {
source: ['<a href="test.js" data-remote=true data-type="script" class="xss-link">XSS</a>\n'],
});
- await vm.$nextTick();
+ await nextTick();
expect(findLink().getAttribute('data-remote')).toBe(null);
expect(findLink().getAttribute('data-type')).toBe(null);
});
@@ -99,7 +99,7 @@ describe('Markdown component', () => {
])('%s', async ([testMd, mustContain]) => {
vm = buildMarkdownComponent([testMd], '/raw/');
- await vm.$nextTick();
+ await nextTick();
expect(vm.$el.innerHTML).toContain(mustContain);
});
@@ -110,29 +110,28 @@ describe('Markdown component', () => {
json = markdownTableJson;
});
- it('renders images and text', () => {
+ it('renders images and text', async () => {
vm = buildCellComponent(json.cells[0]);
- return vm.$nextTick().then(() => {
- const images = vm.$el.querySelectorAll('img');
- expect(images.length).toBe(5);
-
- const columns = vm.$el.querySelectorAll('td');
- expect(columns.length).toBe(6);
-
- expect(columns[0].textContent).toEqual('Hello ');
- expect(columns[1].textContent).toEqual('Test ');
- expect(columns[2].textContent).toEqual('World ');
- expect(columns[3].textContent).toEqual('Fake ');
- expect(columns[4].textContent).toEqual('External image: ');
- expect(columns[5].textContent).toEqual('Empty');
-
- expect(columns[0].innerHTML).toContain('<img src="data:image/jpeg;base64');
- expect(columns[1].innerHTML).toContain('<img src="data:image/png;base64');
- expect(columns[2].innerHTML).toContain('<img src="data:image/jpeg;base64');
- expect(columns[3].innerHTML).toContain('<img>');
- expect(columns[4].innerHTML).toContain('<img src="https://www.google.com/');
- });
+ await nextTick();
+ const images = vm.$el.querySelectorAll('img');
+ expect(images.length).toBe(5);
+
+ const columns = vm.$el.querySelectorAll('td');
+ expect(columns.length).toBe(6);
+
+ expect(columns[0].textContent).toEqual('Hello ');
+ expect(columns[1].textContent).toEqual('Test ');
+ expect(columns[2].textContent).toEqual('World ');
+ expect(columns[3].textContent).toEqual('Fake ');
+ expect(columns[4].textContent).toEqual('External image: ');
+ expect(columns[5].textContent).toEqual('Empty');
+
+ expect(columns[0].innerHTML).toContain('<img src="data:image/jpeg;base64');
+ expect(columns[1].innerHTML).toContain('<img src="data:image/png;base64');
+ expect(columns[2].innerHTML).toContain('<img src="data:image/jpeg;base64');
+ expect(columns[3].innerHTML).toContain('<img>');
+ expect(columns[4].innerHTML).toContain('<img src="https://www.google.com/');
});
});
@@ -144,28 +143,28 @@ describe('Markdown component', () => {
it('renders multi-line katex', async () => {
vm = buildCellComponent(json.cells[0]);
- await vm.$nextTick();
+ await nextTick();
expect(vm.$el.querySelector('.katex')).not.toBeNull();
});
it('renders inline katex', async () => {
vm = buildCellComponent(json.cells[1]);
- await vm.$nextTick();
+ await nextTick();
expect(vm.$el.querySelector('p:first-child .katex')).not.toBeNull();
});
it('renders multiple inline katex', async () => {
vm = buildCellComponent(json.cells[1]);
- await vm.$nextTick();
+ await nextTick();
expect(vm.$el.querySelectorAll('p:nth-child(2) .katex')).toHaveLength(4);
});
it('output cell in case of katex error', async () => {
vm = buildMarkdownComponent(['Some invalid $a & b$ inline formula $b & c$\n', '\n']);
- await vm.$nextTick();
+ await nextTick();
// expect one paragraph with no katex formula in it
expect(vm.$el.querySelectorAll('p')).toHaveLength(1);
expect(vm.$el.querySelectorAll('p .katex')).toHaveLength(0);
@@ -177,7 +176,7 @@ describe('Markdown component', () => {
'\n',
]);
- await vm.$nextTick();
+ await nextTick();
// expect one paragraph with no katex formula in it
expect(vm.$el.querySelectorAll('p')).toHaveLength(1);
expect(vm.$el.querySelectorAll('p .katex')).toHaveLength(1);
@@ -186,7 +185,7 @@ describe('Markdown component', () => {
it('renders math formula in list object', async () => {
vm = buildMarkdownComponent(["- list with inline $a=2$ inline formula $a' + b = c$\n", '\n']);
- await vm.$nextTick();
+ await nextTick();
// expect one list with a katex formula in it
expect(vm.$el.querySelectorAll('li')).toHaveLength(1);
expect(vm.$el.querySelectorAll('li .katex')).toHaveLength(2);
@@ -195,7 +194,7 @@ describe('Markdown component', () => {
it("renders math formula with tick ' in it", async () => {
vm = buildMarkdownComponent(["- list with inline $a=2$ inline formula $a' + b = c$\n", '\n']);
- await vm.$nextTick();
+ await nextTick();
// expect one list with a katex formula in it
expect(vm.$el.querySelectorAll('li')).toHaveLength(1);
expect(vm.$el.querySelectorAll('li .katex')).toHaveLength(2);
@@ -204,7 +203,7 @@ describe('Markdown component', () => {
it('renders math formula with less-than-operator < in it', async () => {
vm = buildMarkdownComponent(['- list with inline $a=2$ inline formula $a + b < c$\n', '\n']);
- await vm.$nextTick();
+ await nextTick();
// expect one list with a katex formula in it
expect(vm.$el.querySelectorAll('li')).toHaveLength(1);
expect(vm.$el.querySelectorAll('li .katex')).toHaveLength(2);
@@ -213,7 +212,7 @@ describe('Markdown component', () => {
it('renders math formula with greater-than-operator > in it', async () => {
vm = buildMarkdownComponent(['- list with inline $a=2$ inline formula $a + b > c$\n', '\n']);
- await vm.$nextTick();
+ await nextTick();
// expect one list with a katex formula in it
expect(vm.$el.querySelectorAll('li')).toHaveLength(1);
expect(vm.$el.querySelectorAll('li .katex')).toHaveLength(2);
diff --git a/spec/frontend/popovers/index_spec.js b/spec/frontend/popovers/index_spec.js
index ea3b78332d7..c82fe7b47d9 100644
--- a/spec/frontend/popovers/index_spec.js
+++ b/spec/frontend/popovers/index_spec.js
@@ -1,8 +1,7 @@
+import { nextTick } from 'vue';
import { initPopovers, dispose, destroy } from '~/popovers';
describe('popovers/index.js', () => {
- let popoversApp;
-
const createPopoverTarget = (trigger = 'hover') => {
const target = document.createElement('button');
const dataset = {
@@ -22,7 +21,7 @@ describe('popovers/index.js', () => {
};
const buildPopoversApp = () => {
- popoversApp = initPopovers('[data-toggle="popover"]');
+ initPopovers('[data-toggle="popover"]');
};
const triggerEvent = (target, eventName = 'mouseenter') => {
@@ -44,7 +43,7 @@ describe('popovers/index.js', () => {
triggerEvent(target);
- await popoversApp.$nextTick();
+ await nextTick();
const html = document.querySelector('.gl-popover').innerHTML;
expect(document.querySelector('.gl-popover')).not.toBe(null);
@@ -59,7 +58,7 @@ describe('popovers/index.js', () => {
buildPopoversApp();
triggerEvent(target, trigger);
- await popoversApp.$nextTick();
+ await nextTick();
expect(document.querySelector('.gl-popover')).not.toBe(null);
expect(document.querySelector('.gl-popover').innerHTML).toContain('default title');
@@ -73,7 +72,7 @@ describe('popovers/index.js', () => {
const trigger = 'click';
const target = createPopoverTarget(trigger);
triggerEvent(target, trigger);
- await popoversApp.$nextTick();
+ await nextTick();
expect(document.querySelector('.gl-popover')).not.toBe(null);
});
@@ -86,17 +85,17 @@ describe('popovers/index.js', () => {
buildPopoversApp();
triggerEvent(target);
triggerEvent(createPopoverTarget());
- await popoversApp.$nextTick();
+ await nextTick();
expect(document.querySelectorAll('.gl-popover')).toHaveLength(2);
dispose([fakeTarget]);
- await popoversApp.$nextTick();
+ await nextTick();
expect(document.querySelectorAll('.gl-popover')).toHaveLength(2);
dispose([target]);
- await popoversApp.$nextTick();
+ await nextTick();
expect(document.querySelectorAll('.gl-popover')).toHaveLength(1);
});
diff --git a/spec/frontend/prometheus_alerts/components/reset_key_spec.js b/spec/frontend/prometheus_alerts/components/reset_key_spec.js
index edf5297cc6a..dc5fdb1dffc 100644
--- a/spec/frontend/prometheus_alerts/components/reset_key_spec.js
+++ b/spec/frontend/prometheus_alerts/components/reset_key_spec.js
@@ -1,6 +1,7 @@
import { GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import ResetKey from '~/prometheus_alerts/components/reset_key.vue';
@@ -45,37 +46,31 @@ describe('ResetKey', () => {
expect(vm.find('.js-reset-auth-key').text()).toEqual('Reset key');
});
- it('reset updates key', () => {
+ it('reset updates key', async () => {
mock.onPost(propsData.changeKeyUrl).replyOnce(200, { token: 'newToken' });
vm.find(GlModal).vm.$emit('ok');
- return vm.vm
- .$nextTick()
- .then(waitForPromises)
- .then(() => {
- expect(vm.vm.authorizationKey).toEqual('newToken');
- expect(vm.find('#authorization-key').attributes('value')).toEqual('newToken');
- });
+ await nextTick();
+ await waitForPromises();
+ expect(vm.vm.authorizationKey).toEqual('newToken');
+ expect(vm.find('#authorization-key').attributes('value')).toEqual('newToken');
});
- it('reset key failure shows error', () => {
+ it('reset key failure shows error', async () => {
mock.onPost(propsData.changeKeyUrl).replyOnce(500);
vm.find(GlModal).vm.$emit('ok');
- return vm.vm
- .$nextTick()
- .then(waitForPromises)
- .then(() => {
- expect(vm.find('#authorization-key').attributes('value')).toEqual(
- propsData.initialAuthorizationKey,
- );
-
- expect(document.querySelector('.flash-container').innerText.trim()).toEqual(
- 'Failed to reset key. Please try again.',
- );
- });
+ await nextTick();
+ await waitForPromises();
+ expect(vm.find('#authorization-key').attributes('value')).toEqual(
+ propsData.initialAuthorizationKey,
+ );
+
+ expect(document.querySelector('.flash-container').innerText.trim()).toEqual(
+ 'Failed to reset key. Please try again.',
+ );
});
});
@@ -92,14 +87,13 @@ describe('ResetKey', () => {
expect(vm.find('#authorization-key').attributes('value')).toEqual('');
});
- it('Generate key button triggers key change', () => {
+ it('Generate key button triggers key change', async () => {
mock.onPost(propsData.changeKeyUrl).replyOnce(200, { token: 'newToken' });
vm.find('.js-reset-auth-key').vm.$emit('click');
- return waitForPromises().then(() => {
- expect(vm.find('#authorization-key').attributes('value')).toEqual('newToken');
- });
+ await waitForPromises();
+ expect(vm.find('#authorization-key').attributes('value')).toEqual('newToken');
});
});
});
diff --git a/spec/frontend/repository/components/last_commit_spec.js b/spec/frontend/repository/components/last_commit_spec.js
index fe05a981845..bb710c3a96c 100644
--- a/spec/frontend/repository/components/last_commit_spec.js
+++ b/spec/frontend/repository/components/last_commit_spec.js
@@ -1,5 +1,6 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import LastCommit from '~/repository/components/last_commit.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
@@ -63,7 +64,7 @@ describe('Repository last commit component', () => {
`('$label when loading icon $loading is true', async ({ loading }) => {
factory(createCommitData(), loading);
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.find(GlLoadingIcon).exists()).toBe(loading);
});
@@ -71,7 +72,7 @@ describe('Repository last commit component', () => {
it('renders commit widget', async () => {
factory();
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.element).toMatchSnapshot();
});
@@ -79,7 +80,7 @@ describe('Repository last commit component', () => {
it('renders short commit ID', async () => {
factory();
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.find('[data-testid="last-commit-id-label"]').text()).toEqual('12345678');
});
@@ -87,7 +88,7 @@ describe('Repository last commit component', () => {
it('hides pipeline components when pipeline does not exist', async () => {
factory(createCommitData({ pipeline: null }));
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.find('.js-commit-pipeline').exists()).toBe(false);
});
@@ -95,7 +96,7 @@ describe('Repository last commit component', () => {
it('renders pipeline components', async () => {
factory();
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.find('.js-commit-pipeline').exists()).toBe(true);
});
@@ -103,7 +104,7 @@ describe('Repository last commit component', () => {
it('hides author component when author does not exist', async () => {
factory(createCommitData({ author: null }));
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.find('.js-user-link').exists()).toBe(false);
expect(vm.find(UserAvatarLink).exists()).toBe(false);
@@ -112,7 +113,7 @@ describe('Repository last commit component', () => {
it('does not render description expander when description is null', async () => {
factory(createCommitData({ descriptionHtml: null }));
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.find('.text-expander').exists()).toBe(false);
expect(vm.find('.commit-row-description').exists()).toBe(false);
@@ -121,11 +122,11 @@ describe('Repository last commit component', () => {
it('expands commit description when clicking expander', async () => {
factory(createCommitData({ descriptionHtml: 'Test description' }));
- await vm.vm.$nextTick();
+ await nextTick();
vm.find('.text-expander').vm.$emit('click');
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.find('.commit-row-description').isVisible()).toBe(true);
expect(vm.find('.text-expander').classes('open')).toBe(true);
@@ -134,7 +135,7 @@ describe('Repository last commit component', () => {
it('strips the first newline of the description', async () => {
factory(createCommitData({ descriptionHtml: '&#x000A;Update ADOPTERS.md' }));
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.find('.commit-row-description').html()).toBe(
'<pre class="commit-row-description gl-mb-3">Update ADOPTERS.md</pre>',
@@ -144,7 +145,7 @@ describe('Repository last commit component', () => {
it('renders the signature HTML as returned by the backend', async () => {
factory(createCommitData({ signatureHtml: '<button>Verified</button>' }));
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.element).toMatchSnapshot();
});
@@ -152,7 +153,7 @@ describe('Repository last commit component', () => {
it('sets correct CSS class if the commit message is empty', async () => {
factory(createCommitData({ message: '' }));
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.find('.item-title').classes()).toContain(emptyMessageClass);
});
diff --git a/spec/frontend/repository/components/preview/index_spec.js b/spec/frontend/repository/components/preview/index_spec.js
index 2490258a048..0d9bfc62ed5 100644
--- a/spec/frontend/repository/components/preview/index_spec.js
+++ b/spec/frontend/repository/components/preview/index_spec.js
@@ -1,5 +1,6 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { handleLocationHash } from '~/lib/utils/common_utils';
import Preview from '~/repository/components/preview/index.vue';
@@ -28,7 +29,7 @@ describe('Repository file preview component', () => {
vm.destroy();
});
- it('renders file HTML', () => {
+ it('renders file HTML', async () => {
factory({
webPath: 'http://test.com',
name: 'README.md',
@@ -38,12 +39,11 @@ describe('Repository file preview component', () => {
// eslint-disable-next-line no-restricted-syntax
vm.setData({ readme: { html: '<div class="blob">test</div>' } });
- return vm.vm.$nextTick(() => {
- expect(vm.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(vm.element).toMatchSnapshot();
});
- it('handles hash after render', () => {
+ it('handles hash after render', async () => {
factory({
webPath: 'http://test.com',
name: 'README.md',
@@ -53,15 +53,11 @@ describe('Repository file preview component', () => {
// eslint-disable-next-line no-restricted-syntax
vm.setData({ readme: { html: '<div class="blob">test</div>' } });
- return vm.vm
- .$nextTick()
- .then(vm.vm.$nextTick())
- .then(() => {
- expect(handleLocationHash).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(handleLocationHash).toHaveBeenCalled();
});
- it('renders loading icon', () => {
+ it('renders loading icon', async () => {
factory({
webPath: 'http://test.com',
name: 'README.md',
@@ -71,8 +67,7 @@ describe('Repository file preview component', () => {
// eslint-disable-next-line no-restricted-syntax
vm.setData({ loading: 1 });
- return vm.vm.$nextTick(() => {
- expect(vm.find(GlLoadingIcon).exists()).toBe(true);
- });
+ await nextTick();
+ expect(vm.find(GlLoadingIcon).exists()).toBe(true);
});
});
diff --git a/spec/frontend/repository/components/table/index_spec.js b/spec/frontend/repository/components/table/index_spec.js
index 2cd88944f81..07c151ad935 100644
--- a/spec/frontend/repository/components/table/index_spec.js
+++ b/spec/frontend/repository/components/table/index_spec.js
@@ -1,5 +1,6 @@
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import Table from '~/repository/components/table/index.vue';
import TableRow from '~/repository/components/table/row.vue';
@@ -86,18 +87,17 @@ describe('Repository table component', () => {
${'/'} | ${'main'}
${'app/assets'} | ${'main'}
${'/'} | ${'test'}
- `('renders table caption for $ref in $path', ({ path, ref }) => {
+ `('renders table caption for $ref in $path', async ({ path, ref }) => {
factory({ path });
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
vm.setData({ ref });
- return vm.vm.$nextTick(() => {
- expect(vm.find('.table').attributes('aria-label')).toEqual(
- `Files, directories, and submodules in the path ${path} for commit reference ${ref}`,
- );
- });
+ await nextTick();
+ expect(vm.find('.table').attributes('aria-label')).toEqual(
+ `Files, directories, and submodules in the path ${path} for commit reference ${ref}`,
+ );
});
it('shows loading icon', () => {
@@ -140,7 +140,7 @@ describe('Repository table component', () => {
showMoreButton().vm.$emit('click');
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.emitted('showMore')).toHaveLength(1);
});
diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js
index 440baa72a3c..22570b2d6ed 100644
--- a/spec/frontend/repository/components/table/row_spec.js
+++ b/spec/frontend/repository/components/table/row_spec.js
@@ -1,5 +1,6 @@
import { GlBadge, GlLink, GlIcon, GlIntersectionObserver } from '@gitlab/ui';
import { shallowMount, RouterLinkStub } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import TableRow from '~/repository/components/table/row.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
@@ -53,7 +54,7 @@ describe('Repository table row component', () => {
vm.destroy();
});
- it('renders table row', () => {
+ it('renders table row', async () => {
factory({
id: '1',
sha: '123',
@@ -62,12 +63,11 @@ describe('Repository table row component', () => {
currentPath: '/',
});
- return vm.vm.$nextTick().then(() => {
- expect(vm.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(vm.element).toMatchSnapshot();
});
- it('renders a symlink table row', () => {
+ it('renders a symlink table row', async () => {
factory({
id: '1',
sha: '123',
@@ -77,12 +77,11 @@ describe('Repository table row component', () => {
mode: FILE_SYMLINK_MODE,
});
- return vm.vm.$nextTick().then(() => {
- expect(vm.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(vm.element).toMatchSnapshot();
});
- it('renders table row for path with special character', () => {
+ it('renders table row for path with special character', async () => {
factory({
id: '1',
sha: '123',
@@ -91,9 +90,8 @@ describe('Repository table row component', () => {
currentPath: 'test$',
});
- return vm.vm.$nextTick().then(() => {
- expect(vm.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(vm.element).toMatchSnapshot();
});
it('renders a gl-hover-load directive', () => {
@@ -116,7 +114,7 @@ describe('Repository table row component', () => {
${'tree'} | ${RouterLinkStub} | ${'RouterLink'}
${'blob'} | ${RouterLinkStub} | ${'RouterLink'}
${'commit'} | ${'a'} | ${'hyperlink'}
- `('renders a $componentName for type $type', ({ type, component }) => {
+ `('renders a $componentName for type $type', async ({ type, component }) => {
factory({
id: '1',
sha: '123',
@@ -125,16 +123,15 @@ describe('Repository table row component', () => {
currentPath: '/',
});
- return vm.vm.$nextTick().then(() => {
- expect(vm.find(component).exists()).toBe(true);
- });
+ await nextTick();
+ expect(vm.find(component).exists()).toBe(true);
});
it.each`
path
${'test#'}
${'Änderungen'}
- `('renders link for $path', ({ path }) => {
+ `('renders link for $path', async ({ path }) => {
factory({
id: '1',
sha: '123',
@@ -143,14 +140,13 @@ describe('Repository table row component', () => {
currentPath: '/',
});
- return vm.vm.$nextTick().then(() => {
- expect(vm.find({ ref: 'link' }).props('to')).toEqual({
- path: `/-/tree/main/${encodeURIComponent(path)}`,
- });
+ await nextTick();
+ expect(vm.find({ ref: 'link' }).props('to')).toEqual({
+ path: `/-/tree/main/${encodeURIComponent(path)}`,
});
});
- it('renders link for directory with hash', () => {
+ it('renders link for directory with hash', async () => {
factory({
id: '1',
sha: '123',
@@ -159,12 +155,11 @@ describe('Repository table row component', () => {
currentPath: '/',
});
- return vm.vm.$nextTick().then(() => {
- expect(vm.find('.tree-item-link').props('to')).toEqual({ path: '/-/tree/main/test%23' });
- });
+ await nextTick();
+ expect(vm.find('.tree-item-link').props('to')).toEqual({ path: '/-/tree/main/test%23' });
});
- it('renders commit ID for submodule', () => {
+ it('renders commit ID for submodule', async () => {
factory({
id: '1',
sha: '123',
@@ -173,12 +168,11 @@ describe('Repository table row component', () => {
currentPath: '/',
});
- return vm.vm.$nextTick().then(() => {
- expect(vm.find('.commit-sha').text()).toContain('1');
- });
+ await nextTick();
+ expect(vm.find('.commit-sha').text()).toContain('1');
});
- it('renders link with href', () => {
+ it('renders link with href', async () => {
factory({
id: '1',
sha: '123',
@@ -188,12 +182,11 @@ describe('Repository table row component', () => {
currentPath: '/',
});
- return vm.vm.$nextTick().then(() => {
- expect(vm.find('a').attributes('href')).toEqual('https://test.com');
- });
+ await nextTick();
+ expect(vm.find('a').attributes('href')).toEqual('https://test.com');
});
- it('renders LFS badge', () => {
+ it('renders LFS badge', async () => {
factory({
id: '1',
sha: '123',
@@ -203,12 +196,11 @@ describe('Repository table row component', () => {
lfsOid: '1',
});
- return vm.vm.$nextTick().then(() => {
- expect(vm.find(GlBadge).exists()).toBe(true);
- });
+ await nextTick();
+ expect(vm.find(GlBadge).exists()).toBe(true);
});
- it('renders commit and web links with href for submodule', () => {
+ it('renders commit and web links with href for submodule', async () => {
factory({
id: '1',
sha: '123',
@@ -219,13 +211,12 @@ describe('Repository table row component', () => {
currentPath: '/',
});
- return vm.vm.$nextTick().then(() => {
- expect(vm.find('a').attributes('href')).toEqual('https://test.com');
- expect(vm.find(GlLink).attributes('href')).toEqual('https://test.com/commit');
- });
+ await nextTick();
+ expect(vm.find('a').attributes('href')).toEqual('https://test.com');
+ expect(vm.find(GlLink).attributes('href')).toEqual('https://test.com/commit');
});
- it('renders lock icon', () => {
+ it('renders lock icon', async () => {
factory({
id: '1',
sha: '123',
@@ -234,10 +225,9 @@ describe('Repository table row component', () => {
currentPath: '/',
});
- return vm.vm.$nextTick().then(() => {
- expect(vm.find(GlIcon).exists()).toBe(true);
- expect(vm.find(GlIcon).props('name')).toBe('lock');
- });
+ await nextTick();
+ expect(vm.find(GlIcon).exists()).toBe(true);
+ expect(vm.find(GlIcon).props('name')).toBe('lock');
});
it('renders loading icon when path is loading', () => {
diff --git a/spec/frontend/repository/components/tree_content_spec.js b/spec/frontend/repository/components/tree_content_spec.js
index 00ad1fc05f6..9d3a5394df8 100644
--- a/spec/frontend/repository/components/tree_content_spec.js
+++ b/spec/frontend/repository/components/tree_content_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import paginatedTreeQuery from 'shared_queries/repository/paginated_tree.query.graphql';
import FilePreview from '~/repository/components/preview/index.vue';
import FileTable from '~/repository/components/table/index.vue';
@@ -50,7 +51,7 @@ describe('Repository table component', () => {
// eslint-disable-next-line no-restricted-syntax
vm.setData({ entries: { blobs: [{ name: 'README.md' }] } });
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.find(FilePreview).exists()).toBe(true);
});
@@ -60,7 +61,7 @@ describe('Repository table component', () => {
jest.spyOn(vm.vm, 'fetchFiles').mockImplementation(() => {});
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.vm.fetchFiles).toHaveBeenCalled();
expect(resetRequestedCommits).toHaveBeenCalled();
@@ -111,7 +112,7 @@ describe('Repository table component', () => {
it('is changes hasShowMore to false when "showMore" event is emitted', async () => {
findFileTable().vm.$emit('showMore');
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.vm.hasShowMore).toBe(false);
});
@@ -119,7 +120,7 @@ describe('Repository table component', () => {
it('changes clickedShowMore when "showMore" event is emitted', async () => {
findFileTable().vm.$emit('showMore');
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.vm.clickedShowMore).toBe(true);
});
@@ -140,7 +141,7 @@ describe('Repository table component', () => {
// eslint-disable-next-line no-restricted-syntax
vm.setData({ fetchCounter: 5, clickedShowMore: false });
- await vm.vm.$nextTick();
+ await nextTick();
expect(vm.vm.hasShowMore).toBe(false);
});
@@ -161,7 +162,7 @@ describe('Repository table component', () => {
// eslint-disable-next-line no-restricted-syntax
vm.setData({ entries: { blobs }, pagesLoaded });
- await vm.vm.$nextTick();
+ await nextTick();
expect(findFileTable().props('hasMore')).toBe(limitReached);
});
diff --git a/spec/frontend/serverless/components/functions_spec.js b/spec/frontend/serverless/components/functions_spec.js
index 1e33e56c2f3..6331f897a87 100644
--- a/spec/frontend/serverless/components/functions_spec.js
+++ b/spec/frontend/serverless/components/functions_spec.js
@@ -1,6 +1,6 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import AxiosMockAdapter from 'axios-mock-adapter';
import Vuex from 'vuex';
import { TEST_HOST } from 'helpers/test_constants';
@@ -73,15 +73,14 @@ describe('functionsComponent', () => {
expect(component.find('.js-functions-loader').exists()).toBe(true);
});
- it('should render the functions list', () => {
+ it('should render the functions list', async () => {
store = createStore({ clustersPath: 'clustersPath', helpPath: 'helpPath', statusPath });
component = shallowMount(functionsComponent, { store });
- component.vm.$store.dispatch('receiveFunctionsSuccess', mockServerlessFunctions);
+ await component.vm.$store.dispatch('receiveFunctionsSuccess', mockServerlessFunctions);
- return component.vm.$nextTick().then(() => {
- expect(component.find(EnvironmentRow).exists()).toBe(true);
- });
+ await nextTick();
+ expect(component.find(EnvironmentRow).exists()).toBe(true);
});
});
diff --git a/spec/frontend/snippets/components/snippet_blob_view_spec.js b/spec/frontend/snippets/components/snippet_blob_view_spec.js
index 172089f9ee6..c395112e313 100644
--- a/spec/frontend/snippets/components/snippet_blob_view_spec.js
+++ b/spec/frontend/snippets/components/snippet_blob_view_spec.js
@@ -86,21 +86,17 @@ describe('Blob Embeddable', () => {
expect(wrapper.find(RichViewer).exists()).toBe(true);
});
- it('correctly switches viewer type', () => {
+ it('correctly switches viewer type', async () => {
createComponent();
expect(wrapper.find(SimpleViewer).exists()).toBe(true);
wrapper.vm.switchViewer(RichViewerMock.type);
- return wrapper.vm
- .$nextTick()
- .then(() => {
- expect(wrapper.find(RichViewer).exists()).toBe(true);
- wrapper.vm.switchViewer(SimpleViewerMock.type);
- })
- .then(() => {
- expect(wrapper.find(SimpleViewer).exists()).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find(RichViewer).exists()).toBe(true);
+ await wrapper.vm.switchViewer(SimpleViewerMock.type);
+
+ expect(wrapper.find(SimpleViewer).exists()).toBe(true);
});
it('passes information about render error down to blob header', () => {
@@ -191,22 +187,18 @@ describe('Blob Embeddable', () => {
});
describe('switchViewer()', () => {
- it('switches to the passed viewer', () => {
+ it('switches to the passed viewer', async () => {
createComponent();
wrapper.vm.switchViewer(RichViewerMock.type);
- return wrapper.vm
- .$nextTick()
- .then(() => {
- expect(wrapper.vm.activeViewerType).toBe(RichViewerMock.type);
- expect(wrapper.find(RichViewer).exists()).toBe(true);
-
- wrapper.vm.switchViewer(SimpleViewerMock.type);
- })
- .then(() => {
- expect(wrapper.vm.activeViewerType).toBe(SimpleViewerMock.type);
- expect(wrapper.find(SimpleViewer).exists()).toBe(true);
- });
+
+ await nextTick();
+ expect(wrapper.vm.activeViewerType).toBe(RichViewerMock.type);
+ expect(wrapper.find(RichViewer).exists()).toBe(true);
+
+ await wrapper.vm.switchViewer(SimpleViewerMock.type);
+ expect(wrapper.vm.activeViewerType).toBe(SimpleViewerMock.type);
+ expect(wrapper.find(SimpleViewer).exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/tooltips/index_spec.js b/spec/frontend/tooltips/index_spec.js
index 9c03ca8f4c9..198a0315ef7 100644
--- a/spec/frontend/tooltips/index_spec.js
+++ b/spec/frontend/tooltips/index_spec.js
@@ -1,3 +1,4 @@
+import { nextTick } from 'vue';
import {
add,
initTooltips,
@@ -57,7 +58,7 @@ describe('tooltips/index.js', () => {
triggerEvent(target);
- await tooltipsApp.$nextTick();
+ await nextTick();
expect(document.querySelector('.gl-tooltip')).not.toBe(null);
expect(document.querySelector('.gl-tooltip').innerHTML).toContain('default title');
@@ -69,7 +70,7 @@ describe('tooltips/index.js', () => {
buildTooltipsApp();
triggerEvent(target, 'click');
- await tooltipsApp.$nextTick();
+ await nextTick();
expect(document.querySelector('.gl-tooltip')).not.toBe(null);
expect(document.querySelector('.gl-tooltip').innerHTML).toContain('default title');
@@ -83,7 +84,7 @@ describe('tooltips/index.js', () => {
buildTooltipsApp();
add([target], { title: 'custom title' });
- await tooltipsApp.$nextTick();
+ await nextTick();
expect(document.querySelector('.gl-tooltip')).not.toBe(null);
expect(document.querySelector('.gl-tooltip').innerHTML).toContain('custom title');
@@ -97,13 +98,13 @@ describe('tooltips/index.js', () => {
buildTooltipsApp();
triggerEvent(target);
- await tooltipsApp.$nextTick();
+ await nextTick();
expect(document.querySelector('.gl-tooltip')).not.toBe(null);
dispose([target]);
- await tooltipsApp.$nextTick();
+ await nextTick();
expect(document.querySelector('.gl-tooltip')).toBe(null);
});
@@ -122,7 +123,7 @@ describe('tooltips/index.js', () => {
buildTooltipsApp();
- await tooltipsApp.$nextTick();
+ await nextTick();
jest.spyOn(tooltipsApp, 'triggerEvent');
@@ -137,7 +138,7 @@ describe('tooltips/index.js', () => {
buildTooltipsApp();
- await tooltipsApp.$nextTick();
+ await nextTick();
jest.spyOn(tooltipsApp, 'fixTitle');
diff --git a/spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js b/spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js
index 1aeb080aa04..82526af7afa 100644
--- a/spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_collapsible_extension_spec.js
@@ -1,5 +1,6 @@
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import MrCollapsibleSection from '~/vue_merge_request_widget/components/mr_collapsible_extension.vue';
describe('Merge Request Collapsible Extension', () => {
@@ -46,9 +47,9 @@ describe('Merge Request Collapsible Extension', () => {
});
describe('onClick', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.find('button').trigger('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('rendes the provided slot', () => {
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js
index e7c10ab4c2d..8a42e2e2ce7 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_author_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import MrWidgetAuthor from '~/vue_merge_request_widget/components/mr_widget_author.vue';
window.gl = window.gl || {};
@@ -50,7 +51,7 @@ describe('MrWidgetAuthor', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('img').attributes('src')).toBe('no_avatar.png');
});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js
index 3e111cd308a..631aef412a6 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_expandable_section_spec.js
@@ -1,5 +1,6 @@
import { GlButton, GlCollapse, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import MrCollapsibleSection from '~/vue_merge_request_widget/components/mr_widget_expandable_section.vue';
describe('MrWidgetExpanableSection', () => {
@@ -43,9 +44,9 @@ describe('MrWidgetExpanableSection', () => {
});
describe('when collapse section is open', () => {
- beforeEach(() => {
+ beforeEach(async () => {
findButton().vm.$emit('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders button with collapse text', () => {
diff --git a/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js
index c30f6f1dfd1..c0add94e6ed 100644
--- a/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue';
const testCommitMessage = 'Test commit message';
@@ -46,16 +47,15 @@ describe('Commits edit component', () => {
expect(findTextarea().element.value).toBe(testCommitMessage);
});
- it('emits an input event and receives changed value', () => {
+ it('emits an input event and receives changed value', async () => {
const changedCommitMessage = 'Changed commit message';
findTextarea().element.value = changedCommitMessage;
findTextarea().trigger('input');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().input[0]).toEqual([changedCommitMessage]);
- expect(findTextarea().element.value).toBe(changedCommitMessage);
- });
+ await nextTick();
+ expect(wrapper.emitted().input[0]).toEqual([changedCommitMessage]);
+ expect(findTextarea().element.value).toBe(changedCommitMessage);
});
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js
index 4c763f40cbe..663fabb761c 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js
@@ -1,5 +1,6 @@
import { GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue';
const commits = [
@@ -51,11 +52,10 @@ describe('Commits message dropdown component', () => {
expect(findFirstDropdownElement().text()).toContain('Commit 1');
});
- it('should emit a commit title on selecting commit', () => {
+ it('should emit a commit title on selecting commit', async () => {
findFirstDropdownElement().vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().input[0]).toEqual(['Update test.txt']);
- });
+ await nextTick();
+ expect(wrapper.emitted().input[0]).toEqual(['Update test.txt']);
});
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js
index 4d05e732f48..2796403b7d0 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js
@@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
+import { nextTick } from 'vue';
import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue';
describe('Commits header component', () => {
@@ -58,15 +59,14 @@ describe('Commits header component', () => {
expect(findCommitToggle().attributes('aria-label')).toBe('Expand');
});
- it('has a chevron-right icon', () => {
+ it('has a chevron-right icon', async () => {
createComponent();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ expanded: false });
- return wrapper.vm.$nextTick().then(() => {
- expect(findCommitToggle().props('icon')).toBe('chevron-right');
- });
+ await nextTick();
+ expect(findCommitToggle().props('icon')).toBe('chevron-right');
});
describe('when squash is disabled', () => {
@@ -118,25 +118,19 @@ describe('Commits header component', () => {
wrapper.setData({ expanded: true });
});
- it('toggle has aria-label equal to collapse', (done) => {
- wrapper.vm.$nextTick(() => {
- expect(findCommitToggle().attributes('aria-label')).toBe('Collapse');
- done();
- });
+ it('toggle has aria-label equal to collapse', async () => {
+ await nextTick();
+ expect(findCommitToggle().attributes('aria-label')).toBe('Collapse');
});
- it('has a chevron-down icon', (done) => {
- wrapper.vm.$nextTick(() => {
- expect(findCommitToggle().props('icon')).toBe('chevron-down');
- done();
- });
+ it('has a chevron-down icon', async () => {
+ await nextTick();
+ expect(findCommitToggle().props('icon')).toBe('chevron-down');
});
- it('has a collapse text', (done) => {
- wrapper.vm.$nextTick(() => {
- expect(findHeaderWrapper().text()).toBe('Collapse');
- done();
- });
+ it('has a collapse text', async () => {
+ await nextTick();
+ expect(findHeaderWrapper().text()).toBe('Collapse');
});
});
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
index ec222e66a97..9dcde3e4f33 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import { removeBreakLine } from 'helpers/text_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
@@ -20,7 +21,7 @@ describe('MRWidgetConflicts', () => {
const resolveConflictsBtnText = 'Resolve conflicts';
const mergeLocallyBtnText = 'Merge locally';
- function createComponent(propsData = {}) {
+ async function createComponent(propsData = {}) {
wrapper = extendedWrapper(
shallowMount(ConflictsComponent, {
propsData,
@@ -55,7 +56,7 @@ describe('MRWidgetConflicts', () => {
});
}
- return wrapper.vm.$nextTick();
+ await nextTick();
}
afterEach(() => {
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js
index ec5ec989d0f..ddce07954ab 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js
@@ -1,9 +1,10 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import MissingBranchComponent from '~/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue';
let wrapper;
-function factory(sourceBranchRemoved, mergeRequestWidgetGraphql) {
+async function factory(sourceBranchRemoved, mergeRequestWidgetGraphql) {
wrapper = shallowMount(MissingBranchComponent, {
propsData: {
mr: { sourceBranchRemoved },
@@ -19,7 +20,7 @@ function factory(sourceBranchRemoved, mergeRequestWidgetGraphql) {
wrapper.setData({ state: { sourceBranchExists: !sourceBranchRemoved } });
}
- return wrapper.vm.$nextTick();
+ await nextTick();
}
describe('MRWidgetMissingBranch', () => {
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 b4ef40ccd72..8b4a0b4c28e 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
@@ -710,7 +710,7 @@ describe('ReadyToMerge', () => {
commitsWithoutMergeCommits: {},
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findCommitEditElements().length).toBe(2);
});
diff --git a/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js b/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js
index 9048975875a..b7c22b403aa 100644
--- a/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js
+++ b/spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js
@@ -1,6 +1,7 @@
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import axios from '~/lib/utils/axios_utils';
import Poll from '~/lib/utils/poll';
import MrWidgetExpanableSection from '~/vue_merge_request_widget/components/mr_widget_expandable_section.vue';
@@ -39,15 +40,14 @@ describe('MrWidgetTerraformConainer', () => {
});
describe('when data is loading', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mockPollingApi(200, plans, {});
- return mountWrapper().then(() => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ loading: true });
- return wrapper.vm.$nextTick();
- });
+ await mountWrapper();
+ // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
+ // eslint-disable-next-line no-restricted-syntax
+ wrapper.setData({ loading: true });
+ await nextTick();
});
it('diplays loading skeleton', () => {
diff --git a/spec/frontend/vue_shared/alert_details/alert_details_spec.js b/spec/frontend/vue_shared/alert_details/alert_details_spec.js
index 221beed744b..7ee6e29e6de 100644
--- a/spec/frontend/vue_shared/alert_details/alert_details_spec.js
+++ b/spec/frontend/vue_shared/alert_details/alert_details_spec.js
@@ -2,6 +2,7 @@ import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { joinPaths } from '~/lib/utils/url_utility';
@@ -216,17 +217,16 @@ describe('AlertDetails', () => {
expect(findCreateIncidentBtn().exists()).toBe(false);
});
- it('should display "Create incident" button when incident doesn\'t exist yet', () => {
+ it('should display "Create incident" button when incident doesn\'t exist yet', async () => {
const issue = null;
mountComponent({
mountMethod: mount,
data: { alert: { ...mockAlert, issue } },
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findViewIncidentBtn().exists()).toBe(false);
- expect(findCreateIncidentBtn().exists()).toBe(true);
- });
+ await nextTick();
+ expect(findViewIncidentBtn().exists()).toBe(false);
+ expect(findCreateIncidentBtn().exists()).toBe(true);
});
it('calls `$apollo.mutate` with `createIssueQuery`', () => {
diff --git a/spec/frontend/vue_shared/alert_details/alert_management_sidebar_todo_spec.js b/spec/frontend/vue_shared/alert_details/alert_management_sidebar_todo_spec.js
index 87ad5e36564..12c5c190e26 100644
--- a/spec/frontend/vue_shared/alert_details/alert_management_sidebar_todo_spec.js
+++ b/spec/frontend/vue_shared/alert_details/alert_management_sidebar_todo_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import todoMarkDoneMutation from '~/graphql_shared/mutations/todo_mark_done.mutation.graphql';
import SidebarTodo from '~/vue_shared/alert_details/components/sidebar/sidebar_todo.vue';
import createAlertTodoMutation from '~/vue_shared/alert_details/graphql/mutations/alert_todo_create.mutation.graphql';
@@ -57,7 +58,7 @@ describe('Alert Details Sidebar To Do', () => {
});
it('renders a button for adding a To-Do', async () => {
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findToDoButton().text()).toBe('Add a to do');
});
@@ -66,7 +67,7 @@ describe('Alert Details Sidebar To Do', () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
findToDoButton().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: createAlertTodoMutation,
@@ -88,7 +89,7 @@ describe('Alert Details Sidebar To Do', () => {
});
it('renders a Mark As Done button when todo is present', async () => {
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findToDoButton().text()).toBe('Mark as done');
});
@@ -97,7 +98,7 @@ describe('Alert Details Sidebar To Do', () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult);
findToDoButton().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: todoMarkDoneMutation,
diff --git a/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js b/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js
index b5a61a4adc1..1216681038f 100644
--- a/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js
+++ b/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js
@@ -1,6 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue';
import AlertMetrics from '~/vue_shared/alert_details/components/alert_metrics.vue';
@@ -53,7 +54,7 @@ describe('Alert Metrics', () => {
mountComponent({ props: { dashboardUrl: 'metrics.url' } });
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findEmptyState().exists()).toBe(false);
expect(findChart().exists()).toBe(true);
diff --git a/spec/frontend/vue_shared/alert_details/alert_status_spec.js b/spec/frontend/vue_shared/alert_details/alert_status_spec.js
index 3fc13243bce..369e9ac18a4 100644
--- a/spec/frontend/vue_shared/alert_details/alert_status_spec.js
+++ b/spec/frontend/vue_shared/alert_details/alert_status_spec.js
@@ -1,4 +1,5 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import updateAlertStatusMutation from '~/graphql_shared//mutations/alert_status_update.mutation.graphql';
@@ -121,7 +122,7 @@ describe('AlertManagementStatus', () => {
it('emits an error when triggered a second time', async () => {
await selectFirstStatusOption();
- await wrapper.vm.$nextTick();
+ await nextTick();
await selectFirstStatusOption();
// Should emit two errors [0,1]
expect(wrapper.emitted('alert-error').length > 1).toBe(true);
diff --git a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_assignees_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_assignees_spec.js
index 29e0eee2c9a..29569734621 100644
--- a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_assignees_spec.js
+++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_assignees_spec.js
@@ -1,6 +1,7 @@
import { GlDropdownItem } from '@gitlab/ui';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import SidebarAssignee from '~/vue_shared/alert_details/components/sidebar/sidebar_assignee.vue';
import SidebarAssignees from '~/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue';
@@ -112,7 +113,7 @@ describe('Alert Details Sidebar Assignees', () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ isDropdownSearching: false });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findDropdown().text()).toBe('Unassigned');
});
@@ -126,7 +127,7 @@ describe('Alert Details Sidebar Assignees', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ isDropdownSearching: false });
- await wrapper.vm.$nextTick();
+ await nextTick();
wrapper.find(SidebarAssignee).vm.$emit('update-alert-assignees', 'root');
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
@@ -139,7 +140,7 @@ describe('Alert Details Sidebar Assignees', () => {
});
});
- it('emits an error when request contains error messages', () => {
+ it('emits an error when request contains error messages', async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ isDropdownSearching: false });
@@ -153,15 +154,11 @@ describe('Alert Details Sidebar Assignees', () => {
};
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(errorMutationResult);
- return wrapper.vm
- .$nextTick()
- .then(() => {
- const SideBarAssigneeItem = wrapper.findAll(SidebarAssignee).at(0);
- SideBarAssigneeItem.vm.$emit('update-alert-assignees');
- })
- .then(() => {
- expect(wrapper.emitted('alert-error')).toBeDefined();
- });
+
+ await nextTick();
+ const SideBarAssigneeItem = wrapper.findAll(SidebarAssignee).at(0);
+ await SideBarAssigneeItem.vm.$emit('update-alert-assignees');
+ expect(wrapper.emitted('alert-error')).toBeDefined();
});
it('stops updating and cancels loading when the request fails', () => {
diff --git a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js
index b00a20dab1a..a3adbcf8d3a 100644
--- a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js
+++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js
@@ -1,4 +1,5 @@
import { GlDropdown, GlLoadingIcon } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import AlertStatus from '~/vue_shared/alert_details/components/alert_status.vue';
import AlertSidebarStatus from '~/vue_shared/alert_details/components/sidebar/sidebar_status.vue';
@@ -75,7 +76,7 @@ describe('Alert Details Sidebar Status', () => {
loading: false,
});
findAlertStatus().vm.$emit('handle-updating', true);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findStatusLoadingIcon().exists()).toBe(true);
});
diff --git a/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js b/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js
index 3277aab43f0..1ff98d4aec5 100644
--- a/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { HIGHLIGHT_CLASS_NAME } from '~/vue_shared/components/blob_viewers/constants';
import SimpleViewer from '~/vue_shared/components/blob_viewers/simple_viewer.vue';
@@ -69,7 +70,7 @@ describe('Blob Simple Viewer component', () => {
expect(linetoBeHighlighted.classes()).toContain(HIGHLIGHT_CLASS_NAME);
});
- it('switches highlighting when another line is selected', () => {
+ it('switches highlighting when another line is selected', async () => {
const currentlyHighlighted = wrapper.find('#LC2');
const hash = '#LC3';
const linetoBeHighlighted = wrapper.find(hash);
@@ -78,11 +79,10 @@ describe('Blob Simple Viewer component', () => {
wrapper.vm.scrollToLine(hash);
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.highlightedLine).toBe(linetoBeHighlighted.element);
- expect(currentlyHighlighted.classes()).not.toContain(HIGHLIGHT_CLASS_NAME);
- expect(linetoBeHighlighted.classes()).toContain(HIGHLIGHT_CLASS_NAME);
- });
+ await nextTick();
+ expect(wrapper.vm.highlightedLine).toBe(linetoBeHighlighted.element);
+ expect(currentlyHighlighted.classes()).not.toContain(HIGHLIGHT_CLASS_NAME);
+ expect(linetoBeHighlighted.classes()).toContain(HIGHLIGHT_CLASS_NAME);
});
});
});
diff --git a/spec/frontend/vue_shared/components/chronic_duration_input_spec.js b/spec/frontend/vue_shared/components/chronic_duration_input_spec.js
index 083a5f60d1d..6932a812287 100644
--- a/spec/frontend/vue_shared/components/chronic_duration_input_spec.js
+++ b/spec/frontend/vue_shared/components/chronic_duration_input_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import ChronicDurationInput from '~/vue_shared/components/chronic_duration_input.vue';
const MOCK_VALUE = 2 * 3600 + 20 * 60;
@@ -48,7 +49,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
describe('change', () => {
const createAndDispatch = async (initialValue, humanReadableInput) => {
createComponent({ value: initialValue });
- await wrapper.vm.$nextTick();
+ await nextTick();
textElement.value = humanReadableInput;
textElement.dispatchEvent(new Event('input'));
};
@@ -118,7 +119,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('emits valid with user input', async () => {
textElement.value = '1m10s';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('valid')).toEqual([
[{ valid: true, feedback: '' }],
@@ -133,7 +134,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
textElement.value = '';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('valid')).toEqual([
[{ valid: true, feedback: '' }],
@@ -151,7 +152,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('emits invalid with user input', async () => {
textElement.value = 'gobbledygook';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('valid')).toEqual([
[{ valid: true, feedback: '' }],
@@ -186,7 +187,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('emits valid with updated value', async () => {
wrapper.setProps({ value: MOCK_VALUE });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('valid')).toEqual([
[{ valid: null, feedback: '' }],
@@ -210,7 +211,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('emits valid when input is integer', async () => {
textElement.value = '2hr20min';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('change')).toEqual([[MOCK_VALUE]]);
expect(wrapper.emitted('valid')).toEqual([
@@ -228,7 +229,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('emits valid when input is decimal', async () => {
textElement.value = '1.5s';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('change')).toEqual([[1.5]]);
expect(wrapper.emitted('valid')).toEqual([
@@ -252,7 +253,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('emits valid when input is integer', async () => {
textElement.value = '2hr20min';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('change')).toEqual([[MOCK_VALUE]]);
expect(wrapper.emitted('valid')).toEqual([
@@ -270,7 +271,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('emits invalid when input is decimal', async () => {
textElement.value = '1.5s';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('change')).toBeUndefined();
expect(wrapper.emitted('valid')).toEqual([
@@ -318,7 +319,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ value: MOCK_VALUE });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(textElement.value).toBe('2 hrs 20 mins');
expect(hiddenElement.value).toBe(MOCK_VALUE.toString());
@@ -329,7 +330,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('passes user input to parent via v-model', async () => {
textElement.value = '2hr20min';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.findComponent(ChronicDurationInput).props('value')).toBe(MOCK_VALUE);
expect(textElement.value).toBe('2hr20min');
@@ -377,7 +378,7 @@ describe('vue_shared/components/chronic_duration_input', () => {
it('creates form data with user-specified value', async () => {
textElement.value = '1m10s';
textElement.dispatchEvent(new Event('input'));
- await wrapper.vm.$nextTick();
+ await nextTick();
const formData = new FormData(wrapper.find('[data-testid=myForm]').element);
const iter = formData.entries();
diff --git a/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js b/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js
index 33667a1bb71..d4b6b987c69 100644
--- a/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js
+++ b/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js
@@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils';
import timezoneMock from 'timezone-mock';
+import { nextTick } from 'vue';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import {
defaultTimeRanges,
@@ -29,26 +30,23 @@ describe('DateTimePicker', () => {
wrapper.destroy();
});
- it('renders dropdown toggle button with selected text', () => {
+ it('renders dropdown toggle button with selected text', async () => {
createComponent();
- return wrapper.vm.$nextTick(() => {
- expect(dropdownToggle().text()).toBe(defaultTimeRange.label);
- });
+ await nextTick();
+ expect(dropdownToggle().text()).toBe(defaultTimeRange.label);
});
- it('renders dropdown toggle button with selected text and utc label', () => {
+ it('renders dropdown toggle button with selected text and utc label', async () => {
createComponent({ utc: true });
- return wrapper.vm.$nextTick(() => {
- expect(dropdownToggle().text()).toContain(defaultTimeRange.label);
- expect(dropdownToggle().text()).toContain('UTC');
- });
+ await nextTick();
+ expect(dropdownToggle().text()).toContain(defaultTimeRange.label);
+ expect(dropdownToggle().text()).toContain('UTC');
});
- it('renders dropdown with 2 custom time range inputs', () => {
+ it('renders dropdown with 2 custom time range inputs', async () => {
createComponent();
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.findAll('input').length).toBe(2);
- });
+ await nextTick();
+ expect(wrapper.findAll('input').length).toBe(2);
});
describe('renders label with h/m/s truncated if possible', () => {
@@ -80,33 +78,30 @@ describe('DateTimePicker', () => {
label: '2019-10-10 00:00:01 to 2019-10-10 00:00:01 UTC',
},
].forEach(({ start, end, utc, label }) => {
- it(`for start ${start}, end ${end}, and utc ${utc}, label is ${label}`, () => {
+ it(`for start ${start}, end ${end}, and utc ${utc}, label is ${label}`, async () => {
createComponent({
value: { start, end },
utc,
});
- return wrapper.vm.$nextTick(() => {
- expect(dropdownToggle().text()).toBe(label);
- });
+ await nextTick();
+ expect(dropdownToggle().text()).toBe(label);
});
});
});
- it(`renders dropdown with ${optionsCount} (default) items in quick range`, () => {
+ it(`renders dropdown with ${optionsCount} (default) items in quick range`, async () => {
createComponent();
dropdownToggle().trigger('click');
- return wrapper.vm.$nextTick(() => {
- expect(findQuickRangeItems().length).toBe(optionsCount);
- });
+ await nextTick();
+ expect(findQuickRangeItems().length).toBe(optionsCount);
});
- it('renders dropdown with a default quick range item selected', () => {
+ it('renders dropdown with a default quick range item selected', async () => {
createComponent();
dropdownToggle().trigger('click');
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find('.dropdown-item.active').exists()).toBe(true);
- expect(wrapper.find('.dropdown-item.active').text()).toBe(defaultTimeRange.label);
- });
+ await nextTick();
+ expect(wrapper.find('.dropdown-item.active').exists()).toBe(true);
+ expect(wrapper.find('.dropdown-item.active').text()).toBe(defaultTimeRange.label);
});
it('renders a disabled apply button on wrong input', () => {
@@ -118,74 +113,63 @@ describe('DateTimePicker', () => {
});
describe('user input', () => {
- const fillInputAndBlur = (input, val) => {
+ const fillInputAndBlur = async (input, val) => {
wrapper.find(input).setValue(val);
- return wrapper.vm.$nextTick().then(() => {
- wrapper.find(input).trigger('blur');
- return wrapper.vm.$nextTick();
- });
+ await nextTick();
+ wrapper.find(input).trigger('blur');
+ await nextTick();
};
- beforeEach(() => {
+ beforeEach(async () => {
createComponent();
- return wrapper.vm.$nextTick();
+ await nextTick();
});
- it('displays inline error message if custom time range inputs are invalid', () => {
- return fillInputAndBlur('#custom-time-from', '2019-10-01abc')
- .then(() => fillInputAndBlur('#custom-time-to', '2019-10-10abc'))
- .then(() => {
- expect(wrapper.findAll('.invalid-feedback').length).toBe(2);
- });
+ it('displays inline error message if custom time range inputs are invalid', async () => {
+ await fillInputAndBlur('#custom-time-from', '2019-10-01abc');
+ await fillInputAndBlur('#custom-time-to', '2019-10-10abc');
+ expect(wrapper.findAll('.invalid-feedback').length).toBe(2);
});
- it('keeps apply button disabled with invalid custom time range inputs', () => {
- return fillInputAndBlur('#custom-time-from', '2019-10-01abc')
- .then(() => fillInputAndBlur('#custom-time-to', '2019-09-19'))
- .then(() => {
- expect(applyButtonElement().getAttribute('disabled')).toBe('disabled');
- });
+ it('keeps apply button disabled with invalid custom time range inputs', async () => {
+ await fillInputAndBlur('#custom-time-from', '2019-10-01abc');
+ await fillInputAndBlur('#custom-time-to', '2019-09-19');
+ expect(applyButtonElement().getAttribute('disabled')).toBe('disabled');
});
- it('enables apply button with valid custom time range inputs', () => {
- return fillInputAndBlur('#custom-time-from', '2019-10-01')
- .then(() => fillInputAndBlur('#custom-time-to', '2019-10-19'))
- .then(() => {
- expect(applyButtonElement().getAttribute('disabled')).toBeNull();
- });
+ it('enables apply button with valid custom time range inputs', async () => {
+ await fillInputAndBlur('#custom-time-from', '2019-10-01');
+ await fillInputAndBlur('#custom-time-to', '2019-10-19');
+ expect(applyButtonElement().getAttribute('disabled')).toBeNull();
});
describe('when "apply" is clicked', () => {
- it('emits iso dates', () => {
- return fillInputAndBlur('#custom-time-from', '2019-10-01 00:00:00')
- .then(() => fillInputAndBlur('#custom-time-to', '2019-10-19 00:00:00'))
- .then(() => {
- applyButtonElement().click();
-
- expect(wrapper.emitted().input).toHaveLength(1);
- expect(wrapper.emitted().input[0]).toEqual([
- {
- end: '2019-10-19T00:00:00Z',
- start: '2019-10-01T00:00:00Z',
- },
- ]);
- });
+ it('emits iso dates', async () => {
+ await fillInputAndBlur('#custom-time-from', '2019-10-01 00:00:00');
+ await fillInputAndBlur('#custom-time-to', '2019-10-19 00:00:00');
+ applyButtonElement().click();
+
+ expect(wrapper.emitted().input).toHaveLength(1);
+ expect(wrapper.emitted().input[0]).toEqual([
+ {
+ end: '2019-10-19T00:00:00Z',
+ start: '2019-10-01T00:00:00Z',
+ },
+ ]);
});
- it('emits iso dates, for dates without time of day', () => {
- return fillInputAndBlur('#custom-time-from', '2019-10-01')
- .then(() => fillInputAndBlur('#custom-time-to', '2019-10-19'))
- .then(() => {
- applyButtonElement().click();
-
- expect(wrapper.emitted().input).toHaveLength(1);
- expect(wrapper.emitted().input[0]).toEqual([
- {
- end: '2019-10-19T00:00:00Z',
- start: '2019-10-01T00:00:00Z',
- },
- ]);
- });
+ it('emits iso dates, for dates without time of day', async () => {
+ await fillInputAndBlur('#custom-time-from', '2019-10-01');
+ await fillInputAndBlur('#custom-time-to', '2019-10-19');
+ applyButtonElement().click();
+
+ expect(wrapper.emitted().input).toHaveLength(1);
+ expect(wrapper.emitted().input[0]).toEqual([
+ {
+ end: '2019-10-19T00:00:00Z',
+ start: '2019-10-01T00:00:00Z',
+ },
+ ]);
});
describe('when timezone is different', () => {
@@ -196,52 +180,46 @@ describe('DateTimePicker', () => {
timezoneMock.unregister();
});
- it('emits iso dates', () => {
- return fillInputAndBlur('#custom-time-from', '2019-10-01 00:00:00')
- .then(() => fillInputAndBlur('#custom-time-to', '2019-10-19 12:00:00'))
- .then(() => {
- applyButtonElement().click();
-
- expect(wrapper.emitted().input).toHaveLength(1);
- expect(wrapper.emitted().input[0]).toEqual([
- {
- start: '2019-10-01T07:00:00Z',
- end: '2019-10-19T19:00:00Z',
- },
- ]);
- });
+ it('emits iso dates', async () => {
+ await fillInputAndBlur('#custom-time-from', '2019-10-01 00:00:00');
+ await fillInputAndBlur('#custom-time-to', '2019-10-19 12:00:00');
+ applyButtonElement().click();
+
+ expect(wrapper.emitted().input).toHaveLength(1);
+ expect(wrapper.emitted().input[0]).toEqual([
+ {
+ start: '2019-10-01T07:00:00Z',
+ end: '2019-10-19T19:00:00Z',
+ },
+ ]);
});
- it('emits iso dates with utc format', () => {
+ it('emits iso dates with utc format', async () => {
wrapper.setProps({ utc: true });
- return wrapper.vm
- .$nextTick()
- .then(() => fillInputAndBlur('#custom-time-from', '2019-10-01 00:00:00'))
- .then(() => fillInputAndBlur('#custom-time-to', '2019-10-19 12:00:00'))
- .then(() => {
- applyButtonElement().click();
-
- expect(wrapper.emitted().input).toHaveLength(1);
- expect(wrapper.emitted().input[0]).toEqual([
- {
- start: '2019-10-01T00:00:00Z',
- end: '2019-10-19T12:00:00Z',
- },
- ]);
- });
+ await nextTick();
+ await fillInputAndBlur('#custom-time-from', '2019-10-01 00:00:00');
+ await fillInputAndBlur('#custom-time-to', '2019-10-19 12:00:00');
+ applyButtonElement().click();
+
+ expect(wrapper.emitted().input).toHaveLength(1);
+ expect(wrapper.emitted().input[0]).toEqual([
+ {
+ start: '2019-10-01T00:00:00Z',
+ end: '2019-10-19T12:00:00Z',
+ },
+ ]);
});
});
});
- it('unchecks quick range when text is input is clicked', () => {
+ it('unchecks quick range when text is input is clicked', async () => {
const findActiveItems = () =>
findQuickRangeItems().filter((w) => w.classes().includes('active'));
expect(findActiveItems().length).toBe(1);
- return fillInputAndBlur('#custom-time-from', '2019-10-01').then(() => {
- expect(findActiveItems().length).toBe(0);
- });
+ await fillInputAndBlur('#custom-time-from', '2019-10-01');
+ expect(findActiveItems().length).toBe(0);
});
it('emits dates in an object when a is clicked', () => {
@@ -257,16 +235,14 @@ describe('DateTimePicker', () => {
});
});
- it('hides the popover with cancel button', () => {
+ it('hides the popover with cancel button', async () => {
dropdownToggle().trigger('click');
- return wrapper.vm.$nextTick(() => {
- cancelButton().trigger('click');
+ await nextTick();
+ cancelButton().trigger('click');
- return wrapper.vm.$nextTick(() => {
- expect(dropdownMenu().classes('show')).toBe(false);
- });
- });
+ await nextTick();
+ expect(dropdownMenu().classes('show')).toBe(false);
});
});
@@ -293,7 +269,7 @@ describe('DateTimePicker', () => {
jest.spyOn(Date, 'now').mockImplementation(() => MOCK_NOW);
});
- it('renders dropdown with a label in the quick range', () => {
+ it('renders dropdown with a label in the quick range', async () => {
createComponent({
value: {
duration: { seconds: 60 * 5 },
@@ -301,12 +277,11 @@ describe('DateTimePicker', () => {
options: otherTimeRanges,
});
dropdownToggle().trigger('click');
- return wrapper.vm.$nextTick(() => {
- expect(dropdownToggle().text()).toBe('5 minutes');
- });
+ await nextTick();
+ expect(dropdownToggle().text()).toBe('5 minutes');
});
- it('renders dropdown with a label in the quick range and utc label', () => {
+ it('renders dropdown with a label in the quick range and utc label', async () => {
createComponent({
value: {
duration: { seconds: 60 * 5 },
@@ -315,12 +290,11 @@ describe('DateTimePicker', () => {
options: otherTimeRanges,
});
dropdownToggle().trigger('click');
- return wrapper.vm.$nextTick(() => {
- expect(dropdownToggle().text()).toBe('5 minutes UTC');
- });
+ await nextTick();
+ expect(dropdownToggle().text()).toBe('5 minutes UTC');
});
- it('renders dropdown with quick range items', () => {
+ it('renders dropdown with quick range items', async () => {
createComponent({
value: {
duration: { seconds: 60 * 2 },
@@ -328,31 +302,29 @@ describe('DateTimePicker', () => {
options: otherTimeRanges,
});
dropdownToggle().trigger('click');
- return wrapper.vm.$nextTick(() => {
- const items = findQuickRangeItems();
+ await nextTick();
+ const items = findQuickRangeItems();
- expect(items.length).toBe(Object.keys(otherTimeRanges).length);
- expect(items.at(0).text()).toBe('1 minute');
- expect(items.at(0).classes()).not.toContain('active');
+ expect(items.length).toBe(Object.keys(otherTimeRanges).length);
+ expect(items.at(0).text()).toBe('1 minute');
+ expect(items.at(0).classes()).not.toContain('active');
- expect(items.at(1).text()).toBe('2 minutes');
- expect(items.at(1).classes()).toContain('active');
+ expect(items.at(1).text()).toBe('2 minutes');
+ expect(items.at(1).classes()).toContain('active');
- expect(items.at(2).text()).toBe('5 minutes');
- expect(items.at(2).classes()).not.toContain('active');
- });
+ expect(items.at(2).text()).toBe('5 minutes');
+ expect(items.at(2).classes()).not.toContain('active');
});
- it('renders dropdown with a label not in the quick range', () => {
+ it('renders dropdown with a label not in the quick range', async () => {
createComponent({
value: {
duration: { seconds: 60 * 4 },
},
});
dropdownToggle().trigger('click');
- return wrapper.vm.$nextTick(() => {
- expect(dropdownToggle().text()).toBe('2020-01-23 19:56:00 to 2020-01-23 20:00:00');
- });
+ await nextTick();
+ expect(dropdownToggle().text()).toBe('2020-01-23 19:56:00 to 2020-01-23 20:00:00');
});
});
});
diff --git a/spec/frontend/vue_shared/components/deployment_instance/deployment_instance_spec.js b/spec/frontend/vue_shared/components/deployment_instance/deployment_instance_spec.js
index b812ced72c9..59653a0ec13 100644
--- a/spec/frontend/vue_shared/components/deployment_instance/deployment_instance_spec.js
+++ b/spec/frontend/vue_shared/components/deployment_instance/deployment_instance_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DeployBoardInstance from '~/vue_shared/components/deployment_instance.vue';
import { folder } from './mock_data';
@@ -28,17 +29,15 @@ describe('Deploy Board Instance', () => {
expect(wrapper.attributes('title')).toEqual('This is a pod');
});
- it('should render a div without tooltip data', (done) => {
+ it('should render a div without tooltip data', async () => {
wrapper = createComponent({
status: 'deploying',
tooltipText: '',
});
- wrapper.vm.$nextTick(() => {
- expect(wrapper.classes('deployment-instance-deploying')).toBe(true);
- expect(wrapper.attributes('title')).toEqual('');
- done();
- });
+ await nextTick();
+ expect(wrapper.classes('deployment-instance-deploying')).toBe(true);
+ expect(wrapper.attributes('title')).toEqual('');
});
it('should have a log path computed with a pod name as a parameter', () => {
@@ -58,15 +57,13 @@ describe('Deploy Board Instance', () => {
wrapper.destroy();
});
- it('should render a div with canary class when stable prop is provided as false', (done) => {
+ it('should render a div with canary class when stable prop is provided as false', async () => {
wrapper = createComponent({
stable: false,
});
- wrapper.vm.$nextTick(() => {
- expect(wrapper.classes('deployment-instance-canary')).toBe(true);
- done();
- });
+ await nextTick();
+ expect(wrapper.classes('deployment-instance-canary')).toBe(true);
});
});
@@ -75,17 +72,15 @@ describe('Deploy Board Instance', () => {
wrapper.destroy();
});
- it('should not be a link without a logsPath prop', (done) => {
+ it('should not be a link without a logsPath prop', async () => {
wrapper = createComponent({
stable: false,
logsPath: '',
});
- wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.computedLogPath).toBeNull();
- expect(wrapper.vm.isLink).toBeFalsy();
- done();
- });
+ await nextTick();
+ expect(wrapper.vm.computedLogPath).toBeNull();
+ expect(wrapper.vm.isLink).toBeFalsy();
});
it('should render a link without href if path is not passed', () => {
diff --git a/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js b/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js
index 8deb466b33c..977e7c9c38c 100644
--- a/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { compileToFunctions } from 'vue-template-compiler';
import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants';
@@ -51,31 +51,28 @@ describe('ImageDiffViewer', () => {
wrapper.destroy();
});
- it('renders image diff for replaced', (done) => {
+ it('renders image diff for replaced', async () => {
createComponent({ ...allProps });
- vm.$nextTick(() => {
- const metaInfoElements = vm.$el.querySelectorAll('.image-info');
+ await nextTick();
+ const metaInfoElements = vm.$el.querySelectorAll('.image-info');
- expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL);
-
- expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(RED_BOX_IMAGE_URL);
+ expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL);
- expect(vm.$el.querySelector('.view-modes-menu li.active').textContent.trim()).toBe('2-up');
- expect(vm.$el.querySelector('.view-modes-menu li:nth-child(2)').textContent.trim()).toBe(
- 'Swipe',
- );
+ expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(RED_BOX_IMAGE_URL);
- expect(vm.$el.querySelector('.view-modes-menu li:nth-child(3)').textContent.trim()).toBe(
- 'Onion skin',
- );
+ expect(vm.$el.querySelector('.view-modes-menu li.active').textContent.trim()).toBe('2-up');
+ expect(vm.$el.querySelector('.view-modes-menu li:nth-child(2)').textContent.trim()).toBe(
+ 'Swipe',
+ );
- expect(metaInfoElements.length).toBe(2);
- expect(metaInfoElements[0]).toHaveText('2.00 KiB');
- expect(metaInfoElements[1]).toHaveText('1.00 KiB');
+ expect(vm.$el.querySelector('.view-modes-menu li:nth-child(3)').textContent.trim()).toBe(
+ 'Onion skin',
+ );
- done();
- });
+ expect(metaInfoElements.length).toBe(2);
+ expect(metaInfoElements[0]).toHaveText('2.00 KiB');
+ expect(metaInfoElements[1]).toHaveText('1.00 KiB');
});
it('renders image diff for new', (done) => {
@@ -151,13 +148,11 @@ describe('ImageDiffViewer', () => {
});
});
- it('switches to Swipe Mode', (done) => {
+ it('switches to Swipe Mode', async () => {
vm.$el.querySelector('.view-modes-menu li:nth-child(2)').click();
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.view-modes-menu li.active').textContent.trim()).toBe('Swipe');
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.view-modes-menu li.active').textContent.trim()).toBe('Swipe');
});
});
@@ -170,29 +165,24 @@ describe('ImageDiffViewer', () => {
});
});
- it('switches to Onion Skin Mode', (done) => {
+ it('switches to Onion Skin Mode', async () => {
vm.$el.querySelector('.view-modes-menu li:nth-child(3)').click();
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.view-modes-menu li.active').textContent.trim()).toBe(
- 'Onion skin',
- );
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.view-modes-menu li.active').textContent.trim()).toBe(
+ 'Onion skin',
+ );
});
- it('has working drag handler', (done) => {
+ it('has working drag handler', async () => {
vm.$el.querySelector('.view-modes-menu li:nth-child(3)').click();
- vm.$nextTick(() => {
- dragSlider(vm.$el.querySelector('.dragger'), document, 20);
+ await nextTick();
+ dragSlider(vm.$el.querySelector('.dragger'), document, 20);
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.dragger').style.left).toBe('20px');
- expect(vm.$el.querySelector('.added.frame').style.opacity).toBe('0.2');
- done();
- });
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.dragger').style.left).toBe('20px');
+ expect(vm.$el.querySelector('.added.frame').style.opacity).toBe('0.2');
});
});
});
diff --git a/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js b/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js
index b8d3cbebe16..549388c1a5c 100644
--- a/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js
+++ b/spec/frontend/vue_shared/components/diff_viewer/viewers/renamed_spec.js
@@ -1,5 +1,5 @@
import { shallowMount, mount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import {
TRANSITION_LOAD_START,
@@ -126,15 +126,14 @@ describe('Renamed Diff Viewer', () => {
store = null;
});
- it('calls the switchToFullDiffFromRenamedFile action when the method is triggered', () => {
+ it('calls the switchToFullDiffFromRenamedFile action when the method is triggered', async () => {
store.dispatch.mockResolvedValue();
wrapper.vm.switchToFull();
- return wrapper.vm.$nextTick().then(() => {
- expect(store.dispatch).toHaveBeenCalledWith('diffs/switchToFullDiffFromRenamedFile', {
- diffFile,
- });
+ await nextTick();
+ expect(store.dispatch).toHaveBeenCalledWith('diffs/switchToFullDiffFromRenamedFile', {
+ diffFile,
});
});
@@ -144,7 +143,7 @@ describe('Renamed Diff Viewer', () => {
${STATE_ERRORED} | ${'mockRejectedValue'} | ${'rejected'}
`(
'moves through the correct states during a $resolution request',
- ({ after, resolvePromise }) => {
+ async ({ after, resolvePromise }) => {
store.dispatch[resolvePromise]();
expect(wrapper.vm.state).toEqual(STATE_IDLING);
@@ -153,16 +152,9 @@ describe('Renamed Diff Viewer', () => {
expect(wrapper.vm.state).toEqual(STATE_LOADING);
- return (
- wrapper.vm
- // This tick is needed for when the action (promise) finishes
- .$nextTick()
- // This tick waits for the state change in the promise .then/.catch to bubble into the component
- .then(() => wrapper.vm.$nextTick())
- .then(() => {
- expect(wrapper.vm.state).toEqual(after);
- })
- );
+ await nextTick(); // This tick is needed for when the action (promise) finishes
+ await nextTick(); // This tick waits for the state change in the promise .then/.catch to bubble into the component
+ expect(wrapper.vm.state).toEqual(after);
},
);
});
diff --git a/spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js b/spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js
index 194681a6138..4b32fbffebe 100644
--- a/spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js
+++ b/spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js
@@ -1,5 +1,6 @@
import { GlAlert, GlSprintf } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import Component from '~/vue_shared/components/dismissible_feedback_alert.vue';
@@ -64,7 +65,7 @@ describe('Dismissible Feedback Alert', () => {
it('should not show the alert once dismissed', async () => {
localStorage.setItem(STORAGE_DISMISSAL_KEY, 'true');
createFullComponent();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findAlert().exists()).toBe(false);
});
diff --git a/spec/frontend/vue_shared/components/dropdown/dropdown_search_input_spec.js b/spec/frontend/vue_shared/components/dropdown/dropdown_search_input_spec.js
index ec553c52236..b32dbeb8852 100644
--- a/spec/frontend/vue_shared/components/dropdown/dropdown_search_input_spec.js
+++ b/spec/frontend/vue_shared/components/dropdown/dropdown_search_input_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DropdownSearchInputComponent from '~/vue_shared/components/dropdown/dropdown_search_input.vue';
describe('DropdownSearchInputComponent', () => {
@@ -36,16 +37,15 @@ describe('DropdownSearchInputComponent', () => {
expect(findInputEl().attributes('placeholder')).toBe(defaultProps.placeholderText);
});
- it('focuses input element when focused property equals true', () => {
+ it('focuses input element when focused property equals true', async () => {
const inputEl = findInputEl().element;
jest.spyOn(inputEl, 'focus');
wrapper.setProps({ focused: true });
- return wrapper.vm.$nextTick().then(() => {
- expect(inputEl.focus).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(inputEl.focus).toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js b/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js
index b3af5fd3feb..084d0559665 100644
--- a/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js
+++ b/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js
@@ -1,6 +1,7 @@
import { GlDropdown, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DropdownWidget from '~/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue';
describe('DropdownWidget component', () => {
@@ -53,7 +54,7 @@ describe('DropdownWidget component', () => {
describe('when dropdown is open', () => {
beforeEach(async () => {
findDropdown().vm.$emit('show');
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('emits search event when typing in search box', () => {
@@ -69,7 +70,7 @@ describe('DropdownWidget component', () => {
it('emits set-option event when clicking on an option', async () => {
wrapper.findAll('[data-testid="unselected-option"]').at(1).trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('set-option')).toEqual([[wrapper.props().options[1]]]);
});
diff --git a/spec/frontend/vue_shared/components/dropdown_keyboard_navigation_spec.js b/spec/frontend/vue_shared/components/dropdown_keyboard_navigation_spec.js
index 996df34f2ff..c34041f9305 100644
--- a/spec/frontend/vue_shared/components/dropdown_keyboard_navigation_spec.js
+++ b/spec/frontend/vue_shared/components/dropdown_keyboard_navigation_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DropdownKeyboardNavigation from '~/vue_shared/components/dropdown_keyboard_navigation.vue';
import { UP_KEY_CODE, DOWN_KEY_CODE, TAB_KEY_CODE } from '~/lib/utils/keycodes';
@@ -53,7 +54,7 @@ describe('DropdownKeyboardNavigation', () => {
it('should $emit @change with the default index when max changes', async () => {
wrapper.setProps({ max: 20 });
- await wrapper.vm.$nextTick();
+ await nextTick();
// The first @change`call happens on created() so we test for the second [1]
expect(wrapper.emitted('change')[1]).toStrictEqual([MOCK_DEFAULT_INDEX]);
});
diff --git a/spec/frontend/vue_shared/components/file_finder/index_spec.js b/spec/frontend/vue_shared/components/file_finder/index_spec.js
index 181fc4017a3..e5c10a23947 100644
--- a/spec/frontend/vue_shared/components/file_finder/index_spec.js
+++ b/spec/frontend/vue_shared/components/file_finder/index_spec.js
@@ -1,5 +1,5 @@
import Mousetrap from 'mousetrap';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import { file } from 'jest/ide/helpers';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
@@ -310,34 +310,26 @@ describe('File finder item spec', () => {
});
describe('keyboard shortcuts', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
createComponent();
jest.spyOn(vm, 'toggle').mockImplementation(() => {});
- vm.$nextTick(done);
+ await nextTick();
});
- it('calls toggle on `t` key press', (done) => {
+ it('calls toggle on `t` key press', async () => {
Mousetrap.trigger('t');
- vm.$nextTick()
- .then(() => {
- expect(vm.toggle).toHaveBeenCalled();
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.toggle).toHaveBeenCalled();
});
- it('calls toggle on `mod+p` key press', (done) => {
+ it('calls toggle on `mod+p` key press', async () => {
Mousetrap.trigger('mod+p');
- vm.$nextTick()
- .then(() => {
- expect(vm.toggle).toHaveBeenCalled();
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.toggle).toHaveBeenCalled();
});
it('always allows `mod+p` to trigger toggle', () => {
diff --git a/spec/frontend/vue_shared/components/file_finder/item_spec.js b/spec/frontend/vue_shared/components/file_finder/item_spec.js
index 1a4a97efb95..b69c33055c1 100644
--- a/spec/frontend/vue_shared/components/file_finder/item_spec.js
+++ b/spec/frontend/vue_shared/components/file_finder/item_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import createComponent from 'helpers/vue_mount_component_helper';
import { file } from 'jest/ide/helpers';
import ItemComponent from '~/vue_shared/components/file_finder/item.vue';
@@ -37,14 +37,11 @@ describe('File finder item spec', () => {
expect(vm.$el.classList).toContain('is-focused');
});
- it('does not have is-focused class when not focused', (done) => {
+ it('does not have is-focused class when not focused', async () => {
vm.focused = false;
- vm.$nextTick(() => {
- expect(vm.$el.classList).not.toContain('is-focused');
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.classList).not.toContain('is-focused');
});
});
@@ -53,24 +50,18 @@ describe('File finder item spec', () => {
expect(vm.$el.querySelector('.diff-changed-stats')).toBe(null);
});
- it('renders when a changed file', (done) => {
+ it('renders when a changed file', async () => {
vm.file.changed = true;
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.diff-changed-stats')).not.toBe(null);
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.diff-changed-stats')).not.toBe(null);
});
- it('renders when a temp file', (done) => {
+ it('renders when a temp file', async () => {
vm.file.tempFile = true;
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.diff-changed-stats')).not.toBe(null);
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.diff-changed-stats')).not.toBe(null);
});
});
@@ -85,56 +76,52 @@ describe('File finder item spec', () => {
describe('path', () => {
let el;
- beforeEach((done) => {
+ beforeEach(async () => {
vm.searchText = 'file';
el = vm.$el.querySelector('.diff-changed-file-path');
- vm.$nextTick(done);
+ nextTick();
});
it('highlights text', () => {
expect(el.querySelectorAll('.highlighted').length).toBe(4);
});
- it('adds ellipsis to long text', (done) => {
+ it('adds ellipsis to long text', async () => {
vm.file.path = new Array(70)
.fill()
.map((_, i) => `${i}-`)
.join('');
- vm.$nextTick(() => {
- expect(el.textContent).toBe(`...${vm.file.path.substr(vm.file.path.length - 60)}`);
- done();
- });
+ await nextTick();
+ expect(el.textContent).toBe(`...${vm.file.path.substr(vm.file.path.length - 60)}`);
});
});
describe('name', () => {
let el;
- beforeEach((done) => {
+ beforeEach(async () => {
vm.searchText = 'file';
el = vm.$el.querySelector('.diff-changed-file-name');
- vm.$nextTick(done);
+ await nextTick();
});
it('highlights text', () => {
expect(el.querySelectorAll('.highlighted').length).toBe(4);
});
- it('does not add ellipsis to long text', (done) => {
+ it('does not add ellipsis to long text', async () => {
vm.file.name = new Array(70)
.fill()
.map((_, i) => `${i}-`)
.join('');
- vm.$nextTick(() => {
- expect(el.textContent).not.toBe(`...${vm.file.name.substr(vm.file.name.length - 60)}`);
- done();
- });
+ await nextTick();
+ expect(el.textContent).not.toBe(`...${vm.file.name.substr(vm.file.name.length - 60)}`);
});
});
});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
index 4e9eac2dde2..575e8a73050 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
@@ -8,6 +8,7 @@ import {
} from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
import RecentSearchesStore from '~/filtered_search/stores/recent_searches_store';
import { SortDirection } from '~/vue_shared/components/filtered_search_bar/constants';
@@ -172,7 +173,7 @@ describe('FilteredSearchBarRoot', () => {
recentSearches: [{ foo: 'bar' }, 'foo'],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.filteredRecentSearches).toHaveLength(1);
expect(wrapper.vm.filteredRecentSearches[0]).toEqual({ foo: 'bar' });
@@ -188,7 +189,7 @@ describe('FilteredSearchBarRoot', () => {
],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.filteredRecentSearches).toHaveLength(2);
expect(uniqueTokens).toHaveBeenCalled();
@@ -199,7 +200,7 @@ describe('FilteredSearchBarRoot', () => {
recentSearchesStorageKey: '',
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.filteredRecentSearches).not.toBeDefined();
});
@@ -208,7 +209,7 @@ describe('FilteredSearchBarRoot', () => {
describe('watchers', () => {
describe('filterValue', () => {
- it('emits component event `onFilter` with empty array and false when filter was never selected', () => {
+ it('emits component event `onFilter` with empty array and false when filter was never selected', async () => {
wrapper = createComponent({ initialFilterValue: [tokenValueEmpty] });
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -217,12 +218,11 @@ describe('FilteredSearchBarRoot', () => {
filterValue: [tokenValueEmpty],
});
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.emitted('onFilter')[0]).toEqual([[], false]);
- });
+ await nextTick();
+ expect(wrapper.emitted('onFilter')[0]).toEqual([[], false]);
});
- it('emits component event `onFilter` with empty array and true when initially selected filter value was cleared', () => {
+ it('emits component event `onFilter` with empty array and true when initially selected filter value was cleared', async () => {
wrapper = createComponent({ initialFilterValue: [tokenValueLabel] });
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -231,9 +231,8 @@ describe('FilteredSearchBarRoot', () => {
filterValue: [tokenValueEmpty],
});
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.emitted('onFilter')[0]).toEqual([[], true]);
- });
+ await nextTick();
+ expect(wrapper.emitted('onFilter')[0]).toEqual([[], true]);
});
});
});
@@ -336,7 +335,7 @@ describe('FilteredSearchBarRoot', () => {
filterValue: mockFilters,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('calls `uniqueTokens` on `filterValue` prop to remove duplicates', () => {
@@ -395,7 +394,7 @@ describe('FilteredSearchBarRoot', () => {
});
describe('template', () => {
- beforeEach(() => {
+ beforeEach(async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
@@ -404,7 +403,7 @@ describe('FilteredSearchBarRoot', () => {
recentSearches: mockHistoryItems,
});
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders gl-filtered-search component', () => {
@@ -439,7 +438,7 @@ describe('FilteredSearchBarRoot', () => {
const wrapperFullMount = createComponent({ sortOptions: mockSortOptions, shallow: false });
wrapperFullMount.vm.recentSearchesStore.addRecentSearch(mockHistoryItems[0]);
- await wrapperFullMount.vm.$nextTick();
+ await nextTick();
const searchHistoryItemsEl = wrapperFullMount.findAll(
'.gl-search-box-by-click-menu .gl-search-box-by-click-history-item',
@@ -462,7 +461,7 @@ describe('FilteredSearchBarRoot', () => {
wrapperFullMount.vm.recentSearchesStore.addRecentSearch([tokenValueMembership]);
- await wrapperFullMount.vm.$nextTick();
+ await nextTick();
expect(wrapperFullMount.find(GlDropdownItem).text()).toBe('Membership := Direct');
@@ -480,7 +479,7 @@ describe('FilteredSearchBarRoot', () => {
wrapperFullMount.vm.recentSearchesStore.addRecentSearch([tokenValueMembership]);
- await wrapperFullMount.vm.$nextTick();
+ await nextTick();
expect(wrapperFullMount.find(GlDropdownItem).text()).toBe('Membership := exclude');
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
index 5865c6a41b8..87066b70023 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js
@@ -6,6 +6,7 @@ import {
} from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
@@ -167,7 +168,7 @@ describe('AuthorToken', () => {
const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
};
it('renders base-token component', () => {
@@ -185,23 +186,22 @@ describe('AuthorToken', () => {
});
});
- it('renders token item when value is selected', () => {
+ it('renders token item when value is selected', async () => {
wrapper = createComponent({
value: { data: mockAuthors[0].username },
data: { authors: mockAuthors },
stubs: { Portal: true },
});
- return wrapper.vm.$nextTick(() => {
- const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
+ await nextTick();
+ const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
- expect(tokenSegments).toHaveLength(3); // Author, =, "Administrator"
+ expect(tokenSegments).toHaveLength(3); // Author, =, "Administrator"
- const tokenValue = tokenSegments.at(2);
+ const tokenValue = tokenSegments.at(2);
- expect(tokenValue.findComponent(GlAvatar).props('src')).toBe(mockAuthors[0].avatar_url);
- expect(tokenValue.text()).toBe(mockAuthors[0].name); // "Administrator"
- });
+ expect(tokenValue.findComponent(GlAvatar).props('src')).toBe(mockAuthors[0].avatar_url);
+ expect(tokenValue.text()).toBe(mockAuthors[0].name); // "Administrator"
});
it('renders token value with correct avatarUrl from author object', async () => {
@@ -220,7 +220,7 @@ describe('AuthorToken', () => {
stubs: { Portal: true },
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(getAvatarEl().props('src')).toBe(mockAuthors[0].avatar_url);
@@ -236,7 +236,7 @@ describe('AuthorToken', () => {
],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(getAvatarEl().props('src')).toBe(mockAuthors[0].avatar_url);
});
@@ -268,7 +268,7 @@ describe('AuthorToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlDropdownDivider).exists()).toBe(false);
});
@@ -323,7 +323,7 @@ describe('AuthorToken', () => {
it('does not show current user while searching', async () => {
wrapper.findComponent(BaseToken).vm.handleInput({ data: 'foo' });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.findComponent(GlFilteredSearchSuggestion).exists()).toBe(false);
});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js
index 4836d54ad07..dd9bf2ff598 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js
@@ -1,5 +1,6 @@
import { GlFilteredSearchToken, GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import {
mockRegularLabel,
mockLabels,
@@ -202,7 +203,7 @@ describe('BaseToken', () => {
jest.useFakeTimers();
findGlFilteredSearchToken().vm.$emit('input', { data: 'foo' });
- await wrapper.vm.$nextTick();
+ await nextTick();
jest.runAllTimers();
@@ -221,7 +222,7 @@ describe('BaseToken', () => {
jest.useFakeTimers();
findGlFilteredSearchToken().vm.$emit('input', { data: 'foo' });
- await wrapper.vm.$nextTick();
+ await nextTick();
jest.runAllTimers();
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js
index cd8be765fb5..7a7db434052 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js
@@ -6,6 +6,7 @@ import {
} from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
@@ -115,7 +116,7 @@ describe('BranchToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
}
beforeEach(async () => {
@@ -127,7 +128,7 @@ describe('BranchToken', () => {
branches: mockBranches,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders gl-filtered-search-token component', () => {
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js
index ed9ac7c271e..b163563cea4 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js
@@ -6,6 +6,7 @@ import {
} from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
@@ -129,7 +130,7 @@ describe('EmojiToken', () => {
emojis: mockEmojis,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders gl-filtered-search-token component', () => {
@@ -152,7 +153,7 @@ describe('EmojiToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
@@ -171,7 +172,7 @@ describe('EmojiToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlFilteredSearchSuggestion).exists()).toBe(false);
expect(wrapper.find(GlDropdownDivider).exists()).toBe(false);
@@ -186,7 +187,7 @@ describe('EmojiToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js
index b9af71ad8a7..52df27c2d00 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js
@@ -5,6 +5,7 @@ import {
} from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import {
mockRegularLabel,
@@ -150,7 +151,7 @@ describe('LabelToken', () => {
labels: mockLabels,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders base-token component', () => {
@@ -182,7 +183,7 @@ describe('LabelToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
@@ -201,7 +202,7 @@ describe('LabelToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlFilteredSearchSuggestion).exists()).toBe(false);
expect(wrapper.find(GlDropdownDivider).exists()).toBe(false);
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js
index c0d8b5fd139..332f865b874 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js
@@ -6,6 +6,7 @@ import {
} from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
@@ -127,7 +128,7 @@ describe('MilestoneToken', () => {
milestones: mockMilestones,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders gl-filtered-search-token component', () => {
@@ -150,7 +151,7 @@ describe('MilestoneToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
@@ -169,7 +170,7 @@ describe('MilestoneToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlFilteredSearchSuggestion).exists()).toBe(false);
expect(wrapper.find(GlDropdownDivider).exists()).toBe(false);
@@ -184,7 +185,7 @@ describe('MilestoneToken', () => {
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
- await wrapper.vm.$nextTick();
+ await nextTick();
const suggestions = wrapper.findAll(GlFilteredSearchSuggestion);
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/release_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/release_token_spec.js
index b2f246a5985..8be21b35414 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/release_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/release_token_spec.js
@@ -1,5 +1,6 @@
import { GlFilteredSearchToken, GlFilteredSearchTokenSegment } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import ReleaseToken from '~/vue_shared/components/filtered_search_bar/tokens/release_token.vue';
@@ -31,7 +32,7 @@ describe('ReleaseToken', () => {
it('renders release value', async () => {
wrapper = createComponent({ value: { data: id } });
- await wrapper.vm.$nextTick();
+ await nextTick();
const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment);
diff --git a/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js b/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
index b837a998cd6..c0a6588833e 100644
--- a/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
+++ b/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js
@@ -1,6 +1,6 @@
import { GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { BV_SHOW_MODAL, BV_HIDE_MODAL } from '~/lib/utils/constants';
import GlModalVuex from '~/vue_shared/components/gl_modal_vuex.vue';
@@ -118,7 +118,7 @@ describe('GlModalVuex', () => {
expect(actions.hide).toHaveBeenCalledTimes(1);
});
- it('calls bootstrap show when isVisible changes', (done) => {
+ it('calls bootstrap show when isVisible changes', async () => {
state.isVisible = false;
factory();
@@ -126,16 +126,11 @@ describe('GlModalVuex', () => {
state.isVisible = true;
- wrapper.vm
- .$nextTick()
- .then(() => {
- expect(rootEmit).toHaveBeenCalledWith(BV_SHOW_MODAL, TEST_MODAL_ID);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(rootEmit).toHaveBeenCalledWith(BV_SHOW_MODAL, TEST_MODAL_ID);
});
- it('calls bootstrap hide when isVisible changes', (done) => {
+ it('calls bootstrap hide when isVisible changes', async () => {
state.isVisible = true;
factory();
@@ -143,13 +138,8 @@ describe('GlModalVuex', () => {
state.isVisible = false;
- wrapper.vm
- .$nextTick()
- .then(() => {
- expect(rootEmit).toHaveBeenCalledWith(BV_HIDE_MODAL, TEST_MODAL_ID);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(rootEmit).toHaveBeenCalledWith(BV_HIDE_MODAL, TEST_MODAL_ID);
});
it.each(['ok', 'cancel'])(
diff --git a/spec/frontend/vue_shared/components/local_storage_sync_spec.js b/spec/frontend/vue_shared/components/local_storage_sync_spec.js
index 4c5a0c1e601..dac633fe6c8 100644
--- a/spec/frontend/vue_shared/components/local_storage_sync_spec.js
+++ b/spec/frontend/vue_shared/components/local_storage_sync_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
describe('Local Storage Sync', () => {
@@ -49,7 +50,7 @@ describe('Local Storage Sync', () => {
it.each('foo', 3, true, ['foo', 'bar'], { foo: 'bar' })(
'saves updated value to localStorage',
- (newValue) => {
+ async (newValue) => {
createComponent({
props: {
storageKey,
@@ -59,9 +60,8 @@ describe('Local Storage Sync', () => {
wrapper.setProps({ value: newValue });
- return wrapper.vm.$nextTick().then(() => {
- expect(localStorage.getItem(storageKey)).toBe(String(newValue));
- });
+ await nextTick();
+ expect(localStorage.getItem(storageKey)).toBe(String(newValue));
},
);
@@ -109,7 +109,7 @@ describe('Local Storage Sync', () => {
expect(localStorage.getItem(storageKey)).toBe(savedValue);
});
- it('updating the value updates localStorage', () => {
+ it('updating the value updates localStorage', async () => {
createComponent({
props: {
storageKey,
@@ -122,9 +122,8 @@ describe('Local Storage Sync', () => {
value: newValue,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(localStorage.getItem(storageKey)).toBe(newValue);
- });
+ await nextTick();
+ expect(localStorage.getItem(storageKey)).toBe(newValue);
});
it('persists the value by default', async () => {
@@ -137,7 +136,7 @@ describe('Local Storage Sync', () => {
});
wrapper.setProps({ value: persistedValue });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(localStorage.getItem(storageKey)).toBe(persistedValue);
});
@@ -151,7 +150,7 @@ describe('Local Storage Sync', () => {
});
wrapper.setProps({ persist: false, value: notPersistedValue });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(localStorage.getItem(storageKey)).not.toBe(notPersistedValue);
});
});
@@ -172,7 +171,7 @@ describe('Local Storage Sync', () => {
${{ foo: 'bar' }} | ${'{"foo":"bar"}'}
`('given $value', ({ value, serializedValue }) => {
describe('is a new value', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createComponent({
props: {
storageKey,
@@ -183,7 +182,7 @@ describe('Local Storage Sync', () => {
wrapper.setProps({ value });
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('serializes the value correctly to localStorage', () => {
@@ -253,7 +252,7 @@ describe('Local Storage Sync', () => {
value,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(localStorage.getItem(storageKey)).toBe(value);
@@ -261,7 +260,7 @@ describe('Local Storage Sync', () => {
clear: true,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(localStorage.getItem(storageKey)).toBe(null);
});
diff --git a/spec/frontend/vue_shared/components/markdown/field_spec.js b/spec/frontend/vue_shared/components/markdown/field_spec.js
index 0d90ca7f1f6..c31374f6d56 100644
--- a/spec/frontend/vue_shared/components/markdown/field_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/field_spec.js
@@ -100,58 +100,46 @@ describe('Markdown field component', () => {
axiosMock.onPost(markdownPreviewPath).reply(200, { body: previewHTML });
});
- it('sets preview link as active', () => {
+ it('sets preview link as active', async () => {
previewLink = getPreviewLink();
previewLink.trigger('click');
- return subject.vm.$nextTick().then(() => {
- expect(previewLink.element.parentNode.classList.contains('active')).toBeTruthy();
- });
+ await nextTick();
+ expect(previewLink.element.parentNode.classList.contains('active')).toBeTruthy();
});
- it('shows preview loading text', () => {
+ it('shows preview loading text', async () => {
previewLink = getPreviewLink();
previewLink.trigger('click');
- return subject.vm.$nextTick(() => {
- expect(subject.find('.md-preview-holder').element.textContent.trim()).toContain(
- 'Loading…',
- );
- });
+ await nextTick();
+ expect(subject.find('.md-preview-holder').element.textContent.trim()).toContain('Loading…');
});
- it('renders markdown preview and GFM', () => {
+ it('renders markdown preview and GFM', async () => {
const renderGFMSpy = jest.spyOn($.fn, 'renderGFM');
previewLink = getPreviewLink();
previewLink.trigger('click');
- return axios.waitFor(markdownPreviewPath).then(() => {
- expect(subject.find('.md-preview-holder').element.innerHTML).toContain(previewHTML);
- expect(renderGFMSpy).toHaveBeenCalled();
- });
+ await axios.waitFor(markdownPreviewPath);
+ expect(subject.find('.md-preview-holder').element.innerHTML).toContain(previewHTML);
+ expect(renderGFMSpy).toHaveBeenCalled();
});
- it('calls video.pause() on comment input when isSubmitting is changed to true', () => {
+ it('calls video.pause() on comment input when isSubmitting is changed to true', async () => {
previewLink = getPreviewLink();
previewLink.trigger('click');
- let callPause;
-
- return axios
- .waitFor(markdownPreviewPath)
- .then(() => {
- const video = getVideo();
- callPause = jest.spyOn(video.element, 'pause').mockImplementation(() => true);
+ await axios.waitFor(markdownPreviewPath);
+ const video = getVideo();
+ const callPause = jest.spyOn(video.element, 'pause').mockImplementation(() => true);
- subject.setProps({ isSubmitting: true });
+ subject.setProps({ isSubmitting: true });
- return subject.vm.$nextTick();
- })
- .then(() => {
- expect(callPause).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(callPause).toHaveBeenCalled();
});
it('clicking already active write or preview link does nothing', async () => {
@@ -159,56 +147,53 @@ describe('Markdown field component', () => {
previewLink = getPreviewLink();
writeLink.trigger('click');
- await subject.vm.$nextTick();
+ await nextTick();
assertMarkdownTabs(true, writeLink, previewLink, subject);
writeLink.trigger('click');
- await subject.vm.$nextTick();
+ await nextTick();
assertMarkdownTabs(true, writeLink, previewLink, subject);
previewLink.trigger('click');
- await subject.vm.$nextTick();
+ await nextTick();
assertMarkdownTabs(false, writeLink, previewLink, subject);
previewLink.trigger('click');
- await subject.vm.$nextTick();
+ await nextTick();
assertMarkdownTabs(false, writeLink, previewLink, subject);
});
});
describe('markdown buttons', () => {
- it('converts single words', () => {
+ it('converts single words', async () => {
const textarea = subject.find('textarea').element;
textarea.setSelectionRange(0, 7);
const markdownButton = getMarkdownButton();
markdownButton.trigger('click');
- return subject.vm.$nextTick(() => {
- expect(textarea.value).toContain('**testing**');
- });
+ await nextTick();
+ expect(textarea.value).toContain('**testing**');
});
- it('converts a line', () => {
+ it('converts a line', async () => {
const textarea = subject.find('textarea').element;
textarea.setSelectionRange(0, 0);
const markdownButton = getAllMarkdownButtons().wrappers[5];
markdownButton.trigger('click');
- return subject.vm.$nextTick(() => {
- expect(textarea.value).toContain('- testing');
- });
+ await nextTick();
+ expect(textarea.value).toContain('- testing');
});
- it('converts multiple lines', () => {
+ it('converts multiple lines', async () => {
const textarea = subject.find('textarea').element;
textarea.setSelectionRange(0, 50);
const markdownButton = getAllMarkdownButtons().wrappers[5];
markdownButton.trigger('click');
- return subject.vm.$nextTick(() => {
- expect(textarea.value).toContain('- testing\n- 123');
- });
+ await nextTick();
+ expect(textarea.value).toContain('- testing\n- 123');
});
});
@@ -229,7 +214,7 @@ describe('Markdown field component', () => {
// Do something to trigger rerendering the class
subject.setProps({ wrapperClasses: 'foo' });
- await subject.vm.$nextTick();
+ await nextTick();
});
it('should have rerendered classes and kept gfm-form', () => {
diff --git a/spec/frontend/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js
index fec6abc9639..70ee72b7e2c 100644
--- a/spec/frontend/vue_shared/components/markdown/header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/header_spec.js
@@ -1,5 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import $ from 'jquery';
+import { nextTick } from 'vue';
import HeaderComponent from '~/vue_shared/components/markdown/header.vue';
import ToolbarButton from '~/vue_shared/components/markdown/toolbar_button.vue';
@@ -84,20 +85,16 @@ describe('Markdown field header component', () => {
expect(wrapper.find('li:nth-child(2)').classes()).toContain('active');
});
- it('emits toggle markdown event when clicking preview', () => {
+ it('emits toggle markdown event when clicking preview', async () => {
wrapper.find('.js-preview-link').trigger('click');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- expect(wrapper.emitted('preview-markdown').length).toEqual(1);
+ await nextTick();
+ expect(wrapper.emitted('preview-markdown').length).toEqual(1);
- wrapper.find('.js-write-link').trigger('click');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.emitted('write-markdown').length).toEqual(1);
- });
+ wrapper.find('.js-write-link').trigger('click');
+
+ await nextTick();
+ expect(wrapper.emitted('write-markdown').length).toEqual(1);
});
it('does not emit toggle markdown event when triggered from another form', () => {
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
index 9bc2aad1895..9944267cf24 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
@@ -1,5 +1,6 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ApplySuggestion from '~/vue_shared/components/markdown/apply_suggestion.vue';
import SuggestionDiffHeader from '~/vue_shared/components/markdown/suggestion_diff_header.vue';
@@ -103,15 +104,14 @@ describe('Suggestion Diff component', () => {
expect(wrapper.text()).toContain('Applying suggestion...');
});
- it('when callback of apply is called, hides loading', () => {
+ it('when callback of apply is called, hides loading', async () => {
const [callback] = wrapper.emitted().apply[0];
callback();
- return wrapper.vm.$nextTick().then(() => {
- expect(findApplyButton().exists()).toBe(true);
- expect(findLoading().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findApplyButton().exists()).toBe(true);
+ expect(findLoading().exists()).toBe(false);
});
});
diff --git a/spec/frontend/vue_shared/components/modal_copy_button_spec.js b/spec/frontend/vue_shared/components/modal_copy_button_spec.js
index adb72c3ef85..b57efc88d57 100644
--- a/spec/frontend/vue_shared/components/modal_copy_button_spec.js
+++ b/spec/frontend/vue_shared/components/modal_copy_button_spec.js
@@ -1,4 +1,5 @@
import { shallowMount, createWrapper } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
@@ -20,7 +21,7 @@ describe('modal copy button', () => {
});
describe('clipboard', () => {
- it('should fire a `success` event on click', () => {
+ it('should fire a `success` event on click', async () => {
const root = createWrapper(wrapper.vm.$root);
document.execCommand = jest.fn(() => true);
window.getSelection = jest.fn(() => ({
@@ -29,20 +30,18 @@ describe('modal copy button', () => {
}));
wrapper.trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().success).not.toBeEmpty();
- expect(document.execCommand).toHaveBeenCalledWith('copy');
- expect(root.emitted(BV_HIDE_TOOLTIP)).toEqual([['test-id']]);
- });
+ await nextTick();
+ expect(wrapper.emitted().success).not.toBeEmpty();
+ expect(document.execCommand).toHaveBeenCalledWith('copy');
+ expect(root.emitted(BV_HIDE_TOOLTIP)).toEqual([['test-id']]);
});
- it("should propagate the clipboard error event if execCommand doesn't work", () => {
+ it("should propagate the clipboard error event if execCommand doesn't work", async () => {
document.execCommand = jest.fn(() => false);
wrapper.trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().error).not.toBeEmpty();
- expect(document.execCommand).toHaveBeenCalledWith('copy');
- });
+ await nextTick();
+ expect(wrapper.emitted().error).not.toBeEmpty();
+ expect(document.execCommand).toHaveBeenCalledWith('copy');
});
});
});
diff --git a/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js b/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js
index 835759b1f20..accbf14572d 100644
--- a/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js
+++ b/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js
@@ -1,5 +1,6 @@
import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import NoteableWarning from '~/vue_shared/components/notes/noteable_warning.vue';
describe('Issue Warning Component', () => {
@@ -64,7 +65,7 @@ describe('Issue Warning Component', () => {
expect(findConfidentialBlock().exists()).toBe(true);
expect(findConfidentialBlock().element).toMatchSnapshot();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findConfidentialBlock(wrapper).text()).toContain('This is a confidential issue.');
});
@@ -154,15 +155,15 @@ describe('Issue Warning Component', () => {
noteableType: 'Epic',
});
- await wrapperLocked.vm.$nextTick();
+ await nextTick();
expect(findLockedBlock(wrapperLocked).text()).toContain('This epic is locked.');
- await wrapperConfidential.vm.$nextTick();
+ await nextTick();
expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
'This is a confidential epic.',
);
- await wrapperLockedAndConfidential.vm.$nextTick();
+ await nextTick();
expect(findLockedAndConfidentialBlock(wrapperLockedAndConfidential).text()).toContain(
'This epic is confidential and locked.',
);
@@ -179,15 +180,15 @@ describe('Issue Warning Component', () => {
noteableType: 'MergeRequest',
});
- await wrapperLocked.vm.$nextTick();
+ await nextTick();
expect(findLockedBlock(wrapperLocked).text()).toContain('This merge request is locked.');
- await wrapperConfidential.vm.$nextTick();
+ await nextTick();
expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
'This is a confidential merge request.',
);
- await wrapperLockedAndConfidential.vm.$nextTick();
+ await nextTick();
expect(findLockedAndConfidentialBlock(wrapperLockedAndConfidential).text()).toContain(
'This merge request is confidential and locked.',
);
diff --git a/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js
index b330b4f5657..36050a42da7 100644
--- a/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js
+++ b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js
@@ -1,5 +1,6 @@
import { GlAlert, GlBadge, GlPagination, GlTabs, GlTab } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import Tracking from '~/tracking';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
@@ -219,21 +220,21 @@ describe('AlertManagementEmptyState', () => {
it('returns prevPage button', async () => {
findPagination().vm.$emit('input', 3);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findPagination().findAll('.page-item').at(0).text()).toBe('Prev');
});
it('returns prevPage number', async () => {
findPagination().vm.$emit('input', 3);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.previousPage).toBe(2);
});
it('returns 0 when it is the first page', async () => {
findPagination().vm.$emit('input', 1);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.previousPage).toBe(0);
});
});
@@ -242,7 +243,7 @@ describe('AlertManagementEmptyState', () => {
it('returns nextPage button', async () => {
findPagination().vm.$emit('input', 3);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findPagination().findAll('.page-item').at(1).text()).toBe('Next');
});
@@ -257,14 +258,14 @@ describe('AlertManagementEmptyState', () => {
});
findPagination().vm.$emit('input', 1);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.nextPage).toBe(2);
});
it('returns `null` when currentPage is already last page', async () => {
findStatusTabs().vm.$emit('input', 1);
findPagination().vm.$emit('input', 1);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.nextPage).toBeNull();
});
});
@@ -319,7 +320,7 @@ describe('AlertManagementEmptyState', () => {
searchTerm,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.filteredSearchValue).toEqual([searchTerm]);
});
diff --git a/spec/frontend/vue_shared/components/project_avatar/default_spec.js b/spec/frontend/vue_shared/components/project_avatar/default_spec.js
index 84dad2374cb..d042db6051c 100644
--- a/spec/frontend/vue_shared/components/project_avatar/default_spec.js
+++ b/spec/frontend/vue_shared/components/project_avatar/default_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import { projectData } from 'jest/ide/mock_data';
import { TEST_HOST } from 'spec/test_constants';
@@ -19,7 +19,7 @@ describe('ProjectAvatarDefault component', () => {
vm.$destroy();
});
- it('renders identicon if project has no avatar_url', (done) => {
+ it('renders identicon if project has no avatar_url', async () => {
const expectedText = getFirstCharacterCapitalized(projectData.name);
vm.project = {
@@ -27,18 +27,14 @@ describe('ProjectAvatarDefault component', () => {
avatar_url: null,
};
- vm.$nextTick()
- .then(() => {
- const identiconEl = vm.$el.querySelector('.identicon');
+ await nextTick();
+ const identiconEl = vm.$el.querySelector('.identicon');
- expect(identiconEl).not.toBe(null);
- expect(identiconEl.textContent.trim()).toEqual(expectedText);
- })
- .then(done)
- .catch(done.fail);
+ expect(identiconEl).not.toBe(null);
+ expect(identiconEl.textContent.trim()).toEqual(expectedText);
});
- it('renders avatar image if project has avatar_url', (done) => {
+ it('renders avatar image if project has avatar_url', async () => {
const avatarUrl = `${TEST_HOST}/images/home/nasa.svg`;
vm.project = {
@@ -46,13 +42,9 @@ describe('ProjectAvatarDefault component', () => {
avatar_url: avatarUrl,
};
- vm.$nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.avatar')).not.toBeNull();
- expect(vm.$el.querySelector('.identicon')).toBeNull();
- expect(vm.$el.querySelector('img')).toHaveAttr('src', avatarUrl);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.querySelector('.avatar')).not.toBeNull();
+ expect(vm.$el.querySelector('.identicon')).toBeNull();
+ expect(vm.$el.querySelector('img')).toHaveAttr('src', avatarUrl);
});
});
diff --git a/spec/frontend/vue_shared/components/project_selector/project_selector_spec.js b/spec/frontend/vue_shared/components/project_selector/project_selector_spec.js
index 34cee10392d..379e60c1b2d 100644
--- a/spec/frontend/vue_shared/components/project_selector/project_selector_spec.js
+++ b/spec/frontend/vue_shared/components/project_selector/project_selector_spec.js
@@ -1,7 +1,7 @@
import { GlSearchBoxByType, GlInfiniteScroll } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { head } from 'lodash';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import mockProjects from 'test_fixtures_static/projects.json';
import { trimText } from 'helpers/text_helper';
import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue';
@@ -77,39 +77,36 @@ describe('ProjectSelector component', () => {
expect(vm.$emit).toHaveBeenCalledWith('projectClicked', head(searchResults));
});
- it(`shows a "no results" message if showNoResultsMessage === true`, () => {
+ it(`shows a "no results" message if showNoResultsMessage === true`, async () => {
wrapper.setProps({ showNoResultsMessage: true });
- return vm.$nextTick().then(() => {
- const noResultsEl = wrapper.find('.js-no-results-message');
+ await nextTick();
+ const noResultsEl = wrapper.find('.js-no-results-message');
- expect(noResultsEl.exists()).toBe(true);
- expect(trimText(noResultsEl.text())).toEqual('Sorry, no projects matched your search');
- });
+ expect(noResultsEl.exists()).toBe(true);
+ expect(trimText(noResultsEl.text())).toEqual('Sorry, no projects matched your search');
});
- it(`shows a "minimum search query" message if showMinimumSearchQueryMessage === true`, () => {
+ it(`shows a "minimum search query" message if showMinimumSearchQueryMessage === true`, async () => {
wrapper.setProps({ showMinimumSearchQueryMessage: true });
- return vm.$nextTick().then(() => {
- const minimumSearchEl = wrapper.find('.js-minimum-search-query-message');
+ await nextTick();
+ const minimumSearchEl = wrapper.find('.js-minimum-search-query-message');
- expect(minimumSearchEl.exists()).toBe(true);
- expect(trimText(minimumSearchEl.text())).toEqual('Enter at least three characters to search');
- });
+ expect(minimumSearchEl.exists()).toBe(true);
+ expect(trimText(minimumSearchEl.text())).toEqual('Enter at least three characters to search');
});
- it(`shows a error message if showSearchErrorMessage === true`, () => {
+ it(`shows a error message if showSearchErrorMessage === true`, async () => {
wrapper.setProps({ showSearchErrorMessage: true });
- return vm.$nextTick().then(() => {
- const errorMessageEl = wrapper.find('.js-search-error-message');
+ await nextTick();
+ const errorMessageEl = wrapper.find('.js-search-error-message');
- expect(errorMessageEl.exists()).toBe(true);
- expect(trimText(errorMessageEl.text())).toEqual(
- 'Something went wrong, unable to search projects',
- );
- });
+ expect(errorMessageEl.exists()).toBe(true);
+ expect(trimText(errorMessageEl.text())).toEqual(
+ 'Something went wrong, unable to search projects',
+ );
});
describe('the search results legend', () => {
@@ -121,7 +118,7 @@ describe('ProjectSelector component', () => {
${2} | ${3} | ${'Showing 2 of 3 projects'}
`(
'is "$expected" given $count results are showing out of $total',
- ({ count, total, expected }) => {
+ async ({ count, total, expected }) => {
search('gitlab ui');
wrapper.setProps({
@@ -129,9 +126,8 @@ describe('ProjectSelector component', () => {
totalResults: total,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findLegendText()).toBe(expected);
- });
+ await nextTick();
+ expect(findLegendText()).toBe(expected);
},
);
diff --git a/spec/frontend/vue_shared/components/registry/list_item_spec.js b/spec/frontend/vue_shared/components/registry/list_item_spec.js
index ca4bf0b0652..1b93292e37b 100644
--- a/spec/frontend/vue_shared/components/registry/list_item_spec.js
+++ b/spec/frontend/vue_shared/components/registry/list_item_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import component from '~/vue_shared/components/registry/list_item.vue';
describe('list item', () => {
@@ -70,10 +71,10 @@ describe('list item', () => {
it('are visible when details is shown', async () => {
mountComponent({}, slotMocks);
- await wrapper.vm.$nextTick();
+ await nextTick();
findToggleDetailsButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
slotNames.forEach((name) => {
expect(findDetailsSlot(name).exists()).toBe(true);
});
@@ -90,7 +91,7 @@ describe('list item', () => {
describe('details toggle button', () => {
it('is visible when at least one details slot exists', async () => {
mountComponent({}, { 'details-foo': '<span></span>' });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findToggleDetailsButton().exists()).toBe(true);
});
diff --git a/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js b/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js
index 79e41ed0c9e..d4b43caa7b2 100644
--- a/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js
@@ -1,5 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import CollapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
@@ -61,7 +62,7 @@ describe('CollapsedCalendarIcon', () => {
it('emits click event when container is clicked', async () => {
wrapper.trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('click')[0]).toBeDefined();
});
diff --git a/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js b/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js
index 263d1e9d947..ae05d85f74f 100644
--- a/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js
@@ -1,5 +1,6 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DatePicker from '~/vue_shared/components/pikaday.vue';
import SidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue';
@@ -79,7 +80,7 @@ describe('SidebarDatePicker', () => {
it('should enable editing when edit button is clicked', async () => {
findEditButton().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.editing).toBe(true);
});
diff --git a/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js b/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js
index 5336ecc614c..f213e37cbc1 100644
--- a/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js
@@ -10,6 +10,7 @@ import {
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import axios from '~/lib/utils/axios_utils';
import IssuableMoveDropdown from '~/vue_shared/components/sidebar/issuable_move_dropdown.vue';
@@ -74,7 +75,7 @@ describe('IssuableMoveDropdown', () => {
searchKey: 'foo',
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.fetchProjects).toHaveBeenCalledWith('foo');
});
@@ -151,7 +152,7 @@ describe('IssuableMoveDropdown', () => {
selectedProject,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.isSelectedProject(project)).toBe(returnValue);
},
@@ -164,7 +165,7 @@ describe('IssuableMoveDropdown', () => {
selectedProject: null,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.isSelectedProject(mockProjects[0])).toBe(false);
});
@@ -218,7 +219,7 @@ describe('IssuableMoveDropdown', () => {
projectsListLoading: true,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findDropdownEl().find(GlLoadingIcon).exists()).toBe(true);
});
@@ -231,7 +232,7 @@ describe('IssuableMoveDropdown', () => {
selectedProject: mockProjects[0],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
const dropdownItems = wrapper.findAll(GlDropdownItem);
@@ -251,7 +252,7 @@ describe('IssuableMoveDropdown', () => {
});
// Wait for `searchKey` watcher to run.
- await wrapper.vm.$nextTick();
+ await nextTick();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -260,7 +261,7 @@ describe('IssuableMoveDropdown', () => {
projectsListLoading: false,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
const dropdownContentEl = wrapper.find('[data-testid="content"]');
@@ -276,7 +277,7 @@ describe('IssuableMoveDropdown', () => {
projectsListLoadFailed: true,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
const dropdownContentEl = wrapper.find('[data-testid="content"]');
@@ -295,7 +296,7 @@ describe('IssuableMoveDropdown', () => {
selectedProject: mockProjects[0],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(
wrapper.find('[data-testid="footer"]').find(GlButton).attributes('disabled'),
@@ -352,7 +353,7 @@ describe('IssuableMoveDropdown', () => {
projects: mockProjects,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
wrapper.findAll(GlDropdownItem).at(0).vm.$emit('click', mockEvent);
@@ -366,7 +367,7 @@ describe('IssuableMoveDropdown', () => {
selectedProject: mockProjects[0],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
wrapper.find('[data-testid="footer"]').find(GlButton).vm.$emit('click');
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js
index c4ed975e746..c05513a6d5f 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js
@@ -1,6 +1,6 @@
import { GlIcon, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import DropdownButton from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_button.vue';
@@ -71,13 +71,12 @@ describe('DropdownButton', () => {
expect(dropdownTextEl.text()).toBe('Label');
});
- it('renders provided button text element', () => {
+ it('renders provided button text element', async () => {
store.state.dropdownButtonText = 'Custom label';
const dropdownTextEl = findDropdownText();
- return wrapper.vm.$nextTick().then(() => {
- expect(dropdownTextEl.text()).toBe('Custom label');
- });
+ await nextTick();
+ expect(dropdownTextEl.text()).toBe('Custom label');
});
it('renders chevron icon element', () => {
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js
index 0eff6a1dace..0673ffee22b 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js
@@ -1,6 +1,6 @@
import { GlButton, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import DropdownContentsCreateView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue';
@@ -42,7 +42,7 @@ describe('DropdownContentsCreateView', () => {
expect(wrapper.vm.disableCreate).toBe(true);
});
- it('returns `true` when `labelCreateInProgress` is true', () => {
+ it('returns `true` when `labelCreateInProgress` is true', async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
@@ -51,12 +51,11 @@ describe('DropdownContentsCreateView', () => {
});
wrapper.vm.$store.dispatch('requestCreateLabel');
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.disableCreate).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.vm.disableCreate).toBe(true);
});
- it('returns `false` when label title and color is defined and create request is not already in progress', () => {
+ it('returns `false` when label title and color is defined and create request is not already in progress', async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
@@ -64,9 +63,8 @@ describe('DropdownContentsCreateView', () => {
selectedColor: '#ff0000',
});
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.disableCreate).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.vm.disableCreate).toBe(false);
});
});
@@ -101,7 +99,7 @@ describe('DropdownContentsCreateView', () => {
});
describe('handleCreateClick', () => {
- it('calls action `createLabel` with object containing `labelTitle` & `selectedColor`', () => {
+ it('calls action `createLabel` with object containing `labelTitle` & `selectedColor`', async () => {
jest.spyOn(wrapper.vm, 'createLabel').mockImplementation();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -112,14 +110,13 @@ describe('DropdownContentsCreateView', () => {
wrapper.vm.handleCreateClick();
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.createLabel).toHaveBeenCalledWith(
- expect.objectContaining({
- title: 'Foo',
- color: '#ff0000',
- }),
- );
- });
+ await nextTick();
+ expect(wrapper.vm.createLabel).toHaveBeenCalledWith(
+ expect.objectContaining({
+ title: 'Foo',
+ color: '#ff0000',
+ }),
+ );
});
});
});
@@ -169,25 +166,22 @@ describe('DropdownContentsCreateView', () => {
});
});
- it('renders color input element', () => {
+ it('renders color input element', async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
selectedColor: '#ff0000',
});
- return wrapper.vm.$nextTick(() => {
- const colorPreviewEl = wrapper.find(
- '.color-input-container > .dropdown-label-color-preview',
- );
- const colorInputEl = wrapper.find('.color-input-container').find(GlFormInput);
+ await nextTick();
+ const colorPreviewEl = wrapper.find('.color-input-container > .dropdown-label-color-preview');
+ const colorInputEl = wrapper.find('.color-input-container').find(GlFormInput);
- expect(colorPreviewEl.exists()).toBe(true);
- expect(colorPreviewEl.attributes('style')).toContain('background-color');
- expect(colorInputEl.exists()).toBe(true);
- expect(colorInputEl.attributes('placeholder')).toBe('Use custom color #FF0000');
- expect(colorInputEl.attributes('value')).toBe('#ff0000');
- });
+ expect(colorPreviewEl.exists()).toBe(true);
+ expect(colorPreviewEl.attributes('style')).toContain('background-color');
+ expect(colorInputEl.exists()).toBe(true);
+ expect(colorInputEl.attributes('placeholder')).toBe('Use custom color #FF0000');
+ expect(colorInputEl.attributes('value')).toBe('#ff0000');
});
it('renders create button element', () => {
@@ -197,15 +191,14 @@ describe('DropdownContentsCreateView', () => {
expect(createBtnEl.text()).toContain('Create');
});
- it('shows gl-loading-icon within create button element when `labelCreateInProgress` is `true`', () => {
+ it('shows gl-loading-icon within create button element when `labelCreateInProgress` is `true`', async () => {
wrapper.vm.$store.dispatch('requestCreateLabel');
- return wrapper.vm.$nextTick(() => {
- const loadingIconEl = wrapper.find('.dropdown-actions').find(GlLoadingIcon);
+ await nextTick();
+ const loadingIconEl = wrapper.find('.dropdown-actions').find(GlLoadingIcon);
- expect(loadingIconEl.exists()).toBe(true);
- expect(loadingIconEl.isVisible()).toBe(true);
- });
+ expect(loadingIconEl.exists()).toBe(true);
+ expect(loadingIconEl.isVisible()).toBe(true);
});
it('renders cancel button element', () => {
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js
index 93a0e2f75bb..42202db4935 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js
@@ -6,7 +6,7 @@ import {
GlLink,
} from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
import DropdownContentsLabelsView from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue';
@@ -114,7 +114,7 @@ describe('DropdownContentsLabelsView', () => {
wrapper.vm.$store.dispatch('receiveLabelsSuccess', labels);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.showNoMatchingResultsMessage).toBe(returnValue);
},
@@ -249,7 +249,7 @@ describe('DropdownContentsLabelsView', () => {
expect(wrapper.vm.toggleDropdownContents).toHaveBeenCalled();
});
- it('calls action `scrollIntoViewIfNeeded` in next tick when any key is pressed', () => {
+ it('calls action `scrollIntoViewIfNeeded` in next tick when any key is pressed', async () => {
jest.spyOn(wrapper.vm, 'scrollIntoViewIfNeeded').mockImplementation();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -261,9 +261,8 @@ describe('DropdownContentsLabelsView', () => {
keyCode: DOWN_KEY_CODE,
});
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.scrollIntoViewIfNeeded).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(wrapper.vm.scrollIntoViewIfNeeded).toHaveBeenCalled();
});
});
@@ -294,15 +293,14 @@ describe('DropdownContentsLabelsView', () => {
expect(wrapper.find(GlIntersectionObserver).exists()).toBe(true);
});
- it('renders gl-loading-icon component when `labelsFetchInProgress` prop is true', () => {
+ it('renders gl-loading-icon component when `labelsFetchInProgress` prop is true', async () => {
wrapper.vm.$store.dispatch('requestLabels');
- return wrapper.vm.$nextTick(() => {
- const loadingIconEl = findLoadingIcon();
+ await nextTick();
+ const loadingIconEl = findLoadingIcon();
- expect(loadingIconEl.exists()).toBe(true);
- expect(loadingIconEl.attributes('class')).toContain('labels-fetch-loading');
- });
+ expect(loadingIconEl.exists()).toBe(true);
+ expect(loadingIconEl.attributes('class')).toContain('labels-fetch-loading');
});
it('renders dropdown title element', () => {
@@ -339,47 +337,44 @@ describe('DropdownContentsLabelsView', () => {
expect(wrapper.findAll(LabelItem)).toHaveLength(mockLabels.length);
});
- it('renders label element with `highlight` set to true when value of `currentHighlightItem` is more than -1', () => {
+ it('renders label element with `highlight` set to true when value of `currentHighlightItem` is more than -1', async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
currentHighlightItem: 0,
});
- return wrapper.vm.$nextTick(() => {
- const labelItemEl = findDropdownContent().find(LabelItem);
+ await nextTick();
+ const labelItemEl = findDropdownContent().find(LabelItem);
- expect(labelItemEl.attributes('highlight')).toBe('true');
- });
+ expect(labelItemEl.attributes('highlight')).toBe('true');
});
- it('renders element containing "No matching results" when `searchKey` does not match with any label', () => {
+ it('renders element containing "No matching results" when `searchKey` does not match with any label', async () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
searchKey: 'abc',
});
- return wrapper.vm.$nextTick(() => {
- const noMatchEl = findDropdownContent().find('li');
+ await nextTick();
+ const noMatchEl = findDropdownContent().find('li');
- expect(noMatchEl.isVisible()).toBe(true);
- expect(noMatchEl.text()).toContain('No matching results');
- });
+ expect(noMatchEl.isVisible()).toBe(true);
+ expect(noMatchEl.text()).toContain('No matching results');
});
- it('renders empty content while loading', () => {
+ it('renders empty content while loading', async () => {
wrapper.vm.$store.state.labelsFetchInProgress = true;
- return wrapper.vm.$nextTick(() => {
- const dropdownContent = findDropdownContent();
- const loadingIcon = findLoadingIcon();
+ await nextTick();
+ const dropdownContent = findDropdownContent();
+ const loadingIcon = findLoadingIcon();
- expect(dropdownContent.exists()).toBe(true);
- expect(dropdownContent.isVisible()).toBe(true);
- expect(loadingIcon.exists()).toBe(true);
- expect(loadingIcon.isVisible()).toBe(true);
- });
+ expect(dropdownContent.exists()).toBe(true);
+ expect(dropdownContent.isVisible()).toBe(true);
+ expect(loadingIcon.exists()).toBe(true);
+ expect(loadingIcon.isVisible()).toBe(true);
});
it('renders footer list items', () => {
@@ -393,14 +388,13 @@ describe('DropdownContentsLabelsView', () => {
expect(manageLabelsLink.text()).toBe('Manage labels');
});
- it('does not render "Create label" footer link when `state.allowLabelCreate` is `false`', () => {
+ it('does not render "Create label" footer link when `state.allowLabelCreate` is `false`', async () => {
wrapper.vm.$store.state.allowLabelCreate = false;
- return wrapper.vm.$nextTick(() => {
- const createLabelLink = findDropdownFooter().findAll(GlLink).at(0);
+ await nextTick();
+ const createLabelLink = findDropdownFooter().findAll(GlLink).at(0);
- expect(createLabelLink.text()).not.toBe('Create label');
- });
+ expect(createLabelLink.text()).not.toBe('Create label');
});
it('does not render footer list items when `state.variant` is "standalone"', () => {
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js
index 110c1d1b7eb..84e9f3f41c3 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js
@@ -1,6 +1,6 @@
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import DropdownTitle from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue';
@@ -47,14 +47,13 @@ describe('DropdownTitle', () => {
expect(editBtnEl.text()).toBe('Edit');
});
- it('renders loading icon element when `labelsSelectInProgress` prop is true', () => {
+ it('renders loading icon element when `labelsSelectInProgress` prop is true', async () => {
wrapper.setProps({
labelsSelectInProgress: true,
});
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(true);
});
});
});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed_spec.js
index a7f9391cb5f..c6400320dea 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed_spec.js
@@ -1,5 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import DropdownValueCollapsedComponent from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed.vue';
@@ -42,7 +43,7 @@ describe('DropdownValueCollapsedComponent', () => {
wrapper.trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('onValueClick')[0]).toBeDefined();
});
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
index 4b0ba075eda..31819d0e2f7 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_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 { isInViewport } from '~/lib/utils/common_utils';
@@ -139,27 +139,26 @@ describe('LabelsSelectRoot', () => {
${'embedded'} | ${'is-embedded'}
`(
'renders component root element with CSS class `$cssClass` when `state.variant` is "$variant"',
- ({ variant, cssClass }) => {
+ async ({ variant, cssClass }) => {
createComponent({
...mockConfig,
variant,
});
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.classes()).toContain(cssClass);
- });
+ await nextTick();
+ expect(wrapper.classes()).toContain(cssClass);
},
);
it('renders `dropdown-value-collapsed` component when `allowLabelCreate` prop is `true`', async () => {
createComponent();
- await wrapper.vm.$nextTick;
+ await nextTick;
expect(wrapper.find(DropdownValueCollapsed).exists()).toBe(true);
});
it('renders `dropdown-title` component', async () => {
createComponent();
- await wrapper.vm.$nextTick;
+ await nextTick;
expect(wrapper.find(DropdownTitle).exists()).toBe(true);
});
@@ -167,7 +166,7 @@ describe('LabelsSelectRoot', () => {
createComponent(mockConfig, {
default: 'None',
});
- await wrapper.vm.$nextTick;
+ await nextTick;
const valueComp = wrapper.find(DropdownValue);
@@ -178,14 +177,14 @@ describe('LabelsSelectRoot', () => {
it('renders `dropdown-button` component when `showDropdownButton` prop is `true`', async () => {
createComponent();
wrapper.vm.$store.dispatch('toggleDropdownButton');
- await wrapper.vm.$nextTick;
+ await nextTick;
expect(wrapper.find(DropdownButton).exists()).toBe(true);
});
it('renders `dropdown-contents` component when `showDropdownButton` & `showDropdownContents` prop is `true`', async () => {
createComponent();
wrapper.vm.$store.dispatch('toggleDropdownContents');
- await wrapper.vm.$nextTick;
+ await nextTick;
expect(wrapper.find(DropdownContents).exists()).toBe(true);
});
@@ -198,22 +197,20 @@ describe('LabelsSelectRoot', () => {
wrapper.vm.$store.dispatch('toggleDropdownContents');
});
- it('set direction when out of viewport', () => {
+ it('set direction when out of viewport', async () => {
isInViewport.mockImplementation(() => false);
wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(true);
});
- it('does not set direction when inside of viewport', () => {
+ it('does not set direction when inside of viewport', async () => {
isInViewport.mockImplementation(() => true);
wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(false);
});
},
);
diff --git a/spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js b/spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js
index a6c9bda1aa2..267a467059d 100644
--- a/spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js
@@ -1,6 +1,7 @@
import { GlButton } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import ToggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue';
describe('ToggleSidebar', () => {
@@ -38,7 +39,7 @@ describe('ToggleSidebar', () => {
createComponent({ mountFn: mount });
findGlButton().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('toggle')[0]).toBeDefined();
});
diff --git a/spec/frontend/vue_shared/components/split_button_spec.js b/spec/frontend/vue_shared/components/split_button_spec.js
index ad11e6519c4..4965969bc3e 100644
--- a/spec/frontend/vue_shared/components/split_button_spec.js
+++ b/spec/frontend/vue_shared/components/split_button_spec.js
@@ -1,6 +1,7 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import SplitButton from '~/vue_shared/components/split_button.vue';
const mockActionItems = [
@@ -27,15 +28,15 @@ describe('SplitButton', () => {
const findDropdown = () => wrapper.find(GlDropdown);
const findDropdownItem = (index = 0) => findDropdown().findAll(GlDropdownItem).at(index);
- const selectItem = (index) => {
+ const selectItem = async (index) => {
findDropdownItem(index).vm.$emit('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
};
- const clickToggleButton = () => {
+ const clickToggleButton = async () => {
findDropdown().vm.$emit('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
};
it('fails for empty actionItems', () => {
diff --git a/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js b/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js
index b3cdbccb271..2f5afeec1fc 100644
--- a/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js
+++ b/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js
@@ -1,5 +1,6 @@
import { GlIcon, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
jest.mock('~/flash');
@@ -84,47 +85,40 @@ describe('Upload dropzone component', () => {
${'contains text'} | ${mockDragEvent({ types: ['text'] })}
${'contains files and text'} | ${mockDragEvent({ types: ['Files', 'text'] })}
${'contains files'} | ${mockDragEvent({ types: ['Files'] })}
- `('renders correct template when drag event $description', ({ eventPayload }) => {
+ `('renders correct template when drag event $description', async ({ eventPayload }) => {
createComponent();
wrapper.trigger('dragenter', eventPayload);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(wrapper.element).toMatchSnapshot();
});
- it('renders correct template when dragging stops', () => {
+ it('renders correct template when dragging stops', async () => {
createComponent();
wrapper.trigger('dragenter');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- wrapper.trigger('dragleave');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
+
+ await nextTick();
+ wrapper.trigger('dragleave');
+
+ await nextTick();
+ expect(wrapper.element).toMatchSnapshot();
});
});
describe('when dropping', () => {
- it('emits upload event', () => {
+ it('emits upload event', async () => {
createComponent();
const mockFile = { name: 'test', type: 'image/jpg' };
const mockEvent = mockDragEvent({ files: [mockFile] });
wrapper.trigger('dragenter', mockEvent);
- return wrapper.vm
- .$nextTick()
- .then(() => {
- wrapper.trigger('drop', mockEvent);
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(wrapper.emitted().change[0]).toEqual([[mockFile]]);
- });
+
+ await nextTick();
+ wrapper.trigger('drop', mockEvent);
+
+ await nextTick();
+ expect(wrapper.emitted().change[0]).toEqual([[mockFile]]);
});
});
diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js
index 1d15da491cd..66bb234aef6 100644
--- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js
+++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { TEST_HOST } from 'spec/test_constants';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue';
@@ -142,14 +143,13 @@ describe('UserAvatarList', () => {
expect(links.length).toEqual(props.items.length);
});
- it('with collapse clicked, it renders avatars up to breakpoint', () => {
+ it('with collapse clicked, it renders avatars up to breakpoint', async () => {
clickButton();
- return wrapper.vm.$nextTick(() => {
- const links = wrapper.findAll(UserAvatarLink);
+ await nextTick();
+ const links = wrapper.findAll(UserAvatarLink);
- expect(links.length).toEqual(TEST_BREAKPOINT);
- });
+ expect(links.length).toEqual(TEST_BREAKPOINT);
});
});
});
diff --git a/spec/frontend/vue_shared/components/web_ide_link_spec.js b/spec/frontend/vue_shared/components/web_ide_link_spec.js
index 659d93d6597..9b7c594b910 100644
--- a/spec/frontend/vue_shared/components/web_ide_link_spec.js
+++ b/spec/frontend/vue_shared/components/web_ide_link_spec.js
@@ -213,7 +213,7 @@ describe('Web IDE link component', () => {
findLocalStorageSync().vm.$emit('input', ACTION_GITPOD.key);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findActionsButton().props('selectedKey')).toBe(ACTION_GITPOD.key);
});
@@ -223,7 +223,7 @@ describe('Web IDE link component', () => {
findActionsButton().vm.$emit('select', ACTION_GITPOD.key);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findActionsButton().props('selectedKey')).toBe(ACTION_GITPOD.key);
expect(findLocalStorageSync().props('value')).toBe(ACTION_GITPOD.key);
diff --git a/spec/frontend/vue_shared/directives/track_event_spec.js b/spec/frontend/vue_shared/directives/track_event_spec.js
index b3f94d0242a..4bf84b06246 100644
--- a/spec/frontend/vue_shared/directives/track_event_spec.js
+++ b/spec/frontend/vue_shared/directives/track_event_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Tracking from '~/tracking';
import TrackEvent from '~/vue_shared/directives/track_event';
@@ -31,7 +31,7 @@ describe('Error Tracking directive', () => {
expect(Tracking.event).not.toHaveBeenCalled();
});
- it('should track event on click if tracking info provided', () => {
+ it('should track event on click if tracking info provided', async () => {
const trackingOptions = {
category: 'Tracking',
action: 'click_trackable_btn',
@@ -43,9 +43,8 @@ describe('Error Tracking directive', () => {
wrapper.setData({ trackingOptions });
const { category, action, label, property, value } = trackingOptions;
- return wrapper.vm.$nextTick(() => {
- button.trigger('click');
- expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property, value });
- });
+ await nextTick();
+ button.trigger('click');
+ expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property, value });
});
});
diff --git a/spec/frontend/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar_spec.js b/spec/frontend/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar_spec.js
index 0f33a3d1122..7dfeced571a 100644
--- a/spec/frontend/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar_spec.js
+++ b/spec/frontend/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar_spec.js
@@ -1,5 +1,6 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import IssuableBulkEditSidebar from '~/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar.vue';
const createComponent = ({ expanded = true } = {}) =>
@@ -48,7 +49,7 @@ describe('IssuableBulkEditSidebar', () => {
expanded,
});
- await wrappeCustom.vm.$nextTick();
+ await nextTick();
expect(document.querySelector('.layout-page').classList.contains(layoutPageClass)).toBe(
true,
@@ -78,7 +79,7 @@ describe('IssuableBulkEditSidebar', () => {
expanded,
});
- await wrappeCustom.vm.$nextTick();
+ await nextTick();
expect(wrappeCustom.classes()).toContain(layoutPageClass);
diff --git a/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js b/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js
index e38a80e7734..0585b6c55fd 100644
--- a/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js
+++ b/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js
@@ -1,4 +1,5 @@
import { GlLink, GlLabel, GlIcon, GlFormCheckbox, GlSprintf } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { useFakeDate } from 'helpers/fake_date';
import { shallowMountExtended as shallowMount } from 'helpers/vue_test_utils_helper';
import IssuableItem from '~/vue_shared/issuable/list/components/issuable_item.vue';
@@ -76,7 +77,7 @@ describe('IssuableItem', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.authorId).toBe(returnValue);
},
@@ -100,7 +101,7 @@ describe('IssuableItem', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.isIssuableUrlExternal).toBe(returnValue);
},
@@ -122,7 +123,7 @@ describe('IssuableItem', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.labels).toEqual(mockLabels);
});
@@ -135,7 +136,7 @@ describe('IssuableItem', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.labels).toEqual([]);
});
@@ -224,7 +225,7 @@ describe('IssuableItem', () => {
enableLabelPermalinks: false,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.labelTarget(mockRegularLabel)).toBe('#');
});
@@ -248,7 +249,7 @@ describe('IssuableItem', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
const titleEl = wrapper.find('[data-testid="issuable-title"]');
@@ -264,7 +265,7 @@ describe('IssuableItem', () => {
showCheckbox: true,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlFormCheckbox).exists()).toBe(true);
expect(wrapper.find(GlFormCheckbox).attributes('checked')).not.toBeDefined();
@@ -273,7 +274,7 @@ describe('IssuableItem', () => {
checked: true,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlFormCheckbox).attributes('checked')).toBe('true');
});
@@ -286,7 +287,7 @@ describe('IssuableItem', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('[data-testid="issuable-title"]').find(GlLink).attributes('target')).toBe(
'_blank',
@@ -301,7 +302,7 @@ describe('IssuableItem', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
const confidentialEl = wrapper.find('[data-testid="issuable-title"]').find(GlIcon);
diff --git a/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js b/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js
index 14e93108447..64823cd4c6c 100644
--- a/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js
+++ b/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js
@@ -2,6 +2,7 @@ import { GlAlert, GlKeysetPagination, GlSkeletonLoading, GlPagination } from '@g
import { shallowMount } from '@vue/test-utils';
import VueDraggable from 'vuedraggable';
+import { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import IssuableItem from '~/vue_shared/issuable/list/components/issuable_item.vue';
@@ -77,7 +78,7 @@ describe('IssuableListRoot', () => {
currentPage,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.skeletonItemCount).toBe(returnValue);
},
@@ -96,7 +97,7 @@ describe('IssuableListRoot', () => {
issuables,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -104,7 +105,7 @@ describe('IssuableListRoot', () => {
checkedIssuables,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.allIssuablesChecked).toBe(returnValue);
},
@@ -119,7 +120,7 @@ describe('IssuableListRoot', () => {
checkedIssuables: mockCheckedIssuables,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.bulkEditIssuables).toHaveLength(mIssuables.length);
});
@@ -137,7 +138,7 @@ describe('IssuableListRoot', () => {
issuables: [mockIssuables[0]],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(Object.keys(wrapper.vm.checkedIssuables)).toHaveLength(1);
expect(wrapper.vm.checkedIssuables[mockIssuables[0].iid]).toEqual({
@@ -160,7 +161,7 @@ describe('IssuableListRoot', () => {
urlParams,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(global.window.location.href).toBe(
`${TEST_HOST}/?state=${urlParams.state}&sort=${urlParams.sort}&page=${urlParams.page}&search=${urlParams.search}`,
@@ -192,7 +193,7 @@ describe('IssuableListRoot', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.issuableChecked(mockIssuables[0])).toBe(true);
});
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_body_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_body_spec.js
index 41bacf18a68..1a93838b03f 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_body_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_body_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { useFakeDate } from 'helpers/fake_date';
import IssuableBody from '~/vue_shared/issuable/show/components/issuable_body.vue';
@@ -68,7 +69,7 @@ describe('IssuableBody', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.isUpdated).toBe(returnValue);
},
@@ -90,13 +91,13 @@ describe('IssuableBody', () => {
editFormVisible: true,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
wrapper.setProps({
editFormVisible: false,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.initTaskList).toHaveBeenCalled();
});
@@ -182,7 +183,7 @@ describe('IssuableBody', () => {
editFormVisible: true,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
const editFormEl = wrapper.find(IssuableEditForm);
expect(editFormEl.exists()).toBe(true);
@@ -221,7 +222,7 @@ describe('IssuableBody', () => {
editFormVisible: true,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
const issuableEditForm = wrapper.find(IssuableEditForm);
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js
index 051ffd27af4..d3e484cf913 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js
@@ -1,6 +1,7 @@
import { GlFormInput } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import IssuableEditForm from '~/vue_shared/issuable/show/components/issuable_edit_form.vue';
import IssuableEventHub from '~/vue_shared/issuable/show/event_hub';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
@@ -52,7 +53,7 @@ describe('IssuableEditForm', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.title).toBe('Foo');
expect(wrapper.vm.description).toBe('Foobar');
@@ -67,7 +68,7 @@ describe('IssuableEditForm', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.title).toBe('');
expect(wrapper.vm.description).toBe('');
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js
index 41735923957..1cdd709159f 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js
@@ -1,5 +1,6 @@
import { GlIcon, GlAvatarLabeled } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import IssuableHeader from '~/vue_shared/issuable/show/components/issuable_header.vue';
@@ -78,7 +79,7 @@ describe('IssuableHeader', () => {
blocked: true,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
const blockedEl = wrapper.findByTestId('blocked');
@@ -91,7 +92,7 @@ describe('IssuableHeader', () => {
confidential: true,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
const confidentialEl = wrapper.findByTestId('confidential');
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js
index cb418371760..93de6dbe306 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js
@@ -1,5 +1,6 @@
import { GlIcon, GlButton, GlIntersectionObserver } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import IssuableTitle from '~/vue_shared/issuable/show/components/issuable_title.vue';
@@ -64,7 +65,7 @@ describe('IssuableTitle', () => {
},
});
- await wrapperWithTitle.vm.$nextTick();
+ await nextTick();
const titleEl = wrapperWithTitle.find('h2');
expect(titleEl.exists()).toBe(true);
@@ -90,7 +91,7 @@ describe('IssuableTitle', () => {
stickyTitleVisible: true,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
const stickyHeaderEl = wrapper.find('[data-testid="header"]');
expect(stickyHeaderEl.exists()).toBe(true);
diff --git a/spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js b/spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js
index 788ba70ddc0..1ef1304185d 100644
--- a/spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js
+++ b/spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js
@@ -1,5 +1,6 @@
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import Cookies from 'js-cookie';
+import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import IssuableSidebarRoot from '~/vue_shared/issuable/sidebar/components/issuable_sidebar_root.vue';
@@ -88,7 +89,7 @@ describe('IssuableSidebarRoot', () => {
jest.spyOn(bp, 'isDesktop').mockReturnValue(breakpoint === 'lg' || breakpoint === 'xl');
window.dispatchEvent(new Event('resize'));
- await wrapper.vm.$nextTick();
+ await nextTick();
assertPageLayoutClasses({ isExpanded: isExpandedValue });
},
diff --git a/spec/frontend/vue_shared/new_namespace/components/welcome_spec.js b/spec/frontend/vue_shared/new_namespace/components/welcome_spec.js
index 2d51f6dbeeb..c90131fea9a 100644
--- a/spec/frontend/vue_shared/new_namespace/components/welcome_spec.js
+++ b/spec/frontend/vue_shared/new_namespace/components/welcome_spec.js
@@ -37,9 +37,8 @@ describe('Welcome page', () => {
const link = wrapper.find('a');
link.trigger('click');
await nextTick();
- return wrapper.vm.$nextTick().then(() => {
- expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_tab', { label: 'test' });
- });
+ await nextTick();
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_tab', { label: 'test' });
});
it('renders footer slot if provided', () => {
diff --git a/spec/frontend/work_items/pages/create_work_item_spec.js b/spec/frontend/work_items/pages/create_work_item_spec.js
index 71e153d30c3..00571c1f5c8 100644
--- a/spec/frontend/work_items/pages/create_work_item_spec.js
+++ b/spec/frontend/work_items/pages/create_work_item_spec.js
@@ -19,7 +19,7 @@ describe('Create work item component', () => {
const findCreateButton = () => wrapper.find('[data-testid="create-button"]');
const findCancelButton = () => wrapper.find('[data-testid="cancel-button"]');
- const createComponent = ({ data = {} } = {}) => {
+ const createComponent = ({ data = {}, props = {} } = {}) => {
fakeApollo = createMockApollo([], resolvers);
wrapper = shallowMount(CreateWorkItem, {
apolloProvider: fakeApollo,
@@ -28,6 +28,9 @@ describe('Create work item component', () => {
...data,
};
},
+ propsData: {
+ ...props,
+ },
mocks: {
$router: {
go: jest.fn(),
@@ -54,40 +57,99 @@ describe('Create work item component', () => {
expect(findCreateButton().props('disabled')).toBe(true);
});
- it('redirects to the previous page on Cancel button click', () => {
- createComponent();
- findCancelButton().vm.$emit('click');
+ describe('when displayed on a separate route', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('redirects to the previous page on Cancel button click', () => {
+ findCancelButton().vm.$emit('click');
+
+ expect(wrapper.vm.$router.go).toHaveBeenCalledWith(-1);
+ });
+
+ it('redirects to the work item page on successful mutation', async () => {
+ findTitleInput().vm.$emit('title-input', 'Test title');
+
+ wrapper.find('form').trigger('submit');
+ await waitForPromises();
+
+ expect(wrapper.vm.$router.push).toHaveBeenCalled();
+ });
+
+ it('adds right margin for create button', () => {
+ expect(findCreateButton().classes()).toContain('gl-mr-3');
+ });
+
+ it('does not add right margin for cancel button', () => {
+ expect(findCancelButton().classes()).not.toContain('gl-mr-3');
+ });
+ });
+
+ describe('when displayed in a modal', () => {
+ beforeEach(() => {
+ createComponent({
+ props: {
+ isModal: true,
+ },
+ });
+ });
+
+ it('emits `closeModal` event on Cancel button click', () => {
+ findCancelButton().vm.$emit('click');
- expect(wrapper.vm.$router.go).toHaveBeenCalledWith(-1);
+ expect(wrapper.emitted('closeModal')).toEqual([[]]);
+ });
+
+ it('emits `onCreate` on successful mutation', async () => {
+ const mockTitle = 'Test title';
+ findTitleInput().vm.$emit('title-input', 'Test title');
+
+ wrapper.find('form').trigger('submit');
+ await waitForPromises();
+
+ expect(wrapper.emitted('onCreate')).toEqual([[mockTitle]]);
+ });
+
+ it('does not right margin for create button', () => {
+ expect(findCreateButton().classes()).not.toContain('gl-mr-3');
+ });
+
+ it('adds right margin for cancel button', () => {
+ expect(findCancelButton().classes()).toContain('gl-mr-3');
+ });
});
it('hides the alert on dismissing the error', async () => {
createComponent({ data: { error: true } });
+
expect(findAlert().exists()).toBe(true);
findAlert().vm.$emit('dismiss');
await nextTick();
+
expect(findAlert().exists()).toBe(false);
});
+ it('displays an initial title if passed', () => {
+ const initialTitle = 'Initial Title';
+ createComponent({
+ props: { initialTitle },
+ });
+ expect(findTitleInput().props('initialTitle')).toBe(initialTitle);
+ });
+
describe('when title input field has a text', () => {
- beforeEach(async () => {
+ beforeEach(() => {
const mockTitle = 'Test title';
createComponent();
- await findTitleInput().vm.$emit('title-input', mockTitle);
+ findTitleInput().vm.$emit('title-input', mockTitle);
});
it('renders a non-disabled Create button', () => {
expect(findCreateButton().props('disabled')).toBe(false);
});
- it('redirects to the work item page on successful mutation', async () => {
- wrapper.find('form').trigger('submit');
- await waitForPromises();
-
- expect(wrapper.vm.$router.push).toHaveBeenCalled();
- });
-
// TODO: write a proper test here when we have a backend implementation
it.todo('shows an alert on mutation error');
});
diff --git a/spec/frontend_integration/ide/ide_integration_spec.js b/spec/frontend_integration/ide/ide_integration_spec.js
index 5f1a5b0d048..aad9b9e526c 100644
--- a/spec/frontend_integration/ide/ide_integration_spec.js
+++ b/spec/frontend_integration/ide/ide_integration_spec.js
@@ -1,3 +1,4 @@
+import { nextTick } from 'vue';
import { setTestTimeout } from 'helpers/timeout';
import waitForPromises from 'helpers/wait_for_promises';
import { waitForText } from 'helpers/wait_for_text';
@@ -134,7 +135,7 @@ describe('WebIDE', () => {
describe('when editor position changes', () => {
beforeEach(async () => {
editor.setPosition({ lineNumber: 4, column: 10 });
- await vm.$nextTick();
+ await nextTick();
});
it('shows new line position', () => {
@@ -145,7 +146,7 @@ describe('WebIDE', () => {
it('updates after rename', async () => {
await ideHelper.renameFile('README.md', 'READMEZ.txt');
await ideHelper.waitForEditorModelChange(editor);
- await vm.$nextTick();
+ await nextTick();
expect(statusBar).toHaveText('1:1');
expect(statusBar).toHaveText('plaintext');
@@ -166,7 +167,7 @@ describe('WebIDE', () => {
await ideHelper.closeFile('README.md');
await ideHelper.openFile('README.md');
await ideHelper.waitForMonacoEditor();
- await vm.$nextTick();
+ await nextTick();
expect(statusBar).toHaveText('4:10');
expect(statusBar).toHaveText('markdown');
diff --git a/spec/graphql/types/ci/runner_type_spec.rb b/spec/graphql/types/ci/runner_type_spec.rb
index 3f89058f44a..3047817a952 100644
--- a/spec/graphql/types/ci/runner_type_spec.rb
+++ b/spec/graphql/types/ci/runner_type_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe GitlabSchema.types['CiRunner'] do
it 'contains attributes related to a runner' do
expected_fields = %w[
- id description created_at contacted_at maximum_timeout access_level active status
+ id description created_at contacted_at maximum_timeout access_level active paused status
version short_sha revision locked run_untagged ip_address runner_type tag_list
project_count job_count admin_url edit_admin_url user_permissions executor_name
groups projects
diff --git a/spec/lib/gitlab/background_migration/batching_strategies/backfill_project_namespace_per_group_batching_strategy_spec.rb b/spec/lib/gitlab/background_migration/batching_strategies/backfill_project_namespace_per_group_batching_strategy_spec.rb
new file mode 100644
index 00000000000..7b8a466b37c
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/batching_strategies/backfill_project_namespace_per_group_batching_strategy_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BatchingStrategies::BackfillProjectNamespacePerGroupBatchingStrategy, '#next_batch' do
+ let!(:namespaces) { table(:namespaces) }
+ let!(:projects) { table(:projects) }
+ let!(:background_migrations) { table(:batched_background_migrations) }
+
+ let!(:namespace1) { namespaces.create!(name: 'batchtest1', type: 'Group', path: 'batch-test1') }
+ let!(:namespace2) { namespaces.create!(name: 'batchtest2', type: 'Group', parent_id: namespace1.id, path: 'batch-test2') }
+ let!(:namespace3) { namespaces.create!(name: 'batchtest3', type: 'Group', parent_id: namespace2.id, path: 'batch-test3') }
+
+ let!(:project1) { projects.create!(name: 'project1', path: 'project1', namespace_id: namespace1.id, visibility_level: 20) }
+ let!(:project2) { projects.create!(name: 'project2', path: 'project2', namespace_id: namespace2.id, visibility_level: 20) }
+ let!(:project3) { projects.create!(name: 'project3', path: 'project3', namespace_id: namespace3.id, visibility_level: 20) }
+ let!(:project4) { projects.create!(name: 'project4', path: 'project4', namespace_id: namespace3.id, visibility_level: 20) }
+ let!(:batching_strategy) { described_class.new }
+
+ let(:job_arguments) { [namespace1.id, 'up'] }
+
+ context 'when starting on the first batch' do
+ it 'returns the bounds of the next batch' do
+ batch_bounds = batching_strategy.next_batch(:projects, :id, batch_min_value: project1.id, batch_size: 3, job_arguments: job_arguments)
+
+ expect(batch_bounds).to match_array([project1.id, project3.id])
+ end
+ end
+
+ context 'when additional batches remain' do
+ it 'returns the bounds of the next batch' do
+ batch_bounds = batching_strategy.next_batch(:projects, :id, batch_min_value: project2.id, batch_size: 3, job_arguments: job_arguments)
+
+ expect(batch_bounds).to match_array([project2.id, project4.id])
+ end
+ end
+
+ context 'when on the final batch' do
+ it 'returns the bounds of the next batch' do
+ batch_bounds = batching_strategy.next_batch(:projects, :id, batch_min_value: project4.id, batch_size: 3, job_arguments: job_arguments)
+
+ expect(batch_bounds).to match_array([project4.id, project4.id])
+ end
+ end
+
+ context 'when no additional batches remain' do
+ it 'returns nil' do
+ batch_bounds = batching_strategy.next_batch(:projects, :id, batch_min_value: project4.id + 1, batch_size: 1, job_arguments: job_arguments)
+
+ expect(batch_bounds).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb b/spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb
index 8febe850e04..39030039125 100644
--- a/spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb
+++ b/spec/lib/gitlab/background_migration/batching_strategies/primary_key_batching_strategy_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchi
context 'when starting on the first batch' do
it 'returns the bounds of the next batch' do
- batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace1.id, batch_size: 3)
+ batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace1.id, batch_size: 3, job_arguments: nil)
expect(batch_bounds).to eq([namespace1.id, namespace3.id])
end
@@ -21,7 +21,7 @@ RSpec.describe Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchi
context 'when additional batches remain' do
it 'returns the bounds of the next batch' do
- batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace2.id, batch_size: 3)
+ batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace2.id, batch_size: 3, job_arguments: nil)
expect(batch_bounds).to eq([namespace2.id, namespace4.id])
end
@@ -29,7 +29,7 @@ RSpec.describe Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchi
context 'when on the final batch' do
it 'returns the bounds of the next batch' do
- batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace4.id, batch_size: 3)
+ batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace4.id, batch_size: 3, job_arguments: nil)
expect(batch_bounds).to eq([namespace4.id, namespace4.id])
end
@@ -37,7 +37,7 @@ RSpec.describe Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchi
context 'when no additional batches remain' do
it 'returns nil' do
- batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace4.id + 1, batch_size: 1)
+ batch_bounds = batching_strategy.next_batch(:namespaces, :id, batch_min_value: namespace4.id + 1, batch_size: 1, job_arguments: nil)
expect(batch_bounds).to be_nil
end
diff --git a/spec/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces_spec.rb b/spec/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces_spec.rb
index 24259b06469..2c5de448fbc 100644
--- a/spec/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces_spec.rb
+++ b/spec/lib/gitlab/background_migration/project_namespaces/backfill_project_namespaces_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa
start_id = ::Project.minimum(:id)
end_id = ::Project.maximum(:id)
projects_count = ::Project.count
- batches_count = (projects_count / described_class::BATCH_SIZE.to_f).ceil
+ batches_count = (projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil
project_namespaces_count = ::Namespace.where(type: 'Project').count
migration = described_class.new
@@ -39,7 +39,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa
expect(migration).to receive(:batch_update_projects).exactly(batches_count).and_call_original
expect(migration).to receive(:batch_update_project_namespaces_traversal_ids).exactly(batches_count).and_call_original
- expect { migration.perform(start_id, end_id, nil, 'up') }.to change(Namespace.where(type: 'Project'), :count)
+ expect { migration.perform(start_id, end_id, nil, nil, nil, nil, nil, 'up') }.to change(Namespace.where(type: 'Project'), :count)
expect(projects_count).to eq(::Namespace.where(type: 'Project').count)
check_projects_in_sync_with(Namespace.where(type: 'Project'))
@@ -53,7 +53,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa
start_id = backfilled_namespace_projects.minimum(:id)
end_id = backfilled_namespace_projects.maximum(:id)
group_projects_count = backfilled_namespace_projects.count
- batches_count = (group_projects_count / described_class::BATCH_SIZE.to_f).ceil
+ batches_count = (group_projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil
project_namespaces_in_hierarchy = project_namespaces_in_hierarchy(base_ancestor(backfilled_namespace))
migration = described_class.new
@@ -66,7 +66,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa
expect(group_projects_count).to eq(14)
expect(project_namespaces_in_hierarchy.count).to eq(0)
- migration.perform(start_id, end_id, backfilled_namespace.id, 'up')
+ migration.perform(start_id, end_id, nil, nil, nil, nil, backfilled_namespace.id, 'up')
expect(project_namespaces_in_hierarchy.count).to eq(14)
check_projects_in_sync_with(project_namespaces_in_hierarchy)
@@ -79,7 +79,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa
start_id = hierarchy1_projects.minimum(:id)
end_id = hierarchy1_projects.maximum(:id)
- described_class.new.perform(start_id, end_id, parent_group1.id, 'up')
+ described_class.new.perform(start_id, end_id, nil, nil, nil, nil, parent_group1.id, 'up')
end
it 'does not duplicate project namespaces' do
@@ -87,7 +87,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa
projects_count = ::Project.count
start_id = ::Project.minimum(:id)
end_id = ::Project.maximum(:id)
- batches_count = (projects_count / described_class::BATCH_SIZE.to_f).ceil
+ batches_count = (projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil
project_namespaces = ::Namespace.where(type: 'Project')
migration = described_class.new
@@ -100,7 +100,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa
expect(migration).to receive(:batch_update_projects).exactly(batches_count).and_call_original
expect(migration).to receive(:batch_update_project_namespaces_traversal_ids).exactly(batches_count).and_call_original
- expect { migration.perform(start_id, end_id, nil, 'up') }.to change(project_namespaces, :count).by(14)
+ expect { migration.perform(start_id, end_id, nil, nil, nil, nil, nil, 'up') }.to change(project_namespaces, :count).by(14)
expect(projects_count).to eq(project_namespaces.count)
end
@@ -125,7 +125,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa
context 'back-fill project namespaces in batches' do
before do
- stub_const("#{described_class.name}::BATCH_SIZE", 2)
+ stub_const("#{described_class.name}::SUB_BATCH_SIZE", 2)
end
it_behaves_like 'back-fill project namespaces'
@@ -137,7 +137,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa
start_id = ::Project.minimum(:id)
end_id = ::Project.maximum(:id)
# back-fill first
- described_class.new.perform(start_id, end_id, nil, 'up')
+ described_class.new.perform(start_id, end_id, nil, nil, nil, nil, nil, 'up')
end
shared_examples 'cleanup project namespaces' do
@@ -146,7 +146,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa
start_id = ::Project.minimum(:id)
end_id = ::Project.maximum(:id)
migration = described_class.new
- batches_count = (projects_count / described_class::BATCH_SIZE.to_f).ceil
+ batches_count = (projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil
expect(projects_count).to be > 0
expect(projects_count).to eq(::Namespace.where(type: 'Project').count)
@@ -154,7 +154,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa
expect(migration).to receive(:nullify_project_namespaces_in_projects).exactly(batches_count).and_call_original
expect(migration).to receive(:delete_project_namespace_records).exactly(batches_count).and_call_original
- migration.perform(start_id, end_id, nil, 'down')
+ migration.perform(start_id, end_id, nil, nil, nil, nil, nil, 'down')
expect(::Project.count).to be > 0
expect(::Namespace.where(type: 'Project').count).to eq(0)
@@ -168,7 +168,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa
start_id = backfilled_namespace_projects.minimum(:id)
end_id = backfilled_namespace_projects.maximum(:id)
group_projects_count = backfilled_namespace_projects.count
- batches_count = (group_projects_count / described_class::BATCH_SIZE.to_f).ceil
+ batches_count = (group_projects_count / described_class::SUB_BATCH_SIZE.to_f).ceil
project_namespaces_in_hierarchy = project_namespaces_in_hierarchy(base_ancestor(backfilled_namespace))
migration = described_class.new
@@ -176,7 +176,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa
expect(migration).to receive(:nullify_project_namespaces_in_projects).exactly(batches_count).and_call_original
expect(migration).to receive(:delete_project_namespace_records).exactly(batches_count).and_call_original
- migration.perform(start_id, end_id, backfilled_namespace.id, 'down')
+ migration.perform(start_id, end_id, nil, nil, nil, nil, backfilled_namespace.id, 'down')
expect(::Namespace.where(type: 'Project').count).to be > 0
expect(project_namespaces_in_hierarchy.count).to eq(0)
@@ -190,7 +190,7 @@ RSpec.describe Gitlab::BackgroundMigration::ProjectNamespaces::BackfillProjectNa
context 'cleanup project namespaces in batches' do
before do
- stub_const("#{described_class.name}::BATCH_SIZE", 2)
+ stub_const("#{described_class.name}::SUB_BATCH_SIZE", 2)
end
it_behaves_like 'cleanup project namespaces'
diff --git a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
index 1f70fbd440c..112ff2c7380 100644
--- a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
+++ b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
@@ -25,21 +25,17 @@ RSpec.describe 'cross-database foreign keys' do
ci_pending_builds.project_id
ci_pipeline_schedules.owner_id
ci_pipeline_schedules.project_id
- ci_pipelines.merge_request_id
ci_pipelines.project_id
ci_project_monthly_usages.project_id
- ci_refs.project_id
ci_resource_groups.project_id
ci_runner_namespaces.namespace_id
ci_runner_projects.project_id
ci_running_builds.project_id
- ci_sources_pipelines.project_id
ci_sources_projects.source_project_id
ci_stages.project_id
ci_subscriptions_projects.downstream_project_id
ci_subscriptions_projects.upstream_project_id
ci_unit_tests.project_id
- ci_variables.project_id
dast_site_profiles_pipelines.ci_pipeline_id
external_pull_requests.project_id
vulnerability_feedback.pipeline_id
diff --git a/spec/lib/gitlab/subscription_portal_spec.rb b/spec/lib/gitlab/subscription_portal_spec.rb
index 627d3bb42c7..fd3654afee0 100644
--- a/spec/lib/gitlab/subscription_portal_spec.rb
+++ b/spec/lib/gitlab/subscription_portal_spec.rb
@@ -61,7 +61,6 @@ RSpec.describe ::Gitlab::SubscriptionPortal do
:subscriptions_more_minutes_url | 'https://customers.staging.gitlab.com/buy_pipeline_minutes'
:subscriptions_more_storage_url | 'https://customers.staging.gitlab.com/buy_storage'
:subscriptions_manage_url | 'https://customers.staging.gitlab.com/subscriptions'
- :subscriptions_plans_url | 'https://about.gitlab.com/pricing/'
:subscriptions_instance_review_url | 'https://customers.staging.gitlab.com/instance_review'
:subscriptions_gitlab_plans_url | 'https://customers.staging.gitlab.com/gitlab_plans'
:edit_account_url | 'https://customers.staging.gitlab.com/customers/edit'
diff --git a/spec/migrations/backfill_project_namespaces_for_group_spec.rb b/spec/migrations/backfill_project_namespaces_for_group_spec.rb
new file mode 100644
index 00000000000..0d34d19d42a
--- /dev/null
+++ b/spec/migrations/backfill_project_namespaces_for_group_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe BackfillProjectNamespacesForGroup do
+ let_it_be(:migration) { described_class::MIGRATION }
+
+ let(:projects) { table(:projects) }
+ let(:namespaces) { table(:namespaces) }
+ let(:parent_group1) { namespaces.create!(name: 'parent_group1', path: 'parent_group1', visibility_level: 20, type: 'Group') }
+ let!(:parent_group1_project) { projects.create!(name: 'parent_group1_project', path: 'parent_group1_project', namespace_id: parent_group1.id, visibility_level: 20) }
+
+ before do
+ allow(Gitlab).to receive(:com?).and_return(true)
+ end
+
+ describe '#up' do
+ before do
+ stub_const("BackfillProjectNamespacesForGroup::GROUP_ID", parent_group1.id)
+ end
+
+ it 'schedules background jobs for each batch of namespaces' do
+ migrate!
+
+ expect(migration).to have_scheduled_batched_migration(
+ table_name: :projects,
+ column_name: :id,
+ job_arguments: [described_class::GROUP_ID, 'up'],
+ interval: described_class::DELAY_INTERVAL
+ )
+ end
+ end
+
+ describe '#down' do
+ it 'deletes all batched migration records' do
+ migrate!
+ schema_migrate_down!
+
+ expect(migration).not_to have_scheduled_batched_migration
+ end
+ end
+end
diff --git a/spec/models/ci/namespace_mirror_spec.rb b/spec/models/ci/namespace_mirror_spec.rb
index a9d916115fc..38471f15849 100644
--- a/spec/models/ci/namespace_mirror_spec.rb
+++ b/spec/models/ci/namespace_mirror_spec.rb
@@ -20,10 +20,10 @@ RSpec.describe Ci::NamespaceMirror do
end
context 'scopes' do
- describe '.contains_namespace' do
+ describe '.by_group_and_descendants' do
let_it_be(:another_group) { create(:group) }
- subject(:result) { described_class.contains_namespace(group2.id) }
+ subject(:result) { described_class.by_group_and_descendants(group2.id) }
it 'returns groups having group2.id in traversal_ids' do
expect(result.pluck(:namespace_id)).to contain_exactly(group2.id, group3.id, group4.id)
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 90f56c1e0a4..8c0f843fdea 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -390,20 +390,63 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
describe '#merge_request?' do
- let(:pipeline) { create(:ci_pipeline, merge_request: merge_request) }
- let(:merge_request) { create(:merge_request) }
+ let_it_be(:merge_request) { create(:merge_request) }
+ let_it_be_with_reload(:pipeline) do
+ create(:ci_pipeline, project: project, merge_request_id: merge_request.id)
+ end
+
+ it { expect(pipeline).to be_merge_request }
- it 'returns true' do
- expect(pipeline).to be_merge_request
+ context 'when merge request is already loaded' do
+ it 'does not reload the record and returns true' do
+ expect(pipeline.merge_request).to be_present
+
+ expect { pipeline.merge_request? }.not_to exceed_query_limit(0)
+ expect(pipeline).to be_merge_request
+ end
end
- context 'when merge request is nil' do
- let(:merge_request) { nil }
+ context 'when merge request is not loaded' do
+ it 'executes a database query and returns true' do
+ expect(pipeline).to be_present
- it 'returns false' do
+ expect { pipeline.merge_request? }.not_to exceed_query_limit(1)
+ expect(pipeline).to be_merge_request
+ end
+
+ it 'caches the result' do
+ expect(pipeline).to be_merge_request
+ expect { pipeline.merge_request? }.not_to exceed_query_limit(0)
+ end
+ end
+
+ context 'when merge request was removed' do
+ before do
+ pipeline.update!(merge_request_id: non_existing_record_id)
+ end
+
+ it 'executes a database query and returns false' do
+ expect { pipeline.merge_request? }.not_to exceed_query_limit(1)
expect(pipeline).not_to be_merge_request
end
end
+
+ context 'when merge request id is not present' do
+ before do
+ pipeline.update!(merge_request_id: nil)
+ end
+
+ it { expect(pipeline).not_to be_merge_request }
+ end
+
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(ci_pipeline_merge_request_presence_check: false)
+ pipeline.update!(merge_request_id: non_existing_record_id)
+ end
+
+ it { expect(pipeline).to be_merge_request }
+ end
end
describe '#detached_merge_request_pipeline?' do
@@ -4656,9 +4699,11 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
let(:factory_name) { :ci_pipeline }
end
- it_behaves_like 'cleanup by a loose foreign key' do
- let!(:model) { create(:ci_pipeline, user: create(:user)) }
- let!(:parent) { model.user }
+ context 'loose foreign key on ci_pipelines.user_id' do
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:model) { create(:ci_pipeline, user: create(:user)) }
+ let!(:parent) { model.user }
+ end
end
describe 'tags count' do
@@ -4679,4 +4724,11 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
it { expect(pipeline.distinct_tags_count).to eq(3) }
end
end
+
+ context 'loose foreign key on ci_pipelines.merge_request_id' do
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:merge_request) }
+ let!(:model) { create(:ci_pipeline, merge_request: parent) }
+ end
+ end
end
diff --git a/spec/models/ci/ref_spec.rb b/spec/models/ci/ref_spec.rb
index 0a9cd5ef2ec..ffbda4b459f 100644
--- a/spec/models/ci/ref_spec.rb
+++ b/spec/models/ci/ref_spec.rb
@@ -231,4 +231,11 @@ RSpec.describe Ci::Ref do
it_behaves_like 'no-op'
end
end
+
+ context 'loose foreign key on ci_refs.project_id' do
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:project) }
+ let!(:model) { create(:ci_ref, project: parent) }
+ end
+ end
end
diff --git a/spec/models/ci/sources/pipeline_spec.rb b/spec/models/ci/sources/pipeline_spec.rb
index 97fc8d4a56d..73f7cfa739f 100644
--- a/spec/models/ci/sources/pipeline_spec.rb
+++ b/spec/models/ci/sources/pipeline_spec.rb
@@ -24,4 +24,11 @@ RSpec.describe Ci::Sources::Pipeline do
let!(:model) { create(:ci_sources_pipeline, source_project: parent) }
end
end
+
+ context 'loose foreign key on ci_sources_pipelines.project_id' do
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:project) }
+ let!(:model) { create(:ci_sources_pipeline, project: parent) }
+ end
+ end
end
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index 93a24ba9157..29ca088ee04 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -44,4 +44,11 @@ RSpec.describe Ci::Variable do
end
end
end
+
+ context 'loose foreign key on ci_variables.project_id' do
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:project) }
+ let!(:model) { create(:ci_variable, project: parent) }
+ end
+ end
end
diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb
index eb1f84e3ef9..647d64002ef 100644
--- a/spec/requests/api/graphql/ci/runner_spec.rb
+++ b/spec/requests/api/graphql/ci/runner_spec.rb
@@ -68,6 +68,7 @@ RSpec.describe 'Query.runner(id)' do
'revision' => runner.revision,
'locked' => false,
'active' => runner.active,
+ 'paused' => !runner.active,
'status' => runner.status('14.5').to_s.upcase,
'maximumTimeout' => runner.maximum_timeout,
'accessLevel' => runner.access_level.to_s.upcase,
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index 81cab973b30..1a3dde78580 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -289,4 +289,33 @@ RSpec.describe Groups::CreateService, '#execute' do
end
end
end
+
+ describe 'logged_out_marketing_header experiment', :experiment do
+ let(:service) { described_class.new(user, group_params) }
+
+ subject { service.execute }
+
+ before do
+ stub_experiments(logged_out_marketing_header: :candidate)
+ end
+
+ it 'tracks signed_up event' do
+ expect(experiment(:logged_out_marketing_header)).to track(
+ :namespace_created,
+ namespace: an_instance_of(Group)
+ ).on_next_instance.with_context(actor: user)
+
+ subject
+ end
+
+ context 'when group has not been persisted' do
+ let(:service) { described_class.new(user, group_params.merge(name: '<script>alert("Attack!")</script>')) }
+
+ it 'does not track signed_up event' do
+ expect(experiment(:logged_out_marketing_header)).not_to track(:namespace_created)
+
+ subject
+ end
+ end
+ end
end