summaryrefslogtreecommitdiff
path: root/spec/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/__helpers__/matchers/to_match_interpolated_text.js15
-rw-r--r--spec/frontend/__helpers__/mock_apollo_helper.js19
-rw-r--r--spec/frontend/__helpers__/test_apollo_link.js5
-rw-r--r--spec/frontend/__mocks__/@gitlab/ui.js13
-rw-r--r--spec/frontend/actioncable_link_spec.js2
-rw-r--r--spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap1
-rw-r--r--spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js50
-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/admin/users/components/users_table_spec.js11
-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.js34
-rw-r--r--spec/frontend/alerts_settings/components/mocks/apollo_mock.js6
-rw-r--r--spec/frontend/analytics/shared/components/daterange_spec.js41
-rw-r--r--spec/frontend/analytics/shared/components/metric_popover_spec.js (renamed from spec/frontend/cycle_analytics/metric_popover_spec.js)2
-rw-r--r--spec/frontend/analytics/shared/components/metric_tile_spec.js81
-rw-r--r--spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js31
-rw-r--r--spec/frontend/analytics/shared/utils_spec.js36
-rw-r--r--spec/frontend/analytics/usage_trends/apollo_mock_data.js1
-rw-r--r--spec/frontend/analytics/usage_trends/components/usage_trends_count_chart_spec.js15
-rw-r--r--spec/frontend/analytics/usage_trends/components/users_chart_spec.js15
-rw-r--r--spec/frontend/analytics/usage_trends/mock_data.js20
-rw-r--r--spec/frontend/artifacts_settings/components/keep_latest_artifact_checkbox_spec.js11
-rw-r--r--spec/frontend/authentication/webauthn/util_spec.js19
-rw-r--r--spec/frontend/badges/components/badge_form_spec.js16
-rw-r--r--spec/frontend/badges/components/badge_list_row_spec.js16
-rw-r--r--spec/frontend/badges/components/badge_list_spec.js38
-rw-r--r--spec/frontend/badges/components/badge_settings_spec.js9
-rw-r--r--spec/frontend/badges/components/badge_spec.js77
-rw-r--r--spec/frontend/batch_comments/components/diff_file_drafts_spec.js19
-rw-r--r--spec/frontend/batch_comments/components/draft_note_spec.js94
-rw-r--r--spec/frontend/batch_comments/components/drafts_count_spec.js11
-rw-r--r--spec/frontend/batch_comments/components/preview_dropdown_spec.js6
-rw-r--r--spec/frontend/batch_comments/components/publish_button_spec.js11
-rw-r--r--spec/frontend/batch_comments/components/publish_dropdown_spec.js6
-rw-r--r--spec/frontend/behaviors/copy_as_gfm_spec.js37
-rw-r--r--spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js144
-rw-r--r--spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap1
-rw-r--r--spec/frontend/blob/components/blob_edit_header_spec.js8
-rw-r--r--spec/frontend/blob/components/blob_header_default_actions_spec.js43
-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/blob/components/mock_data.js3
-rw-r--r--spec/frontend/blob/viewer/index_spec.js48
-rw-r--r--spec/frontend/boards/board_card_inner_spec.js62
-rw-r--r--spec/frontend/boards/board_list_helper.js9
-rw-r--r--spec/frontend/boards/board_list_spec.js25
-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_filtered_search_spec.js4
-rw-r--r--spec/frontend/boards/components/board_form_spec.js2
-rw-r--r--spec/frontend/boards/components/board_list_header_spec.js8
-rw-r--r--spec/frontend/boards/components/board_new_issue_spec.js49
-rw-r--r--spec/frontend/boards/components/board_new_item_spec.js37
-rw-r--r--spec/frontend/boards/components/board_settings_sidebar_spec.js28
-rw-r--r--spec/frontend/boards/components/boards_selector_spec.js195
-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/mock_data.js80
-rw-r--r--spec/frontend/boards/project_select_spec.js4
-rw-r--r--spec/frontend/boards/stores/actions_spec.js4
-rw-r--r--spec/frontend/broadcast_notification_spec.js1
-rw-r--r--spec/frontend/captcha/apollo_captcha_link_spec.js2
-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.js11
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js7
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js7
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_table_spec.js46
-rw-r--r--spec/frontend/clusters/agents/components/activity_events_list_spec.js7
-rw-r--r--spec/frontend/clusters/agents/components/show_spec.js51
-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.js45
-rw-r--r--spec/frontend/clusters_list/components/agent_table_spec.js155
-rw-r--r--spec/frontend/clusters_list/components/agents_spec.js116
-rw-r--r--spec/frontend/clusters_list/components/ancestor_notice_spec.js13
-rw-r--r--spec/frontend/clusters_list/components/clusters_actions_spec.js35
-rw-r--r--spec/frontend/clusters_list/components/clusters_spec.js5
-rw-r--r--spec/frontend/clusters_list/components/clusters_view_all_spec.js79
-rw-r--r--spec/frontend/clusters_list/components/delete_agent_button_spec.js (renamed from spec/frontend/clusters_list/components/agent_options_spec.js)77
-rw-r--r--spec/frontend/clusters_list/components/install_agent_modal_spec.js60
-rw-r--r--spec/frontend/clusters_list/components/mock_data.js112
-rw-r--r--spec/frontend/clusters_list/components/node_error_help_text_spec.js5
-rw-r--r--spec/frontend/clusters_list/mocks/apollo.js13
-rw-r--r--spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap1
-rw-r--r--spec/frontend/code_navigation/components/app_spec.js8
-rw-r--r--spec/frontend/code_quality_walkthrough/components/step_spec.js2
-rw-r--r--spec/frontend/commit/commit_pipeline_status_component_spec.js12
-rw-r--r--spec/frontend/commit/pipelines/pipelines_table_spec.js66
-rw-r--r--spec/frontend/confirm_modal_spec.js16
-rw-r--r--spec/frontend/content_editor/test_utils.js2
-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/create_eks_cluster_spec.js7
-rw-r--r--spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js99
-rw-r--r--spec/frontend/create_cluster/eks_cluster/components/service_credentials_form_spec.js18
-rw-r--r--spec/frontend/create_cluster/gke_cluster/components/gke_machine_type_dropdown_spec.js22
-rw-r--r--spec/frontend/create_cluster/gke_cluster/components/gke_network_dropdown_spec.js8
-rw-r--r--spec/frontend/create_cluster/gke_cluster/components/gke_project_id_dropdown_spec.js42
-rw-r--r--spec/frontend/create_cluster/gke_cluster/components/gke_submit_button_spec.js8
-rw-r--r--spec/frontend/create_cluster/gke_cluster/components/gke_subnetwork_dropdown_spec.js8
-rw-r--r--spec/frontend/create_cluster/gke_cluster/components/gke_zone_dropdown_spec.js24
-rw-r--r--spec/frontend/cycle_analytics/base_spec.js2
-rw-r--r--spec/frontend/cycle_analytics/filter_bar_spec.js9
-rw-r--r--spec/frontend/cycle_analytics/stage_table_spec.js3
-rw-r--r--spec/frontend/cycle_analytics/utils_spec.js31
-rw-r--r--spec/frontend/cycle_analytics/value_stream_metrics_spec.js80
-rw-r--r--spec/frontend/deploy_freeze/components/deploy_freeze_settings_spec.js7
-rw-r--r--spec/frontend/deploy_freeze/components/deploy_freeze_table_spec.js11
-rw-r--r--spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js7
-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.js65
-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.js27
-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.js35
-rw-r--r--spec/frontend/design_management/components/toolbar/design_navigation_spec.js8
-rw-r--r--spec/frontend/design_management/components/toolbar/index_spec.js61
-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.js64
-rw-r--r--spec/frontend/design_management/pages/index_spec.js35
-rw-r--r--spec/frontend/design_management/router_spec.js8
-rw-r--r--spec/frontend/design_management/utils/cache_update_spec.js2
-rw-r--r--spec/frontend/diffs/components/app_spec.js23
-rw-r--r--spec/frontend/diffs/components/collapsed_files_warning_spec.js29
-rw-r--r--spec/frontend/diffs/components/compare_versions_spec.js36
-rw-r--r--spec/frontend/diffs/components/diff_content_spec.js7
-rw-r--r--spec/frontend/diffs/components/diff_discussion_reply_spec.js7
-rw-r--r--spec/frontend/diffs/components/diff_discussions_spec.js13
-rw-r--r--spec/frontend/diffs/components/diff_expansion_cell_spec.js8
-rw-r--r--spec/frontend/diffs/components/diff_file_header_spec.js30
-rw-r--r--spec/frontend/diffs/components/diff_file_spec.js40
-rw-r--r--spec/frontend/diffs/components/diff_gutter_avatars_spec.js22
-rw-r--r--spec/frontend/diffs/components/diff_view_spec.js5
-rw-r--r--spec/frontend/diffs/components/image_diff_overlay_spec.js4
-rw-r--r--spec/frontend/diffs/components/merge_conflict_warning_spec.js15
-rw-r--r--spec/frontend/diffs/components/no_changes_spec.js7
-rw-r--r--spec/frontend/diffs/components/settings_dropdown_spec.js7
-rw-r--r--spec/frontend/diffs/components/tree_list_spec.js39
-rw-r--r--spec/frontend/diffs/store/actions_spec.js14
-rw-r--r--spec/frontend/editor/source_editor_yaml_ext_spec.js102
-rw-r--r--spec/frontend/emoji/awards_app/store/actions_spec.js37
-rw-r--r--spec/frontend/emoji/components/utils_spec.js3
-rw-r--r--spec/frontend/environment.js3
-rw-r--r--spec/frontend/environments/canary_ingress_spec.js27
-rw-r--r--spec/frontend/environments/canary_update_modal_spec.js11
-rw-r--r--spec/frontend/environments/commit_spec.js71
-rw-r--r--spec/frontend/environments/deploy_board_component_spec.js75
-rw-r--r--spec/frontend/environments/deploy_board_wrapper_spec.js124
-rw-r--r--spec/frontend/environments/deployment_spec.js243
-rw-r--r--spec/frontend/environments/environment_actions_spec.js4
-rw-r--r--spec/frontend/environments/environment_pin_spec.js74
-rw-r--r--spec/frontend/environments/environment_table_spec.js3
-rw-r--r--spec/frontend/environments/environments_app_spec.js8
-rw-r--r--spec/frontend/environments/graphql/mock_data.js66
-rw-r--r--spec/frontend/environments/graphql/resolvers_spec.js4
-rw-r--r--spec/frontend/environments/new_environment_folder_spec.js1
-rw-r--r--spec/frontend/environments/new_environment_item_spec.js185
-rw-r--r--spec/frontend/environments/new_environments_app_spec.js31
-rw-r--r--spec/frontend/error_tracking/components/error_details_spec.js160
-rw-r--r--spec/frontend/error_tracking/components/error_tracking_actions_spec.js24
-rw-r--r--spec/frontend/error_tracking/components/error_tracking_list_spec.js52
-rw-r--r--spec/frontend/error_tracking_settings/components/app_spec.js15
-rw-r--r--spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js7
-rw-r--r--spec/frontend/error_tracking_settings/components/project_dropdown_spec.js19
-rw-r--r--spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js7
-rw-r--r--spec/frontend/feature_flags/components/edit_feature_flag_spec.js14
-rw-r--r--spec/frontend/feature_flags/components/empty_state_spec.js3
-rw-r--r--spec/frontend/feature_flags/components/environments_dropdown_spec.js15
-rw-r--r--spec/frontend/feature_flags/components/feature_flags_spec.js29
-rw-r--r--spec/frontend/feature_flags/components/feature_flags_table_spec.js8
-rw-r--r--spec/frontend/feature_flags/components/form_spec.js21
-rw-r--r--spec/frontend/feature_flags/components/new_environments_dropdown_spec.js12
-rw-r--r--spec/frontend/feature_flags/components/new_feature_flag_spec.js18
-rw-r--r--spec/frontend/feature_flags/components/strategies/flexible_rollout_spec.js3
-rw-r--r--spec/frontend/feature_flags/components/strategies/gitlab_user_list_spec.js11
-rw-r--r--spec/frontend/feature_flags/components/strategies/percent_rollout_spec.js3
-rw-r--r--spec/frontend/feature_flags/components/strategy_spec.js140
-rw-r--r--spec/frontend/feature_highlight/feature_highlight_popover_spec.js3
-rw-r--r--spec/frontend/filtered_search/dropdown_user_spec.js2
-rw-r--r--spec/frontend/filtered_search/dropdown_utils_spec.js4
-rw-r--r--spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js9
-rw-r--r--spec/frontend/filtered_search/recent_searches_root_spec.js5
-rw-r--r--spec/frontend/fixtures/application_settings.rb1
-rw-r--r--spec/frontend/fixtures/blob.rb2
-rw-r--r--spec/frontend/fixtures/branches.rb2
-rw-r--r--spec/frontend/fixtures/clusters.rb2
-rw-r--r--spec/frontend/fixtures/commit.rb2
-rw-r--r--spec/frontend/fixtures/freeze_period.rb2
-rw-r--r--spec/frontend/fixtures/issues.rb11
-rw-r--r--spec/frontend/fixtures/jobs.rb2
-rw-r--r--spec/frontend/fixtures/listbox.rb31
-rw-r--r--spec/frontend/fixtures/merge_requests.rb13
-rw-r--r--spec/frontend/fixtures/merge_requests_diffs.rb2
-rw-r--r--spec/frontend/fixtures/pipeline_schedules.rb2
-rw-r--r--spec/frontend/fixtures/projects.rb2
-rw-r--r--spec/frontend/fixtures/prometheus_service.rb2
-rw-r--r--spec/frontend/fixtures/runner.rb44
-rw-r--r--spec/frontend/fixtures/services.rb2
-rw-r--r--spec/frontend/fixtures/snippet.rb2
-rw-r--r--spec/frontend/fixtures/tags.rb2
-rw-r--r--spec/frontend/fixtures/todos.rb2
-rw-r--r--spec/frontend/flash_spec.js8
-rw-r--r--spec/frontend/frequent_items/components/app_spec.js8
-rw-r--r--spec/frontend/frequent_items/components/frequent_items_list_item_spec.js7
-rw-r--r--spec/frontend/frequent_items/components/frequent_items_list_spec.js33
-rw-r--r--spec/frontend/frequent_items/components/frequent_items_search_input_spec.js9
-rw-r--r--spec/frontend/gl_form_spec.js15
-rw-r--r--spec/frontend/google_cloud/components/app_spec.js4
-rw-r--r--spec/frontend/google_cloud/components/deployments_service_table_spec.js7
-rw-r--r--spec/frontend/google_cloud/components/home_spec.js4
-rw-r--r--spec/frontend/google_cloud/components/service_accounts_form_spec.js19
-rw-r--r--spec/frontend/google_cloud/components/service_accounts_list_spec.js19
-rw-r--r--spec/frontend/google_tag_manager/index_spec.js178
-rw-r--r--spec/frontend/grafana_integration/components/grafana_integration_spec.js22
-rw-r--r--spec/frontend/group_settings/components/shared_runners_form_spec.js3
-rw-r--r--spec/frontend/groups/components/app_spec.js66
-rw-r--r--spec/frontend/groups/components/group_folder_spec.js6
-rw-r--r--spec/frontend/groups/components/groups_spec.js26
-rw-r--r--spec/frontend/groups/components/invite_members_banner_spec.js4
-rw-r--r--spec/frontend/groups/components/item_actions_spec.js38
-rw-r--r--spec/frontend/groups/components/transfer_group_form_spec.js131
-rw-r--r--spec/frontend/groups/landing_spec.js5
-rw-r--r--spec/frontend/groups/transfer_edit_spec.js31
-rw-r--r--spec/frontend/header_search/components/app_spec.js14
-rw-r--r--spec/frontend/header_search/components/header_search_autocomplete_items_spec.js4
-rw-r--r--spec/frontend/ide/components/activity_bar_spec.js11
-rw-r--r--spec/frontend/ide/components/branches/search_list_spec.js14
-rw-r--r--spec/frontend/ide/components/commit_sidebar/actions_spec.js29
-rw-r--r--spec/frontend/ide/components/commit_sidebar/editor_header_spec.js7
-rw-r--r--spec/frontend/ide/components/commit_sidebar/form_spec.js47
-rw-r--r--spec/frontend/ide/components/commit_sidebar/list_item_spec.js50
-rw-r--r--spec/frontend/ide/components/commit_sidebar/list_spec.js7
-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/commit_sidebar/radio_group_spec.js45
-rw-r--r--spec/frontend/ide/components/commit_sidebar/success_message_spec.js11
-rw-r--r--spec/frontend/ide/components/error_message_spec.js38
-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/file_templates/dropdown_spec.js36
-rw-r--r--spec/frontend/ide/components/ide_file_row_spec.js32
-rw-r--r--spec/frontend/ide/components/ide_review_spec.js16
-rw-r--r--spec/frontend/ide/components/ide_side_bar_spec.js19
-rw-r--r--spec/frontend/ide/components/ide_spec.js7
-rw-r--r--spec/frontend/ide/components/ide_status_bar_spec.js14
-rw-r--r--spec/frontend/ide/components/ide_status_list_spec.js7
-rw-r--r--spec/frontend/ide/components/ide_tree_list_spec.js13
-rw-r--r--spec/frontend/ide/components/ide_tree_spec.js6
-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/jobs/list_spec.js8
-rw-r--r--spec/frontend/ide/components/jobs/stage_spec.js15
-rw-r--r--spec/frontend/ide/components/merge_requests/item_spec.js7
-rw-r--r--spec/frontend/ide/components/merge_requests/list_spec.js75
-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/index_spec.js12
-rw-r--r--spec/frontend/ide/components/new_dropdown/modal_spec.js17
-rw-r--r--spec/frontend/ide/components/panes/collapsible_sidebar_spec.js9
-rw-r--r--spec/frontend/ide/components/panes/right_spec.js29
-rw-r--r--spec/frontend/ide/components/preview/clientside_spec.js60
-rw-r--r--spec/frontend/ide/components/preview/navigator_spec.js120
-rw-r--r--spec/frontend/ide/components/repo_editor_spec.js28
-rw-r--r--spec/frontend/ide/components/repo_tab_spec.js7
-rw-r--r--spec/frontend/ide/components/repo_tabs_spec.js22
-rw-r--r--spec/frontend/ide/components/resizable_panel_spec.js14
-rw-r--r--spec/frontend/ide/components/shared/tokened_input_spec.js12
-rw-r--r--spec/frontend/ide/components/terminal/session_spec.js25
-rw-r--r--spec/frontend/ide/components/terminal/terminal_controls_spec.js15
-rw-r--r--spec/frontend/ide/components/terminal/view_spec.js8
-rw-r--r--spec/frontend/ide/components/terminal_sync/terminal_sync_status_safe_spec.js7
-rw-r--r--spec/frontend/ide/components/terminal_sync/terminal_sync_status_spec.js7
-rw-r--r--spec/frontend/ide/lib/common/model_spec.js7
-rw-r--r--spec/frontend/ide/stores/actions/file_spec.js6
-rw-r--r--spec/frontend/ide/stores/actions_spec.js17
-rw-r--r--spec/frontend/ide/stores/modules/clientside/actions_spec.js5
-rw-r--r--spec/frontend/ide/stores/plugins/terminal_spec.js5
-rw-r--r--spec/frontend/image_diff/helpers/badge_helper_spec.js9
-rw-r--r--spec/frontend/image_diff/helpers/dom_helper_spec.js12
-rw-r--r--spec/frontend/image_diff/image_diff_spec.js8
-rw-r--r--spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js2
-rw-r--r--spec/frontend/import_entities/import_groups/graphql/fixtures.js1
-rw-r--r--spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js8
-rw-r--r--spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js8
-rw-r--r--spec/frontend/incidents/components/incidents_list_spec.js11
-rw-r--r--spec/frontend/incidents/mocks/incidents.json4
-rw-r--r--spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap1
-rw-r--r--spec/frontend/integrations/edit/components/active_checkbox_spec.js3
-rw-r--r--spec/frontend/integrations/edit/components/confirmation_modal_spec.js3
-rw-r--r--spec/frontend/integrations/edit/components/dynamic_field_spec.js58
-rw-r--r--spec/frontend/integrations/edit/components/integration_form_spec.js363
-rw-r--r--spec/frontend/integrations/edit/components/jira_issues_fields_spec.js28
-rw-r--r--spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js19
-rw-r--r--spec/frontend/integrations/edit/mock_data.js9
-rw-r--r--spec/frontend/invite_members/components/group_select_spec.js30
-rw-r--r--spec/frontend/invite_members/components/import_a_project_modal_spec.js5
-rw-r--r--spec/frontend/invite_members/components/invite_group_trigger_spec.js2
-rw-r--r--spec/frontend/invite_members/components/invite_groups_modal_spec.js143
-rw-r--r--spec/frontend/invite_members/components/invite_members_modal_spec.js495
-rw-r--r--spec/frontend/invite_members/components/invite_members_trigger_spec.js1
-rw-r--r--spec/frontend/invite_members/components/invite_modal_base_spec.js103
-rw-r--r--spec/frontend/invite_members/mock_data/group_modal.js11
-rw-r--r--spec/frontend/invite_members/mock_data/member_modal.js36
-rw-r--r--spec/frontend/invite_members/mock_data/modal_base.js11
-rw-r--r--spec/frontend/issuable/components/issue_milestone_spec.js24
-rw-r--r--spec/frontend/issuable/components/related_issuable_item_spec.js9
-rw-r--r--spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js72
-rw-r--r--spec/frontend/issuable/related_issues/components/related_issues_block_spec.js29
-rw-r--r--spec/frontend/issuable/related_issues/components/related_issues_root_spec.js30
-rw-r--r--spec/frontend/issues/create_merge_request_dropdown_spec.js4
-rw-r--r--spec/frontend/issues/list/components/issues_list_app_spec.js161
-rw-r--r--spec/frontend/issues/list/components/jira_issues_import_status_app_spec.js9
-rw-r--r--spec/frontend/issues/list/components/new_issue_dropdown_spec.js18
-rw-r--r--spec/frontend/issues/list/mock_data.js21
-rw-r--r--spec/frontend/issues/list/utils_spec.js13
-rw-r--r--spec/frontend/issues/new/components/title_suggestions_spec.js57
-rw-r--r--spec/frontend/issues/related_merge_requests/components/related_merge_requests_spec.js10
-rw-r--r--spec/frontend/issues/show/components/app_spec.js165
-rw-r--r--spec/frontend/issues/show/components/description_spec.js294
-rw-r--r--spec/frontend/issues/show/components/fields/description_spec.js1
-rw-r--r--spec/frontend/issues/show/components/fields/type_spec.js9
-rw-r--r--spec/frontend/issues/show/components/form_spec.js3
-rw-r--r--spec/frontend/issues/show/components/header_actions_spec.js4
-rw-r--r--spec/frontend/issues/show/components/title_spec.js46
-rw-r--r--spec/frontend/issues/show/mock_data/mock_data.js14
-rw-r--r--spec/frontend/jira_connect/branches/components/new_branch_form_spec.js143
-rw-r--r--spec/frontend/jira_connect/branches/components/project_dropdown_spec.js74
-rw-r--r--spec/frontend/jira_connect/branches/components/source_branch_dropdown_spec.js19
-rw-r--r--spec/frontend/jira_connect/branches/mock_data.js30
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js3
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_spec.js110
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/app_spec.js96
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/compatibility_alert_spec.js56
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/sign_in_button_spec.js12
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js3
-rw-r--r--spec/frontend/jira_connect/subscriptions/pages/sign_in_spec.js62
-rw-r--r--spec/frontend/jira_connect/subscriptions/pages/subscriptions_spec.js56
-rw-r--r--spec/frontend/jira_import/components/jira_import_app_spec.js6
-rw-r--r--spec/frontend/jobs/bridge/app_spec.js18
-rw-r--r--spec/frontend/jobs/components/job_app_spec.js18
-rw-r--r--spec/frontend/jobs/components/job_container_item_spec.js3
-rw-r--r--spec/frontend/jobs/components/job_log_controllers_spec.js9
-rw-r--r--spec/frontend/jobs/components/log/collapsible_section_spec.js8
-rw-r--r--spec/frontend/jobs/components/log/line_header_spec.js8
-rw-r--r--spec/frontend/jobs/components/log/log_spec.js11
-rw-r--r--spec/frontend/jobs/components/manual_variables_form_spec.js7
-rw-r--r--spec/frontend/jobs/components/sidebar_spec.js3
-rw-r--r--spec/frontend/jobs/components/table/cells/actions_cell_spec.js31
-rw-r--r--spec/frontend/jobs/components/table/job_table_app_spec.js53
-rw-r--r--spec/frontend/jobs/mixins/delayed_job_mixin_spec.js11
-rw-r--r--spec/frontend/jobs/mock_data.js97
-rw-r--r--spec/frontend/lib/apollo/suppress_network_errors_during_navigation_link_spec.js2
-rw-r--r--spec/frontend/lib/utils/apollo_startup_js_link_spec.js2
-rw-r--r--spec/frontend/lib/utils/common_utils_spec.js9
-rw-r--r--spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js18
-rw-r--r--spec/frontend/lib/utils/number_utility_spec.js16
-rw-r--r--spec/frontend/lib/utils/table_utility_spec.js31
-rw-r--r--spec/frontend/lib/utils/text_markdown_spec.js74
-rw-r--r--spec/frontend/lib/utils/vuex_module_mappers_spec.js10
-rw-r--r--spec/frontend/lib/utils/yaml_spec.js105
-rw-r--r--spec/frontend/listbox/index_spec.js111
-rw-r--r--spec/frontend/listbox/redirect_behavior_spec.js51
-rw-r--r--spec/frontend/logs/components/environment_logs_spec.js22
-rw-r--r--spec/frontend/logs/components/log_control_buttons_spec.js31
-rw-r--r--spec/frontend/members/components/action_buttons/approve_access_request_button_spec.js7
-rw-r--r--spec/frontend/members/components/action_buttons/remove_group_link_button_spec.js7
-rw-r--r--spec/frontend/members/components/action_buttons/remove_member_button_spec.js7
-rw-r--r--spec/frontend/members/components/action_buttons/resend_invite_button_spec.js7
-rw-r--r--spec/frontend/members/components/app_spec.js8
-rw-r--r--spec/frontend/members/components/avatars/user_avatar_spec.js63
-rw-r--r--spec/frontend/members/components/filter_sort/filter_sort_container_spec.js7
-rw-r--r--spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js7
-rw-r--r--spec/frontend/members/components/filter_sort/sort_dropdown_spec.js7
-rw-r--r--spec/frontend/members/components/modals/leave_modal_spec.js8
-rw-r--r--spec/frontend/members/components/modals/remove_group_link_modal_spec.js8
-rw-r--r--spec/frontend/members/components/table/expiration_datepicker_spec.js8
-rw-r--r--spec/frontend/members/components/table/members_table_cell_spec.js9
-rw-r--r--spec/frontend/members/components/table/members_table_spec.js11
-rw-r--r--spec/frontend/members/components/table/role_dropdown_spec.js8
-rw-r--r--spec/frontend/members/mock_data.js2
-rw-r--r--spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js10
-rw-r--r--spec/frontend/merge_conflicts/store/actions_spec.js5
-rw-r--r--spec/frontend/merge_request_spec.js67
-rw-r--r--spec/frontend/mocks_spec.js11
-rw-r--r--spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap22
-rw-r--r--spec/frontend/monitoring/components/charts/stacked_column_spec.js25
-rw-r--r--spec/frontend/monitoring/components/charts/time_series_spec.js142
-rw-r--r--spec/frontend/monitoring/components/create_dashboard_modal_spec.js10
-rw-r--r--spec/frontend/monitoring/components/dashboard_actions_menu_spec.js100
-rw-r--r--spec/frontend/monitoring/components/dashboard_header_spec.js143
-rw-r--r--spec/frontend/monitoring/components/dashboard_panel_builder_spec.js67
-rw-r--r--spec/frontend/monitoring/components/dashboard_panel_spec.js137
-rw-r--r--spec/frontend/monitoring/components/dashboard_spec.js341
-rw-r--r--spec/frontend/monitoring/components/dashboard_url_time_spec.js101
-rw-r--r--spec/frontend/monitoring/components/embeds/embed_group_spec.js15
-rw-r--r--spec/frontend/monitoring/components/embeds/metric_embed_spec.js7
-rw-r--r--spec/frontend/monitoring/components/graph_group_spec.js35
-rw-r--r--spec/frontend/monitoring/components/links_section_spec.js20
-rw-r--r--spec/frontend/monitoring/components/refresh_button_spec.js13
-rw-r--r--spec/frontend/monitoring/components/variables/dropdown_field_spec.js8
-rw-r--r--spec/frontend/monitoring/components/variables/text_field_spec.js22
-rw-r--r--spec/frontend/monitoring/components/variables_section_spec.js39
-rw-r--r--spec/frontend/monitoring/router_spec.js7
-rw-r--r--spec/frontend/mr_popover/mr_popover_spec.js29
-rw-r--r--spec/frontend/nav/components/responsive_app_spec.js5
-rw-r--r--spec/frontend/nav/components/top_nav_menu_item_spec.js1
-rw-r--r--spec/frontend/notebook/cells/code_spec.js22
-rw-r--r--spec/frontend/notebook/cells/markdown_spec.js71
-rw-r--r--spec/frontend/notebook/cells/output/index_spec.js36
-rw-r--r--spec/frontend/notebook/cells/prompt_spec.js14
-rw-r--r--spec/frontend/notebook/index_spec.js20
-rw-r--r--spec/frontend/notes/components/comment_form_spec.js13
-rw-r--r--spec/frontend/notes/components/diff_discussion_header_spec.js63
-rw-r--r--spec/frontend/notes/components/discussion_counter_spec.js34
-rw-r--r--spec/frontend/notes/components/discussion_filter_spec.js56
-rw-r--r--spec/frontend/notes/components/discussion_navigator_spec.js10
-rw-r--r--spec/frontend/notes/components/discussion_notes_spec.js29
-rw-r--r--spec/frontend/notes/components/discussion_resolve_button_spec.js17
-rw-r--r--spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js5
-rw-r--r--spec/frontend/notes/components/note_actions_spec.js40
-rw-r--r--spec/frontend/notes/components/note_body_spec.js6
-rw-r--r--spec/frontend/notes/components/note_form_spec.js2
-rw-r--r--spec/frontend/notes/components/note_header_spec.js8
-rw-r--r--spec/frontend/notes/components/noteable_note_spec.js24
-rw-r--r--spec/frontend/notes/components/notes_app_spec.js20
-rw-r--r--spec/frontend/notes/components/sort_discussion_spec.js7
-rw-r--r--spec/frontend/notes/components/timeline_toggle_spec.js15
-rw-r--r--spec/frontend/notes/deprecated_notes_spec.js81
-rw-r--r--spec/frontend/notes/mixins/discussion_navigation_spec.js57
-rw-r--r--spec/frontend/notifications/components/custom_notifications_modal_spec.js7
-rw-r--r--spec/frontend/notifications/components/notifications_dropdown_spec.js8
-rw-r--r--spec/frontend/operation_settings/components/metrics_settings_spec.js21
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js15
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cli_commands_spec.js21
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state_spec.js7
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state_spec.js7
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js14
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js30
-rw-r--r--spec/frontend/packages_and_registries/dependency_proxy/app_spec.js13
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js8
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js14
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js7
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap5
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js7
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js39
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js72
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js23
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js3
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap14
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/package_title_spec.js9
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js7
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js8
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js24
-rw-r--r--spec/frontend/packages_and_registries/package_registry/mock_data.js1
-rw-r--r--spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/pages/details_spec.js19
-rw-r--r--spec/frontend/packages_and_registries/package_registry/pages/list_spec.js24
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js7
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js10
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js9
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js31
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/settings_form_spec.js15
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/mock_data.js1
-rw-r--r--spec/frontend/pager_spec.js57
-rw-r--r--spec/frontend/pages/admin/projects/components/namespace_select_spec.js7
-rw-r--r--spec/frontend/pages/dashboard/todos/index/todos_spec.js5
-rw-r--r--spec/frontend/pages/profiles/password_prompt/password_prompt_modal_spec.js7
-rw-r--r--spec/frontend/pages/projects/forks/new/components/fork_form_spec.js11
-rw-r--r--spec/frontend/pages/projects/graphs/code_coverage_spec.js5
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap27
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js38
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js30
-rw-r--r--spec/frontend/pages/projects/learn_gitlab/components/mock_data.js2
-rw-r--r--spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js13
-rw-r--r--spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js8
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/project_setting_row_spec.js33
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js2
-rw-r--r--spec/frontend/pages/shared/wikis/components/wiki_form_spec.js6
-rw-r--r--spec/frontend/performance_bar/components/add_request_spec.js25
-rw-r--r--spec/frontend/persistent_user_callout_spec.js8
-rw-r--r--spec/frontend/pipeline_editor/components/commit/commit_section_spec.js53
-rw-r--r--spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js46
-rw-r--r--spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js11
-rw-r--r--spec/frontend/pipeline_editor/components/header/pipline_editor_mini_graph_spec.js11
-rw-r--r--spec/frontend/pipeline_editor/components/header/validation_segment_spec.js7
-rw-r--r--spec/frontend/pipeline_editor/components/ui/pipeline_editor_messages_spec.js13
-rw-r--r--spec/frontend/pipeline_editor/pipeline_editor_app_spec.js36
-rw-r--r--spec/frontend/pipeline_new/components/pipeline_new_form_spec.js13
-rw-r--r--spec/frontend/pipeline_wizard/components/commit_spec.js282
-rw-r--r--spec/frontend/pipeline_wizard/components/editor_spec.js69
-rw-r--r--spec/frontend/pipeline_wizard/components/step_nav_spec.js79
-rw-r--r--spec/frontend/pipeline_wizard/components/widgets/text_spec.js152
-rw-r--r--spec/frontend/pipeline_wizard/mock/query_responses.js62
-rw-r--r--spec/frontend/pipelines/__snapshots__/utils_spec.js.snap11
-rw-r--r--spec/frontend/pipelines/components/dag/dag_annotations_spec.js19
-rw-r--r--spec/frontend/pipelines/components/dag/dag_spec.js9
-rw-r--r--spec/frontend/pipelines/components/jobs/jobs_app_spec.js13
-rw-r--r--spec/frontend/pipelines/components/pipelines_filtered_search_spec.js37
-rw-r--r--spec/frontend/pipelines/graph/action_component_spec.js23
-rw-r--r--spec/frontend/pipelines/graph/graph_component_wrapper_spec.js58
-rw-r--r--spec/frontend/pipelines/graph/job_item_spec.js49
-rw-r--r--spec/frontend/pipelines/graph/linked_pipeline_spec.js113
-rw-r--r--spec/frontend/pipelines/graph/linked_pipelines_column_spec.js30
-rw-r--r--spec/frontend/pipelines/graph/mock_data.js12
-rw-r--r--spec/frontend/pipelines/header_component_spec.js5
-rw-r--r--spec/frontend/pipelines/mock_data.js680
-rw-r--r--spec/frontend/pipelines/notification/deprecated_type_keyword_notification_spec.js146
-rw-r--r--spec/frontend/pipelines/notification/mock_data.js33
-rw-r--r--spec/frontend/pipelines/pipeline_triggerer_spec.js8
-rw-r--r--spec/frontend/pipelines/pipeline_url_spec.js349
-rw-r--r--spec/frontend/pipelines/pipelines_actions_spec.js20
-rw-r--r--spec/frontend/pipelines/pipelines_table_spec.js50
-rw-r--r--spec/frontend/pipelines/test_reports/test_case_details_spec.js5
-rw-r--r--spec/frontend/pipelines/test_reports/test_reports_spec.js7
-rw-r--r--spec/frontend/pipelines/test_reports/test_suite_table_spec.js7
-rw-r--r--spec/frontend/pipelines/test_reports/test_summary_table_spec.js7
-rw-r--r--spec/frontend/popovers/components/popovers_spec.js15
-rw-r--r--spec/frontend/popovers/index_spec.js17
-rw-r--r--spec/frontend/profile/account/components/delete_account_modal_spec.js74
-rw-r--r--spec/frontend/profile/account/components/update_username_spec.js11
-rw-r--r--spec/frontend/projects/commit/components/branches_dropdown_spec.js4
-rw-r--r--spec/frontend/projects/commit/components/form_modal_spec.js7
-rw-r--r--spec/frontend/projects/commits/components/author_select_spec.js60
-rw-r--r--spec/frontend/projects/compare/components/app_spec.js7
-rw-r--r--spec/frontend/projects/compare/components/repo_dropdown_spec.js7
-rw-r--r--spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js3
-rw-r--r--spec/frontend/projects/compare/components/revision_dropdown_spec.js3
-rw-r--r--spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap33
-rw-r--r--spec/frontend/projects/components/project_delete_button_spec.js5
-rw-r--r--spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap54
-rw-r--r--spec/frontend/projects/components/shared/delete_button_spec.js30
-rw-r--r--spec/frontend/projects/new/components/deployment_target_select_spec.js82
-rw-r--r--spec/frontend/projects/new/components/new_project_url_select_spec.js9
-rw-r--r--spec/frontend/projects/pipelines/charts/components/app_spec.js7
-rw-r--r--spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_charts_spec.js43
-rw-r--r--spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js12
-rw-r--r--spec/frontend/projects/project_find_file_spec.js5
-rw-r--r--spec/frontend/projects/settings/components/shared_runners_toggle_spec.js3
-rw-r--r--spec/frontend/projects/settings/components/transfer_project_form_spec.js10
-rw-r--r--spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js2
-rw-r--r--spec/frontend/prometheus_alerts/components/reset_key_spec.js44
-rw-r--r--spec/frontend/prometheus_metrics/prometheus_metrics_spec.js30
-rw-r--r--spec/frontend/ref/components/ref_selector_spec.js41
-rw-r--r--spec/frontend/related_issues/components/related_issuable_input_spec.js3
-rw-r--r--spec/frontend/releases/components/app_edit_new_spec.js3
-rw-r--r--spec/frontend/releases/components/app_index_apollo_client_spec.js14
-rw-r--r--spec/frontend/releases/components/app_show_spec.js13
-rw-r--r--spec/frontend/releases/components/asset_links_form_spec.js7
-rw-r--r--spec/frontend/releases/components/evidence_block_spec.js8
-rw-r--r--spec/frontend/releases/components/release_block_footer_spec.js5
-rw-r--r--spec/frontend/releases/components/release_block_milestone_info_spec.js30
-rw-r--r--spec/frontend/releases/components/release_block_spec.js5
-rw-r--r--spec/frontend/releases/components/releases_pagination_spec.js7
-rw-r--r--spec/frontend/releases/components/releases_sort_spec.js7
-rw-r--r--spec/frontend/releases/components/tag_field_exsting_spec.js7
-rw-r--r--spec/frontend/releases/components/tag_field_new_spec.js4
-rw-r--r--spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js14
-rw-r--r--spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js10
-rw-r--r--spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js7
-rw-r--r--spec/frontend/reports/components/report_section_spec.js92
-rw-r--r--spec/frontend/reports/grouped_test_report/components/test_issue_body_spec.js7
-rw-r--r--spec/frontend/reports/grouped_test_report/grouped_test_reports_app_spec.js7
-rw-r--r--spec/frontend/repository/components/blob_content_viewer_spec.js92
-rw-r--r--spec/frontend/repository/components/blob_controls_spec.js9
-rw-r--r--spec/frontend/repository/components/blob_viewers/download_viewer_spec.js37
-rw-r--r--spec/frontend/repository/components/blob_viewers/image_viewer_spec.js12
-rw-r--r--spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js41
-rw-r--r--spec/frontend/repository/components/blob_viewers/pdf_viewer_spec.js10
-rw-r--r--spec/frontend/repository/components/blob_viewers/video_viewer_spec.js6
-rw-r--r--spec/frontend/repository/components/breadcrumbs_spec.js9
-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/repository/components/upload_blob_modal_spec.js5
-rw-r--r--spec/frontend/repository/mock_data.js5
-rw-r--r--spec/frontend/runner/admin_runner_edit/admin_runner_edit_app_spec.js14
-rw-r--r--spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js146
-rw-r--r--spec/frontend/runner/admin_runners/admin_runners_app_spec.js51
-rw-r--r--spec/frontend/runner/components/cells/link_cell_spec.js72
-rw-r--r--spec/frontend/runner/components/cells/runner_actions_cell_spec.js201
-rw-r--r--spec/frontend/runner/components/registration/registration_dropdown_spec.js15
-rw-r--r--spec/frontend/runner/components/registration/registration_token_reset_dropdown_item_spec.js15
-rw-r--r--spec/frontend/runner/components/registration/registration_token_spec.js20
-rw-r--r--spec/frontend/runner/components/runner_assigned_item_spec.js53
-rw-r--r--spec/frontend/runner/components/runner_details_spec.js189
-rw-r--r--spec/frontend/runner/components/runner_edit_button_spec.js41
-rw-r--r--spec/frontend/runner/components/runner_filtered_search_bar_spec.js41
-rw-r--r--spec/frontend/runner/components/runner_groups_spec.js67
-rw-r--r--spec/frontend/runner/components/runner_header_spec.js44
-rw-r--r--spec/frontend/runner/components/runner_jobs_spec.js156
-rw-r--r--spec/frontend/runner/components/runner_jobs_table_spec.js119
-rw-r--r--spec/frontend/runner/components/runner_list_spec.js50
-rw-r--r--spec/frontend/runner/components/runner_pagination_spec.js1
-rw-r--r--spec/frontend/runner/components/runner_pause_button_spec.js239
-rw-r--r--spec/frontend/runner/components/runner_projects_spec.js193
-rw-r--r--spec/frontend/runner/components/runner_type_tabs_spec.js22
-rw-r--r--spec/frontend/runner/components/runner_update_form_spec.js46
-rw-r--r--spec/frontend/runner/group_runners/group_runners_app_spec.js79
-rw-r--r--spec/frontend/runner/mock_data.js10
-rw-r--r--spec/frontend/runner/utils_spec.js65
-rw-r--r--spec/frontend/search/sidebar/components/confidentiality_filter_spec.js7
-rw-r--r--spec/frontend/search/sidebar/components/radio_filter_spec.js7
-rw-r--r--spec/frontend/search/sidebar/components/status_filter_spec.js7
-rw-r--r--spec/frontend/search/sort/components/app_spec.js7
-rw-r--r--spec/frontend/security_configuration/components/app_spec.js24
-rw-r--r--spec/frontend/security_configuration/components/feature_card_spec.js1
-rw-r--r--spec/frontend/security_configuration/components/training_provider_list_spec.js200
-rw-r--r--spec/frontend/security_configuration/components/upgrade_banner_spec.js85
-rw-r--r--spec/frontend/security_configuration/mock_data.js57
-rw-r--r--spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap2
-rw-r--r--spec/frontend/serverless/components/function_details_spec.js11
-rw-r--r--spec/frontend/serverless/components/functions_spec.js52
-rw-r--r--spec/frontend/serverless/survey_banner_spec.js51
-rw-r--r--spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js19
-rw-r--r--spec/frontend/settings_panels_spec.js14
-rw-r--r--spec/frontend/sidebar/assignees_realtime_spec.js7
-rw-r--r--spec/frontend/sidebar/assignees_spec.js8
-rw-r--r--spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js29
-rw-r--r--spec/frontend/sidebar/components/assignees/sidebar_editable_item_spec.js11
-rw-r--r--spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js5
-rw-r--r--spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js9
-rw-r--r--spec/frontend/sidebar/components/mock_data.js2
-rw-r--r--spec/frontend/sidebar/components/participants/sidebar_participants_widget_spec.js5
-rw-r--r--spec/frontend/sidebar/components/severity/sidebar_severity_spec.js8
-rw-r--r--spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js17
-rw-r--r--spec/frontend/sidebar/components/time_tracking/report_spec.js7
-rw-r--r--spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js15
-rw-r--r--spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js10
-rw-r--r--spec/frontend/sidebar/lock/edit_form_buttons_spec.js25
-rw-r--r--spec/frontend/sidebar/lock/issuable_lock_form_spec.js22
-rw-r--r--spec/frontend/sidebar/mock_data.js10
-rw-r--r--spec/frontend/sidebar/participants_spec.js40
-rw-r--r--spec/frontend/sidebar/sidebar_assignees_spec.js8
-rw-r--r--spec/frontend/sidebar/sidebar_move_issue_spec.js54
-rw-r--r--spec/frontend/sidebar/todo_spec.js8
-rw-r--r--spec/frontend/snippets/components/edit_spec.js14
-rw-r--r--spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js3
-rw-r--r--spec/frontend/snippets/components/snippet_blob_view_spec.js38
-rw-r--r--spec/frontend/snippets/components/snippet_header_spec.js25
-rw-r--r--spec/frontend/static_site_editor/components/edit_meta_controls_spec.js5
-rw-r--r--spec/frontend/static_site_editor/components/edit_meta_modal_spec.js15
-rw-r--r--spec/frontend/static_site_editor/pages/home_spec.js28
-rw-r--r--spec/frontend/terraform/components/states_table_actions_spec.js11
-rw-r--r--spec/frontend/terraform/components/states_table_spec.js9
-rw-r--r--spec/frontend/terraform/components/terraform_list_spec.js8
-rw-r--r--spec/frontend/toggles/index_spec.js149
-rw-r--r--spec/frontend/token_access/token_access_spec.js6
-rw-r--r--spec/frontend/tooltips/components/tooltips_spec.js35
-rw-r--r--spec/frontend/tooltips/index_spec.js15
-rw-r--r--spec/frontend/user_lists/components/add_user_modal_spec.js5
-rw-r--r--spec/frontend/user_lists/components/edit_user_list_spec.js18
-rw-r--r--spec/frontend/user_lists/components/new_user_list_spec.js14
-rw-r--r--spec/frontend/user_lists/components/user_list_spec.js22
-rw-r--r--spec/frontend/user_lists/components/user_lists_spec.js8
-rw-r--r--spec/frontend/user_lists/components/user_lists_table_spec.js28
-rw-r--r--spec/frontend/vue_alerts_spec.js7
-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/mr_widget_memory_usage_spec.js32
-rw-r--r--spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap2
-rw-r--r--spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js10
-rw-r--r--spec/frontend/vue_mr_widget/components/states/merge_failed_pipeline_confirmation_dialog_spec.js78
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js39
-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_merged_spec.js27
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_merging_spec.js25
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_missing_branch_spec.js7
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js4
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js218
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js7
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js26
-rw-r--r--spec/frontend/vue_mr_widget/components/terraform/mr_widget_terraform_container_spec.js14
-rw-r--r--spec/frontend/vue_mr_widget/deployment/deployment_actions_spec.js64
-rw-r--r--spec/frontend/vue_mr_widget/deployment/deployment_mock_data.js3
-rw-r--r--spec/frontend/vue_mr_widget/extentions/accessibility/index_spec.js125
-rw-r--r--spec/frontend/vue_mr_widget/extentions/accessibility/mock_data.js137
-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.js23
-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/__snapshots__/simple_viewer_spec.js.snap4
-rw-r--r--spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js32
-rw-r--r--spec/frontend/vue_shared/components/chronic_duration_input_spec.js25
-rw-r--r--spec/frontend/vue_shared/components/confirm_fork_modal_spec.js80
-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/design_management/design_note_pin_spec.js68
-rw-r--r--spec/frontend/vue_shared/components/diff_viewer/diff_viewer_spec.js44
-rw-r--r--spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js120
-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/expand_button_spec.js39
-rw-r--r--spec/frontend/vue_shared/components/file_finder/index_spec.js152
-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.js141
-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.js35
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/release_token_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap54
-rw-r--r--spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js34
-rw-r--r--spec/frontend/vue_shared/components/gfm_autocomplete/utils_spec.js427
-rw-r--r--spec/frontend/vue_shared/components/gl_countdown_spec.js20
-rw-r--r--spec/frontend/vue_shared/components/gl_modal_vuex_spec.js24
-rw-r--r--spec/frontend/vue_shared/components/help_popover_spec.js110
-rw-r--r--spec/frontend/vue_shared/components/local_storage_sync_spec.js27
-rw-r--r--spec/frontend/vue_shared/components/markdown/field_spec.js111
-rw-r--r--spec/frontend/vue_shared/components/markdown/header_spec.js49
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestions_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/modal_copy_button_spec.js21
-rw-r--r--spec/frontend/vue_shared/components/multiselect_dropdown_spec.js35
-rw-r--r--spec/frontend/vue_shared/components/namespace_select/mock_data.js9
-rw-r--r--spec/frontend/vue_shared/components/namespace_select/namespace_select_spec.js151
-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/pikaday_spec.js41
-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/resizable_chart/resizable_chart_container_spec.js15
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js20
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js68
-rw-r--r--spec/frontend/vue_shared/components/sidebar/date_picker_spec.js125
-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/labels_select_widget/labels_select_root_spec.js12
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js3
-rw-r--r--spec/frontend/vue_shared/components/sidebar/toggle_sidebar_spec.js3
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js (renamed from spec/frontend/vue_shared/components/source_viewer_spec.js)43
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/utils_spec.js13
-rw-r--r--spec/frontend/vue_shared/components/split_button_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap7
-rw-r--r--spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js99
-rw-r--r--spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/user_select_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/web_ide_link_spec.js51
-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.js33
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js15
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_tabs_spec.js86
-rw-r--r--spec/frontend/vue_shared/issuable/list/mock_data.js2
-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.js6
-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.js8
-rw-r--r--spec/frontend/vue_shared/new_namespace/components/welcome_spec.js5
-rw-r--r--spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js6
-rw-r--r--spec/frontend/vue_shared/security_reports/mock_data.js2
-rw-r--r--spec/frontend/whats_new/components/app_spec.js11
-rw-r--r--spec/frontend/work_items/mock_data.js14
-rw-r--r--spec/frontend/work_items/pages/create_work_item_spec.js146
-rw-r--r--spec/frontend/work_items/pages/work_item_root_spec.js6
-rw-r--r--spec/frontend/work_items/router_spec.js10
-rw-r--r--spec/frontend/work_items_hierarchy/components/app_spec.js63
-rw-r--r--spec/frontend/work_items_hierarchy/components/hierarchy_spec.js118
-rw-r--r--spec/frontend/work_items_hierarchy/hierarchy_util_spec.js16
-rw-r--r--spec/frontend/zen_mode_spec.js2
794 files changed, 16362 insertions, 10374 deletions
diff --git a/spec/frontend/__helpers__/matchers/to_match_interpolated_text.js b/spec/frontend/__helpers__/matchers/to_match_interpolated_text.js
index 4ce814a01b4..41e69bffd88 100644
--- a/spec/frontend/__helpers__/matchers/to_match_interpolated_text.js
+++ b/spec/frontend/__helpers__/matchers/to_match_interpolated_text.js
@@ -1,4 +1,5 @@
-export const toMatchInterpolatedText = (received, match) => {
+// Custom matchers are object methods and should be traditional functions to be able to access `utils` on `this`
+export function toMatchInterpolatedText(received, match) {
let clearReceived;
let clearMatch;
@@ -15,16 +16,14 @@ export const toMatchInterpolatedText = (received, match) => {
const pass = clearReceived === clearMatch;
const message = pass
? () => `
- \n\n
- Expected: ${this.utils.printExpected(clearReceived)}
- To not equal: ${this.utils.printReceived(clearMatch)}
+ Expected to not be: ${this.utils.printExpected(clearMatch)}
+ Received: ${this.utils.printReceived(clearReceived)}
`
: () =>
`
- \n\n
- Expected: ${this.utils.printExpected(clearReceived)}
- To equal: ${this.utils.printReceived(clearMatch)}
+ Expected to be: ${this.utils.printExpected(clearMatch)}
+ Received: ${this.utils.printReceived(clearReceived)}
`;
return { actual: received, message, pass };
-};
+}
diff --git a/spec/frontend/__helpers__/mock_apollo_helper.js b/spec/frontend/__helpers__/mock_apollo_helper.js
index ee4bbd42b1e..c07a6d8ef85 100644
--- a/spec/frontend/__helpers__/mock_apollo_helper.js
+++ b/spec/frontend/__helpers__/mock_apollo_helper.js
@@ -1,22 +1,25 @@
-import { InMemoryCache } from 'apollo-cache-inmemory';
+import { InMemoryCache } from '@apollo/client/core';
import { createMockClient as createMockApolloClient } from 'mock-apollo-client';
import VueApollo from 'vue-apollo';
-
-const defaultCacheOptions = {
- fragmentMatcher: { match: () => true },
- addTypename: false,
-};
+import possibleTypes from '~/graphql_shared/possibleTypes.json';
+import { typePolicies } from '~/lib/graphql';
export function createMockClient(handlers = [], resolvers = {}, cacheOptions = {}) {
const cache = new InMemoryCache({
- ...defaultCacheOptions,
+ possibleTypes,
+ typePolicies,
+ addTypename: false,
...cacheOptions,
});
const mockClient = createMockApolloClient({ cache, resolvers });
if (Array.isArray(handlers)) {
- handlers.forEach(([query, value]) => mockClient.setRequestHandler(query, value));
+ handlers.forEach(([query, value]) =>
+ mockClient.setRequestHandler(query, (...args) =>
+ Promise.resolve(value(...args)).then((r) => ({ ...r })),
+ ),
+ );
} else {
throw new Error('You should pass an array of handlers to mock Apollo client');
}
diff --git a/spec/frontend/__helpers__/test_apollo_link.js b/spec/frontend/__helpers__/test_apollo_link.js
index dde3a4e99bb..eab0c2de212 100644
--- a/spec/frontend/__helpers__/test_apollo_link.js
+++ b/spec/frontend/__helpers__/test_apollo_link.js
@@ -1,7 +1,4 @@
-import { InMemoryCache } from 'apollo-cache-inmemory';
-import { ApolloClient } from 'apollo-client';
-import { ApolloLink } from 'apollo-link';
-import gql from 'graphql-tag';
+import { InMemoryCache, ApolloClient, ApolloLink, gql } from '@apollo/client/core';
const FOO_QUERY = gql`
query {
diff --git a/spec/frontend/__mocks__/@gitlab/ui.js b/spec/frontend/__mocks__/@gitlab/ui.js
index 6b3f1f01e6a..6f2888e5c42 100644
--- a/spec/frontend/__mocks__/@gitlab/ui.js
+++ b/spec/frontend/__mocks__/@gitlab/ui.js
@@ -41,10 +41,15 @@ jest.mock('@gitlab/ui/dist/components/base/popover/popover.js', () => ({
default: () => [],
},
...Object.fromEntries(
- ['title', 'target', 'triggers', 'placement', 'boundary', 'container'].map((prop) => [
- prop,
- {},
- ]),
+ [
+ 'title',
+ 'target',
+ 'triggers',
+ 'placement',
+ 'boundary',
+ 'container',
+ 'showCloseButton',
+ ].map((prop) => [prop, {}]),
),
},
render(h) {
diff --git a/spec/frontend/actioncable_link_spec.js b/spec/frontend/actioncable_link_spec.js
index c785151f8fd..b15b93cb11d 100644
--- a/spec/frontend/actioncable_link_spec.js
+++ b/spec/frontend/actioncable_link_spec.js
@@ -1,5 +1,5 @@
import { print } from 'graphql';
-import gql from 'graphql-tag';
+import { gql } from '@apollo/client/core';
import cable from '~/actioncable_consumer';
import ActionCableLink from '~/actioncable_link';
diff --git a/spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap b/spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap
index 10437c48f88..82114077455 100644
--- a/spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap
+++ b/spec/frontend/add_context_commits_modal/components/__snapshots__/add_context_commits_modal_spec.js.snap
@@ -17,7 +17,6 @@ exports[`AddContextCommitsModal renders modal with 2 tabs 1`] = `
<gl-tabs-stub
contentclass="pt-0"
queryparamname="tab"
- theme="indigo"
value="0"
>
<gl-tab-stub
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 e7a20ae114c..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,5 +1,6 @@
import { GlModal, GlSearchBoxByType } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+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';
@@ -8,8 +9,7 @@ import * as actions from '~/add_context_commits_modal/store/actions';
import mutations from '~/add_context_commits_modal/store/mutations';
import defaultState from '~/add_context_commits_modal/store/state';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('AddContextCommitsModal', () => {
let wrapper;
@@ -36,7 +36,6 @@ describe('AddContextCommitsModal', () => {
});
wrapper = shallowMount(AddReviewItemsModal, {
- localVue,
store,
propsData: {
contextCommitsPath: '',
@@ -85,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();
});
});
@@ -101,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', () => {
@@ -115,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/admin/users/components/users_table_spec.js b/spec/frontend/admin/users/components/users_table_spec.js
index 9ff5961c7ec..ad1c45495b5 100644
--- a/spec/frontend/admin/users/components/users_table_spec.js
+++ b/spec/frontend/admin/users/components/users_table_spec.js
@@ -3,6 +3,7 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import AdminUserActions from '~/admin/users/components/user_actions.vue';
@@ -106,8 +107,9 @@ describe('AdminUsersTable component', () => {
});
describe('when the data has been fetched', () => {
- beforeEach(() => {
+ beforeEach(async () => {
initComponent();
+ await waitForPromises();
});
it("renders the user's group count", () => {
@@ -115,8 +117,9 @@ describe('AdminUsersTable component', () => {
});
describe("and a user's group count is null", () => {
- beforeEach(() => {
+ beforeEach(async () => {
initComponent({}, createFetchGroupCount([{ id: user.id, groupCount: null }]));
+ await waitForPromises();
});
it("renders the user's group count as 0", () => {
@@ -126,12 +129,12 @@ describe('AdminUsersTable component', () => {
});
describe('when there is an error while fetching the data', () => {
- beforeEach(() => {
+ beforeEach(async () => {
initComponent({}, fetchGroupCountsError);
+ await waitForPromises();
});
it('creates a flash message and captures the error', () => {
- expect(createFlash).toHaveBeenCalledTimes(1);
expect(createFlash).toHaveBeenCalledWith({
message: 'Could not load user group counts. Please refresh the page to try again.',
captureError: true,
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 e6a6e01c41c..6193233881d 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, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
-import { nextTick } from 'vue';
+import Vue 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';
@@ -50,8 +50,6 @@ import mockIntegrations from './mocks/integrations.json';
jest.mock('~/flash');
-const localVue = createLocalVue();
-
describe('AlertsSettingsWrapper', () => {
let wrapper;
let fakeApollo;
@@ -70,21 +68,12 @@ describe('AlertsSettingsWrapper', () => {
const findAlertsSettingsForm = () => wrapper.findComponent(AlertsSettingsForm);
const findAlert = () => wrapper.findComponent(GlAlert);
- async function destroyHttpIntegration(localWrapper) {
- await jest.runOnlyPendingTimers();
- await localWrapper.vm.$nextTick();
-
+ function destroyHttpIntegration(localWrapper) {
localWrapper
.find(IntegrationsList)
.vm.$emit('delete-integration', { id: integrationToDestroy.id });
}
- async function awaitApolloDomMock() {
- await nextTick(); // kick off the DOM update
- await jest.runOnlyPendingTimers(); // kick off the mocked GQL stuff (promises)
- await nextTick(); // kick off the DOM update for flash
- }
-
const createComponent = ({ data = {}, provide = {}, loading = false } = {}) => {
wrapper = extendedWrapper(
mount(AlertsSettingsWrapper, {
@@ -118,7 +107,7 @@ describe('AlertsSettingsWrapper', () => {
function createComponentWithApollo({
destroyHandler = jest.fn().mockResolvedValue(destroyIntegrationResponse),
} = {}) {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
destroyIntegrationHandler = destroyHandler;
const requestHandlers = [
@@ -129,7 +118,6 @@ describe('AlertsSettingsWrapper', () => {
fakeApollo = createMockApollo(requestHandlers);
wrapper = mount(AlertsSettingsWrapper, {
- localVue,
apolloProvider: fakeApollo,
provide: {
alertSettings: {
@@ -476,21 +464,19 @@ describe('AlertsSettingsWrapper', () => {
describe('with mocked Apollo client', () => {
it('has a selection of integrations loaded via the getIntegrationsQuery', async () => {
createComponentWithApollo();
-
- await jest.runOnlyPendingTimers();
- await nextTick();
+ await waitForPromises();
expect(findIntegrations()).toHaveLength(4);
});
it('calls a mutation with correct parameters and destroys a integration', async () => {
createComponentWithApollo();
+ await waitForPromises();
- await destroyHttpIntegration(wrapper);
+ destroyHttpIntegration(wrapper);
expect(destroyIntegrationHandler).toHaveBeenCalled();
-
- await nextTick();
+ await waitForPromises();
expect(findIntegrations()).toHaveLength(3);
});
@@ -501,7 +487,7 @@ describe('AlertsSettingsWrapper', () => {
});
await destroyHttpIntegration(wrapper);
- await awaitApolloDomMock();
+ await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ message: 'Houston, we have a problem' });
});
@@ -512,7 +498,7 @@ describe('AlertsSettingsWrapper', () => {
});
await destroyHttpIntegration(wrapper);
- await awaitApolloDomMock();
+ await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({
message: DELETE_INTEGRATION_ERROR,
diff --git a/spec/frontend/alerts_settings/components/mocks/apollo_mock.js b/spec/frontend/alerts_settings/components/mocks/apollo_mock.js
index e7ad2cd1d2a..694dff56632 100644
--- a/spec/frontend/alerts_settings/components/mocks/apollo_mock.js
+++ b/spec/frontend/alerts_settings/components/mocks/apollo_mock.js
@@ -38,6 +38,7 @@ export const getIntegrationsQueryResponse = {
alertManagementIntegrations: {
nodes: [
{
+ __typename: 'AlertManagementIntegration',
id: '37',
type: 'HTTP',
active: true,
@@ -48,6 +49,7 @@ export const getIntegrationsQueryResponse = {
apiUrl: null,
},
{
+ __typename: 'AlertManagementIntegration',
id: '41',
type: 'HTTP',
active: true,
@@ -58,6 +60,7 @@ export const getIntegrationsQueryResponse = {
apiUrl: null,
},
{
+ __typename: 'AlertManagementIntegration',
id: '40',
type: 'HTTP',
active: true,
@@ -68,6 +71,7 @@ export const getIntegrationsQueryResponse = {
apiUrl: null,
},
{
+ __typename: 'AlertManagementIntegration',
id: '12',
type: 'PROMETHEUS',
active: false,
@@ -83,6 +87,7 @@ export const getIntegrationsQueryResponse = {
};
export const integrationToDestroy = {
+ __typename: 'AlertManagementIntegration',
id: '37',
type: 'HTTP',
active: true,
@@ -97,6 +102,7 @@ export const destroyIntegrationResponse = {
httpIntegrationDestroy: {
errors: [],
integration: {
+ __typename: 'AlertManagementIntegration',
id: '37',
type: 'HTTP',
active: true,
diff --git a/spec/frontend/analytics/shared/components/daterange_spec.js b/spec/frontend/analytics/shared/components/daterange_spec.js
index 854582abb82..a38df274243 100644
--- a/spec/frontend/analytics/shared/components/daterange_spec.js
+++ b/spec/frontend/analytics/shared/components/daterange_spec.js
@@ -1,7 +1,6 @@
-import { GlDaterangePicker } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
+import { GlDaterangePicker, GlSprintf } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
import { useFakeDate } from 'helpers/fake_date';
-import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import Daterange from '~/analytics/shared/components/daterange.vue';
const defaultProps = {
@@ -14,13 +13,13 @@ describe('Daterange component', () => {
let wrapper;
- const factory = (props = defaultProps) => {
- wrapper = mount(Daterange, {
+ const factory = (props = defaultProps, mountFn = shallowMount) => {
+ wrapper = mountFn(Daterange, {
propsData: {
...defaultProps,
...props,
},
- directives: { GlTooltip: createMockDirective() },
+ stubs: { GlSprintf },
});
};
@@ -28,9 +27,8 @@ describe('Daterange component', () => {
wrapper.destroy();
});
- const findDaterangePicker = () => wrapper.find(GlDaterangePicker);
-
- const findDateRangeIndicator = () => wrapper.find('.daterange-indicator');
+ const findDaterangePicker = () => wrapper.findComponent(GlDaterangePicker);
+ const findDateRangeIndicator = () => wrapper.findComponent(GlSprintf);
describe('template', () => {
describe('when show is false', () => {
@@ -43,26 +41,24 @@ describe('Daterange component', () => {
describe('when show is true', () => {
it('renders the daterange picker', () => {
factory({ show: true });
+
expect(findDaterangePicker().exists()).toBe(true);
});
});
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');
- factory({ show: true, startDate, endDate, minDate });
-
+ factory({ show: true, startDate, endDate, minDate }, mount);
const input = findDaterangePicker().find('input');
input.setValue('2019-01-01');
- input.trigger('change');
+ await input.trigger('change');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().change).toEqual([[{ startDate: minDate, endDate }]]);
- });
+ expect(wrapper.emitted().change).toEqual([[{ startDate: minDate, endDate }]]);
});
});
@@ -76,16 +72,13 @@ describe('Daterange component', () => {
});
it('displays the correct number of selected days in the indicator', () => {
- expect(findDateRangeIndicator().find('span').text()).toBe('10 days selected');
+ expect(findDateRangeIndicator().text()).toMatchInterpolatedText('10 days selected');
});
- it('displays a tooltip', () => {
- const icon = wrapper.find('[data-testid="helper-icon"]');
- const tooltip = getBinding(icon.element, 'gl-tooltip');
-
- expect(tooltip).toBeDefined();
- expect(icon.attributes('title')).toBe(
- 'Showing data for workflow items created in this date range. Date range cannot exceed 30 days.',
+ it('sets the tooltip', () => {
+ const tooltip = findDaterangePicker().props('tooltip');
+ expect(tooltip).toBe(
+ 'Showing data for workflow items created in this date range. Date range limited to 30 days.',
);
});
});
diff --git a/spec/frontend/cycle_analytics/metric_popover_spec.js b/spec/frontend/analytics/shared/components/metric_popover_spec.js
index 5a622fcacd5..b799c911488 100644
--- a/spec/frontend/cycle_analytics/metric_popover_spec.js
+++ b/spec/frontend/analytics/shared/components/metric_popover_spec.js
@@ -1,6 +1,6 @@
import { GlLink, GlIcon } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import MetricPopover from '~/cycle_analytics/components/metric_popover.vue';
+import MetricPopover from '~/analytics/shared/components/metric_popover.vue';
const MOCK_METRIC = {
key: 'deployment-frequency',
diff --git a/spec/frontend/analytics/shared/components/metric_tile_spec.js b/spec/frontend/analytics/shared/components/metric_tile_spec.js
new file mode 100644
index 00000000000..980dfad9eb0
--- /dev/null
+++ b/spec/frontend/analytics/shared/components/metric_tile_spec.js
@@ -0,0 +1,81 @@
+import { GlSingleStat } from '@gitlab/ui/dist/charts';
+import { shallowMount } from '@vue/test-utils';
+import MetricTile from '~/analytics/shared/components/metric_tile.vue';
+import MetricPopover from '~/analytics/shared/components/metric_popover.vue';
+import { redirectTo } from '~/lib/utils/url_utility';
+
+jest.mock('~/lib/utils/url_utility');
+
+describe('MetricTile', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ return shallowMount(MetricTile, {
+ propsData: {
+ metric: {},
+ ...props,
+ },
+ });
+ };
+
+ const findSingleStat = () => wrapper.findComponent(GlSingleStat);
+ const findPopover = () => wrapper.findComponent(MetricPopover);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('template', () => {
+ describe('links', () => {
+ it('when the metric has links, it redirects the user on click', () => {
+ const metric = {
+ identifier: 'deploys',
+ value: '10',
+ label: 'Deploys',
+ links: [{ url: 'foo/bar' }],
+ };
+ wrapper = createComponent({ metric });
+
+ const singleStat = findSingleStat();
+ singleStat.vm.$emit('click');
+ expect(redirectTo).toHaveBeenCalledWith('foo/bar');
+ });
+
+ it("when the metric doesn't have links, it won't the user on click", () => {
+ const metric = { identifier: 'deploys', value: '10', label: 'Deploys' };
+ wrapper = createComponent({ metric });
+
+ const singleStat = findSingleStat();
+ singleStat.vm.$emit('click');
+ expect(redirectTo).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('decimal places', () => {
+ it(`will render 0 decimal places for an integer value`, () => {
+ const metric = { identifier: 'deploys', value: '10', label: 'Deploys' };
+ wrapper = createComponent({ metric });
+
+ const singleStat = findSingleStat();
+ expect(singleStat.props('animationDecimalPlaces')).toBe(0);
+ });
+
+ it(`will render 1 decimal place for a float value`, () => {
+ const metric = { identifier: 'deploys', value: '10.5', label: 'Deploys' };
+ wrapper = createComponent({ metric });
+
+ const singleStat = findSingleStat();
+ expect(singleStat.props('animationDecimalPlaces')).toBe(1);
+ });
+ });
+
+ it('renders a metric popover', () => {
+ const metric = { identifier: 'deploys', value: '10', label: 'Deploys' };
+ wrapper = createComponent({ metric });
+
+ const popover = findPopover();
+ expect(popover.exists()).toBe(true);
+ expect(popover.props()).toMatchObject({ metric, target: metric.identifier });
+ });
+ });
+});
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/shared/utils_spec.js b/spec/frontend/analytics/shared/utils_spec.js
index 0513ccb2890..b48e2d971b5 100644
--- a/spec/frontend/analytics/shared/utils_spec.js
+++ b/spec/frontend/analytics/shared/utils_spec.js
@@ -1,9 +1,12 @@
+import metricsData from 'test_fixtures/projects/analytics/value_stream_analytics/summary.json';
import {
filterBySearchTerm,
extractFilterQueryParameters,
extractPaginationQueryParameters,
getDataZoomOption,
+ prepareTimeMetricsData,
} from '~/analytics/shared/utils';
+import { slugify } from '~/lib/utils/text_utility';
import { objectToQuery } from '~/lib/utils/url_utility';
describe('filterBySearchTerm', () => {
@@ -176,3 +179,36 @@ describe('getDataZoomOption', () => {
});
});
});
+
+describe('prepareTimeMetricsData', () => {
+ let prepared;
+ const [first, second] = metricsData;
+ delete second.identifier; // testing the case when identifier is missing
+
+ const firstIdentifier = first.identifier;
+ const secondIdentifier = slugify(second.title);
+
+ beforeEach(() => {
+ prepared = prepareTimeMetricsData([first, second], {
+ [firstIdentifier]: { description: 'Is a value that is good' },
+ });
+ });
+
+ it('will add a `identifier` based on the title', () => {
+ expect(prepared).toMatchObject([
+ { identifier: firstIdentifier },
+ { identifier: secondIdentifier },
+ ]);
+ });
+
+ it('will add a `label` key', () => {
+ expect(prepared).toMatchObject([{ label: 'New Issues' }, { label: 'Commits' }]);
+ });
+
+ it('will add a popover description using the key if it is provided', () => {
+ expect(prepared).toMatchObject([
+ { description: 'Is a value that is good' },
+ { description: '' },
+ ]);
+ });
+});
diff --git a/spec/frontend/analytics/usage_trends/apollo_mock_data.js b/spec/frontend/analytics/usage_trends/apollo_mock_data.js
index 98eabd577ee..934bbc63689 100644
--- a/spec/frontend/analytics/usage_trends/apollo_mock_data.js
+++ b/spec/frontend/analytics/usage_trends/apollo_mock_data.js
@@ -1,4 +1,5 @@
const defaultPageInfo = {
+ __typename: 'PageInfo',
hasNextPage: false,
hasPreviousPage: false,
startCursor: null,
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 1a331100bb8..02cf7f42a0b 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,9 +1,10 @@
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';
import UsageTrendsCountChart from '~/analytics/usage_trends/components/usage_trends_count_chart.vue';
import statsQuery from '~/analytics/usage_trends/graphql/queries/usage_count.query.graphql';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
@@ -77,9 +78,10 @@ describe('UsageTrendsCountChart', () => {
});
describe('without data', () => {
- beforeEach(() => {
+ beforeEach(async () => {
queryHandler = mockQueryResponse({ key: queryResponseDataKey, data: [] });
wrapper = createComponent({ responseHandler: queryHandler });
+ await waitForPromises();
});
it('renders an no data message', () => {
@@ -96,9 +98,10 @@ describe('UsageTrendsCountChart', () => {
});
describe('with data', () => {
- beforeEach(() => {
+ beforeEach(async () => {
queryHandler = mockQueryResponse({ key: queryResponseDataKey, data: mockCountsData1 });
wrapper = createComponent({ responseHandler: queryHandler });
+ await waitForPromises();
});
it('requests data', () => {
@@ -126,7 +129,7 @@ describe('UsageTrendsCountChart', () => {
const recordedAt = '2020-08-01';
describe('when the fetchMore query returns data', () => {
beforeEach(async () => {
- const newData = [{ recordedAt, count: 5 }];
+ const newData = [{ __typename: 'UsageTrendsMeasurement', recordedAt, count: 5 }];
queryHandler = mockQueryResponse({
key: queryResponseDataKey,
data: mockCountsData1,
@@ -134,7 +137,7 @@ describe('UsageTrendsCountChart', () => {
});
wrapper = createComponent({ responseHandler: queryHandler });
- await wrapper.vm.$nextTick();
+ await waitForPromises();
});
it('requests data twice', () => {
@@ -161,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..32a664a5026 100644
--- a/spec/frontend/analytics/usage_trends/components/users_chart_spec.js
+++ b/spec/frontend/analytics/usage_trends/components/users_chart_spec.js
@@ -1,9 +1,10 @@
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 waitForPromises from 'helpers/wait_for_promises';
import UsersChart from '~/analytics/usage_trends/components/users_chart.vue';
import usersQuery from '~/analytics/usage_trends/graphql/queries/users.query.graphql';
import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
@@ -67,7 +68,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 +87,7 @@ describe('UsersChart', () => {
describe('with data', () => {
beforeEach(async () => {
wrapper = createComponent({ users: mockCountsData2 });
- await wrapper.vm.$nextTick();
+ await waitForPromises();
});
it('hides the skeleton loader', () => {
@@ -107,7 +108,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 +135,7 @@ describe('UsersChart', () => {
});
jest.spyOn(wrapper.vm.$apollo.queries.users, 'fetchMore');
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('requests data twice', () => {
@@ -147,7 +148,7 @@ describe('UsersChart', () => {
});
describe('when the fetchMore query throws an error', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper = createComponent({
users: mockCountsData2,
additionalData: mockCountsData1,
@@ -156,7 +157,7 @@ describe('UsersChart', () => {
jest
.spyOn(wrapper.vm.$apollo.queries.users, 'fetchMore')
.mockImplementation(jest.fn().mockRejectedValue());
- return wrapper.vm.$nextTick();
+ await waitForPromises();
});
it('calls fetchMore', () => {
diff --git a/spec/frontend/analytics/usage_trends/mock_data.js b/spec/frontend/analytics/usage_trends/mock_data.js
index d96dfa26209..77bd44d17f5 100644
--- a/spec/frontend/analytics/usage_trends/mock_data.js
+++ b/spec/frontend/analytics/usage_trends/mock_data.js
@@ -4,11 +4,11 @@ export const mockUsageCounts = [
];
export const mockCountsData1 = [
- { recordedAt: '2020-07-23', count: 52 },
- { recordedAt: '2020-07-22', count: 40 },
- { recordedAt: '2020-07-21', count: 31 },
- { recordedAt: '2020-06-14', count: 23 },
- { recordedAt: '2020-06-12', count: 20 },
+ { __typename: 'UsageTrendsMeasurement', recordedAt: '2020-07-23', count: 52 },
+ { __typename: 'UsageTrendsMeasurement', recordedAt: '2020-07-22', count: 40 },
+ { __typename: 'UsageTrendsMeasurement', recordedAt: '2020-07-21', count: 31 },
+ { __typename: 'UsageTrendsMeasurement', recordedAt: '2020-06-14', count: 23 },
+ { __typename: 'UsageTrendsMeasurement', recordedAt: '2020-06-12', count: 20 },
];
export const countsMonthlyChartData1 = [
@@ -17,11 +17,11 @@ export const countsMonthlyChartData1 = [
];
export const mockCountsData2 = [
- { recordedAt: '2020-07-28', count: 10 },
- { recordedAt: '2020-07-27', count: 9 },
- { recordedAt: '2020-06-26', count: 14 },
- { recordedAt: '2020-06-25', count: 23 },
- { recordedAt: '2020-06-24', count: 25 },
+ { __typename: 'UsageTrendsMeasurement', recordedAt: '2020-07-28', count: 10 },
+ { __typename: 'UsageTrendsMeasurement', recordedAt: '2020-07-27', count: 9 },
+ { __typename: 'UsageTrendsMeasurement', recordedAt: '2020-06-26', count: 14 },
+ { __typename: 'UsageTrendsMeasurement', recordedAt: '2020-06-25', count: 23 },
+ { __typename: 'UsageTrendsMeasurement', recordedAt: '2020-06-24', count: 25 },
];
export const countsMonthlyChartData2 = [
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 bfa8274f0eb..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,5 +1,6 @@
import { GlFormCheckbox, GlLink } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+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';
@@ -7,8 +8,7 @@ import GetKeepLatestArtifactApplicationSetting from '~/artifacts_settings/graphq
import GetKeepLatestArtifactProjectSetting from '~/artifacts_settings/graphql/queries/get_keep_latest_artifact_project_setting.query.graphql';
import KeepLatestArtifactCheckbox from '~/artifacts_settings/keep_latest_artifact_checkbox.vue';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
const keepLatestArtifactProjectMock = {
data: {
@@ -73,7 +73,6 @@ describe('Keep latest artifact checkbox', () => {
stubs: {
GlFormCheckbox,
},
- localVue,
apolloProvider,
});
};
@@ -110,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/authentication/webauthn/util_spec.js b/spec/frontend/authentication/webauthn/util_spec.js
new file mode 100644
index 00000000000..c9b8bfd8679
--- /dev/null
+++ b/spec/frontend/authentication/webauthn/util_spec.js
@@ -0,0 +1,19 @@
+import { base64ToBuffer, bufferToBase64 } from '~/authentication/webauthn/util';
+
+const encodedString = 'SGVsbG8gd29ybGQh';
+const stringBytes = [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33];
+
+describe('Webauthn utils', () => {
+ it('base64ToBuffer', () => {
+ const toArray = (val) => new Uint8Array(val);
+
+ expect(base64ToBuffer(encodedString)).toBeInstanceOf(ArrayBuffer);
+
+ expect(toArray(base64ToBuffer(encodedString))).toEqual(toArray(stringBytes));
+ });
+
+ it('bufferToBase64', () => {
+ const buffer = base64ToBuffer(encodedString);
+ expect(bufferToBase64(buffer)).toBe(encodedString);
+ });
+});
diff --git a/spec/frontend/badges/components/badge_form_spec.js b/spec/frontend/badges/components/badge_form_spec.js
index e375fcb4705..ba2ec775b61 100644
--- a/spec/frontend/badges/components/badge_form_spec.js
+++ b/spec/frontend/badges/components/badge_form_spec.js
@@ -1,5 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { DUMMY_IMAGE_URL, TEST_HOST } from 'helpers/test_constants';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import BadgeForm from '~/badges/components/badge_form.vue';
@@ -74,7 +74,7 @@ describe('BadgeForm component', () => {
expect(feedbackElement).toBeVisible();
};
- beforeEach((done) => {
+ beforeEach(async () => {
jest.spyOn(vm, submitAction).mockReturnValue(Promise.resolve());
store.replaceState({
...store.state,
@@ -83,14 +83,10 @@ describe('BadgeForm component', () => {
isSaving: false,
});
- Vue.nextTick()
- .then(() => {
- setValue(nameSelector, 'TestBadge');
- setValue(linkUrlSelector, `${TEST_HOST}/link/url`);
- setValue(imageUrlSelector, `${window.location.origin}${DUMMY_IMAGE_URL}`);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ setValue(nameSelector, 'TestBadge');
+ setValue(linkUrlSelector, `${TEST_HOST}/link/url`);
+ setValue(imageUrlSelector, `${window.location.origin}${DUMMY_IMAGE_URL}`);
});
it('returns immediately if imageUrl is empty', () => {
diff --git a/spec/frontend/badges/components/badge_list_row_spec.js b/spec/frontend/badges/components/badge_list_row_spec.js
index 372663017e2..0fb0fa86a02 100644
--- a/spec/frontend/badges/components/badge_list_row_spec.js
+++ b/spec/frontend/badges/components/badge_list_row_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import BadgeListRow from '~/badges/components/badge_list_row.vue';
import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
@@ -73,25 +73,21 @@ describe('BadgeListRow component', () => {
expect(vm.editBadge).toHaveBeenCalled();
});
- it('calls updateBadgeInModal and shows modal when clicking then delete button', (done) => {
+ it('calls updateBadgeInModal and shows modal when clicking then delete button', async () => {
jest.spyOn(vm, 'updateBadgeInModal').mockImplementation(() => {});
const deleteButton = vm.$el.querySelector('.table-button-footer button:last-of-type');
deleteButton.click();
- Vue.nextTick()
- .then(() => {
- expect(vm.updateBadgeInModal).toHaveBeenCalled();
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.updateBadgeInModal).toHaveBeenCalled();
});
describe('for a group badge', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
badge.kind = GROUP_BADGE;
- Vue.nextTick().then(done).catch(done.fail);
+ await nextTick();
});
it('renders the badge kind', () => {
diff --git a/spec/frontend/badges/components/badge_list_spec.js b/spec/frontend/badges/components/badge_list_spec.js
index 6cc90c6de46..39fa502b207 100644
--- a/spec/frontend/badges/components/badge_list_spec.js
+++ b/spec/frontend/badges/components/badge_list_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import BadgeList from '~/badges/components/badge_list.vue';
import { GROUP_BADGE, PROJECT_BADGE } from '~/badges/constants';
@@ -48,46 +48,34 @@ describe('BadgeList component', () => {
expect(rows).toHaveLength(numberOfDummyBadges);
});
- it('renders a message if no badges exist', (done) => {
+ it('renders a message if no badges exist', async () => {
store.state.badges = [];
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.innerText).toMatch('This project has no badges');
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.innerText).toMatch('This project has no badges');
});
- it('shows a loading icon when loading', (done) => {
+ it('shows a loading icon when loading', async () => {
store.state.isLoading = true;
- Vue.nextTick()
- .then(() => {
- const loadingIcon = vm.$el.querySelector('.gl-spinner');
+ await nextTick();
+ const loadingIcon = vm.$el.querySelector('.gl-spinner');
- expect(loadingIcon).toBeVisible();
- })
- .then(done)
- .catch(done.fail);
+ expect(loadingIcon).toBeVisible();
});
describe('for group badges', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
store.state.kind = GROUP_BADGE;
- Vue.nextTick().then(done).catch(done.fail);
+ await nextTick();
});
- it('renders a message if no badges exist', (done) => {
+ it('renders a message if no badges exist', async () => {
store.state.badges = [];
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.innerText).toMatch('This group has no badges');
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.innerText).toMatch('This group has no badges');
});
});
});
diff --git a/spec/frontend/badges/components/badge_settings_spec.js b/spec/frontend/badges/components/badge_settings_spec.js
index 0c29379763e..79cf5f3e4ff 100644
--- a/spec/frontend/badges/components/badge_settings_spec.js
+++ b/spec/frontend/badges/components/badge_settings_spec.js
@@ -1,5 +1,6 @@
import { GlModal } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+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';
@@ -7,8 +8,7 @@ import BadgeSettings from '~/badges/components/badge_settings.vue';
import store from '~/badges/store';
import { createDummyBadge } from '../dummy_badge';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('BadgeSettings component', () => {
let wrapper;
@@ -21,7 +21,6 @@ describe('BadgeSettings component', () => {
wrapper = shallowMount(BadgeSettings, {
store,
- localVue,
stubs: {
'badge-list': BadgeList,
'badge-list-row': BadgeListRow,
@@ -41,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/badges/components/badge_spec.js b/spec/frontend/badges/components/badge_spec.js
index 990bc094d59..2310fb8bd8e 100644
--- a/spec/frontend/badges/components/badge_spec.js
+++ b/spec/frontend/badges/components/badge_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import { DUMMY_IMAGE_URL, TEST_HOST } from 'spec/test_constants';
import Badge from '~/badges/components/badge.vue';
@@ -27,7 +27,7 @@ describe('Badge component', () => {
badgeImage.addEventListener('load', resolve);
// Manually dispatch load event as it is not triggered
badgeImage.dispatchEvent(new Event('load'));
- }).then(() => Vue.nextTick());
+ }).then(() => nextTick());
};
afterEach(() => {
@@ -36,34 +36,25 @@ describe('Badge component', () => {
describe('watchers', () => {
describe('imageUrl', () => {
- it('sets isLoading and resets numRetries and hasError', (done) => {
+ it('sets isLoading and resets numRetries and hasError', async () => {
const props = { ...dummyProps };
- createComponent(props)
- .then(() => {
- expect(vm.isLoading).toBe(false);
- vm.hasError = true;
- vm.numRetries = 42;
-
- vm.imageUrl = `${props.imageUrl}#something/else`;
-
- return Vue.nextTick();
- })
- .then(() => {
- expect(vm.isLoading).toBe(true);
- expect(vm.numRetries).toBe(0);
- expect(vm.hasError).toBe(false);
- })
- .then(done)
- .catch(done.fail);
+ await createComponent(props);
+ expect(vm.isLoading).toBe(false);
+ vm.hasError = true;
+ vm.numRetries = 42;
+
+ vm.imageUrl = `${props.imageUrl}#something/else`;
+ await nextTick();
+ expect(vm.isLoading).toBe(true);
+ expect(vm.numRetries).toBe(0);
+ expect(vm.hasError).toBe(false);
});
});
});
describe('methods', () => {
- beforeEach((done) => {
- createComponent({ ...dummyProps })
- .then(done)
- .catch(done.fail);
+ beforeEach(async () => {
+ await createComponent({ ...dummyProps });
});
it('onError resets isLoading and sets hasError', () => {
@@ -116,37 +107,29 @@ describe('Badge component', () => {
expect(vm.$el.querySelector('.btn-group')).toBeHidden();
});
- it('shows a loading icon when loading', (done) => {
+ it('shows a loading icon when loading', async () => {
vm.isLoading = true;
- Vue.nextTick()
- .then(() => {
- const { badgeImage, loadingIcon, reloadButton } = findElements();
+ await nextTick();
+ const { badgeImage, loadingIcon, reloadButton } = findElements();
- expect(badgeImage).toBeHidden();
- expect(loadingIcon).toBeVisible();
- expect(reloadButton).toBeHidden();
- expect(vm.$el.querySelector('.btn-group')).toBeHidden();
- })
- .then(done)
- .catch(done.fail);
+ expect(badgeImage).toBeHidden();
+ expect(loadingIcon).toBeVisible();
+ expect(reloadButton).toBeHidden();
+ expect(vm.$el.querySelector('.btn-group')).toBeHidden();
});
- it('shows an error and reload button if loading failed', (done) => {
+ it('shows an error and reload button if loading failed', async () => {
vm.hasError = true;
- Vue.nextTick()
- .then(() => {
- const { badgeImage, loadingIcon, reloadButton } = findElements();
+ await nextTick();
+ const { badgeImage, loadingIcon, reloadButton } = findElements();
- expect(badgeImage).toBeHidden();
- expect(loadingIcon).toBeHidden();
- expect(reloadButton).toBeVisible();
- expect(reloadButton).toHaveSpriteIcon('retry');
- expect(vm.$el.innerText.trim()).toBe('No badge image');
- })
- .then(done)
- .catch(done.fail);
+ expect(badgeImage).toBeHidden();
+ expect(loadingIcon).toBeHidden();
+ expect(reloadButton).toBeVisible();
+ expect(reloadButton).toHaveSpriteIcon('retry');
+ expect(vm.$el.innerText.trim()).toBe('No badge image');
});
});
});
diff --git a/spec/frontend/batch_comments/components/diff_file_drafts_spec.js b/spec/frontend/batch_comments/components/diff_file_drafts_spec.js
index dcb68b1804f..6a5ff1af7c9 100644
--- a/spec/frontend/batch_comments/components/diff_file_drafts_spec.js
+++ b/spec/frontend/batch_comments/components/diff_file_drafts_spec.js
@@ -1,11 +1,11 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import DiffFileDrafts from '~/batch_comments/components/diff_file_drafts.vue';
import DraftNote from '~/batch_comments/components/draft_note.vue';
+import DesignNotePin from '~/vue_shared/components/design_management/design_note_pin.vue';
-const localVue = createLocalVue();
-
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Batch comments diff file drafts component', () => {
let vm;
@@ -22,9 +22,8 @@ describe('Batch comments diff file drafts component', () => {
},
});
- vm = shallowMount(localVue.extend(DiffFileDrafts), {
+ vm = shallowMount(DiffFileDrafts, {
store,
- localVue,
propsData: { fileHash: 'filehash' },
});
}
@@ -42,10 +41,12 @@ describe('Batch comments diff file drafts component', () => {
it('renders index of draft note', () => {
factory();
- expect(vm.findAll('.js-diff-notes-index').length).toEqual(2);
+ const elements = vm.findAll(DesignNotePin);
+
+ expect(elements.length).toEqual(2);
- expect(vm.findAll('.js-diff-notes-index').at(0).text()).toEqual('1');
+ expect(elements.at(0).props('label')).toEqual(1);
- expect(vm.findAll('.js-diff-notes-index').at(1).text()).toEqual('2');
+ expect(elements.at(1).props('label')).toEqual(2);
});
});
diff --git a/spec/frontend/batch_comments/components/draft_note_spec.js b/spec/frontend/batch_comments/components/draft_note_spec.js
index 5d22823e974..6a997ebaaa8 100644
--- a/spec/frontend/batch_comments/components/draft_note_spec.js
+++ b/spec/frontend/batch_comments/components/draft_note_spec.js
@@ -1,14 +1,15 @@
+import { nextTick } from 'vue';
+import { GlButton, GlBadge } from '@gitlab/ui';
import { getByRole } from '@testing-library/dom';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
import { stubComponent } from 'helpers/stub_component';
import DraftNote from '~/batch_comments/components/draft_note.vue';
+import PublishButton from '~/batch_comments/components/publish_button.vue';
import { createStore } from '~/batch_comments/stores';
import NoteableNote from '~/notes/components/noteable_note.vue';
import '~/behaviors/markdown/render_gfm';
import { createDraft } from '../mock_data';
-const localVue = createLocalVue();
-
const NoteableNoteStub = stubComponent(NoteableNote, {
template: `
<div>
@@ -29,12 +30,13 @@ describe('Batch comments draft note component', () => {
};
const getList = () => getByRole(wrapper.element, 'list');
+ const findSubmitReviewButton = () => wrapper.findComponent(PublishButton);
+ const findAddCommentButton = () => wrapper.findComponent(GlButton);
const createComponent = (propsData = { draft }) => {
- wrapper = shallowMount(localVue.extend(DraftNote), {
+ wrapper = shallowMount(DraftNote, {
store,
propsData,
- localVue,
stubs: {
NoteableNote: NoteableNoteStub,
},
@@ -54,7 +56,7 @@ describe('Batch comments draft note component', () => {
it('renders template', () => {
createComponent();
- expect(wrapper.find('.draft-pending-label').exists()).toBe(true);
+ expect(wrapper.findComponent(GlBadge).exists()).toBe(true);
const note = wrapper.find(NoteableNote);
@@ -65,7 +67,7 @@ describe('Batch comments draft note component', () => {
describe('add comment now', () => {
it('dispatches publishSingleDraft when clicking', () => {
createComponent();
- const publishNowButton = wrapper.find({ ref: 'publishNowButton' });
+ const publishNowButton = findAddCommentButton();
publishNowButton.vm.$emit('click');
expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith(
@@ -74,45 +76,60 @@ 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' });
+ await nextTick();
+ const publishNowButton = findAddCommentButton();
- expect(publishNowButton.props().loading).toBe(true);
+ expect(publishNowButton.props().loading).toBe(true);
+ });
- done();
- });
+ it('sets as disabled when review is publishing', async () => {
+ createComponent();
+ wrapper.vm.$store.state.batchComments.isPublishing = true;
+
+ await nextTick();
+ const publishNowButton = findAddCommentButton();
+
+ expect(publishNowButton.props().disabled).toBe(true);
+ expect(publishNowButton.props().loading).toBe(false);
+ });
+ });
+
+ describe('submit review', () => {
+ it('sets as disabled when draft is publishing', async () => {
+ createComponent();
+ wrapper.vm.$store.state.batchComments.currentlyPublishingDrafts.push(1);
+
+ await nextTick();
+ const publishNowButton = findSubmitReviewButton();
+
+ expect(publishNowButton.attributes().disabled).toBeTruthy();
});
});
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,
+ );
});
});
@@ -130,7 +147,7 @@ describe('Batch comments draft note component', () => {
});
describe('quick actions', () => {
- it('renders referenced commands', (done) => {
+ it('renders referenced commands', async () => {
createComponent();
wrapper.setProps({
draft: {
@@ -141,14 +158,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/preview_dropdown_spec.js b/spec/frontend/batch_comments/components/preview_dropdown_spec.js
index 5327879f003..bf3bbf4de26 100644
--- a/spec/frontend/batch_comments/components/preview_dropdown_spec.js
+++ b/spec/frontend/batch_comments/components/preview_dropdown_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import PreviewDropdown from '~/batch_comments/components/preview_dropdown.vue';
@@ -49,7 +49,7 @@ describe('Batch comments preview dropdown', () => {
wrapper.findByTestId('preview-item').vm.$emit('click');
- await Vue.nextTick();
+ await nextTick();
expect(setCurrentFileHash).toHaveBeenCalledWith(expect.anything(), 'hash');
expect(scrollToDraft).toHaveBeenCalledWith(expect.anything(), { id: 1, file_hash: 'hash' });
@@ -63,7 +63,7 @@ describe('Batch comments preview dropdown', () => {
wrapper.findByTestId('preview-item').vm.$emit('click');
- await Vue.nextTick();
+ await nextTick();
expect(scrollToDraft).toHaveBeenCalledWith(expect.anything(), { id: 1 });
});
diff --git a/spec/frontend/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/batch_comments/components/publish_dropdown_spec.js b/spec/frontend/batch_comments/components/publish_dropdown_spec.js
index bd8091c20e0..a3168931f1f 100644
--- a/spec/frontend/batch_comments/components/publish_dropdown_spec.js
+++ b/spec/frontend/batch_comments/components/publish_dropdown_spec.js
@@ -1,13 +1,13 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import PreviewDropdown from '~/batch_comments/components/preview_dropdown.vue';
import { createStore } from '~/mr_notes/stores';
import '~/behaviors/markdown/render_gfm';
import { createDraft } from '../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Batch comments publish dropdown component', () => {
let wrapper;
diff --git a/spec/frontend/behaviors/copy_as_gfm_spec.js b/spec/frontend/behaviors/copy_as_gfm_spec.js
index 557b609f5f9..c96db09cc76 100644
--- a/spec/frontend/behaviors/copy_as_gfm_spec.js
+++ b/spec/frontend/behaviors/copy_as_gfm_spec.js
@@ -1,3 +1,4 @@
+import waitForPromises from 'helpers/wait_for_promises';
import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
describe('CopyAsGFM', () => {
@@ -81,49 +82,40 @@ describe('CopyAsGFM', () => {
stopPropagation() {},
};
CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection);
- return clipboardData;
+
+ return waitForPromises();
};
- beforeAll((done) => {
+ beforeAll(() => {
initCopyAsGFM();
// Fake call to nodeToGfm so the import of lazy bundle happened
- CopyAsGFM.nodeToGFM(document.createElement('div'))
- .then(() => {
- done();
- })
- .catch(done.fail);
+ return CopyAsGFM.nodeToGFM(document.createElement('div'));
});
beforeEach(() => jest.spyOn(clipboardData, 'setData'));
describe('list handling', () => {
- it('uses correct gfm for unordered lists', (done) => {
+ it('uses correct gfm for unordered lists', async () => {
const selection = stubSelection('<li>List Item1</li><li>List Item2</li>\n', 'UL');
window.getSelection = jest.fn(() => selection);
- simulateCopy();
+ await simulateCopy();
- setImmediate(() => {
- const expectedGFM = '* List Item1\n* List Item2';
+ const expectedGFM = '* List Item1\n* List Item2';
- expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM);
- done();
- });
+ expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM);
});
- it('uses correct gfm for ordered lists', (done) => {
+ it('uses correct gfm for ordered lists', async () => {
const selection = stubSelection('<li>List Item1</li><li>List Item2</li>\n', 'OL');
window.getSelection = jest.fn(() => selection);
- simulateCopy();
+ await simulateCopy();
- setImmediate(() => {
- const expectedGFM = '1. List Item1\n1. List Item2';
+ const expectedGFM = '1. List Item1\n1. List Item2';
- expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM);
- done();
- });
+ expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM);
});
});
});
@@ -131,10 +123,9 @@ describe('CopyAsGFM', () => {
describe('CopyAsGFM.quoted', () => {
const sampleGFM = '* List 1\n* List 2\n\n`Some code`';
- it('adds quote char `> ` to each line', (done) => {
+ it('adds quote char `> ` to each line', () => {
const expectedQuotedGFM = '> * List 1\n> * List 2\n> \n> `Some code`';
expect(CopyAsGFM.quoted(sampleGFM)).toEqual(expectedQuotedGFM);
- done();
});
});
});
diff --git a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
index bb3b16b4c7a..e1811247124 100644
--- a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
+++ b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import Mousetrap from 'mousetrap';
+import waitForPromises from 'helpers/wait_for_promises';
import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import { getSelectedFragment } from '~/lib/utils/common_utils';
@@ -13,15 +14,11 @@ describe('ShortcutsIssuable', () => {
const snippetShowFixtureName = 'snippets/show.html';
const mrShowFixtureName = 'merge_requests/merge_request_of_current_user.html';
- beforeAll((done) => {
+ beforeAll(() => {
initCopyAsGFM();
// Fake call to nodeToGfm so the import of lazy bundle happened
- CopyAsGFM.nodeToGFM(document.createElement('div'))
- .then(() => {
- done();
- })
- .catch(done.fail);
+ return CopyAsGFM.nodeToGFM(document.createElement('div'));
});
describe('replyWithSelectedText', () => {
@@ -79,22 +76,18 @@ describe('ShortcutsIssuable', () => {
stubSelection('<p>Selected text.</p>');
});
- it('leaves existing input intact', (done) => {
+ it('leaves existing input intact', async () => {
$(FORM_SELECTOR).val('This text was already here.');
expect($(FORM_SELECTOR).val()).toBe('This text was already here.');
ShortcutsIssuable.replyWithSelectedText(true);
- setImmediate(() => {
- expect($(FORM_SELECTOR).val()).toBe(
- 'This text was already here.\n\n> Selected text.\n\n',
- );
- done();
- });
+ await waitForPromises();
+ expect($(FORM_SELECTOR).val()).toBe('This text was already here.\n\n> Selected text.\n\n');
});
- it('triggers `input`', (done) => {
+ it('triggers `input`', async () => {
let triggered = false;
$(FORM_SELECTOR).on('input', () => {
triggered = true;
@@ -102,48 +95,40 @@ describe('ShortcutsIssuable', () => {
ShortcutsIssuable.replyWithSelectedText(true);
- setImmediate(() => {
- expect(triggered).toBe(true);
- done();
- });
+ await waitForPromises();
+ expect(triggered).toBe(true);
});
- it('triggers `focus`', (done) => {
+ it('triggers `focus`', async () => {
const spy = jest.spyOn(document.querySelector(FORM_SELECTOR), 'focus');
ShortcutsIssuable.replyWithSelectedText(true);
- setImmediate(() => {
- expect(spy).toHaveBeenCalled();
- done();
- });
+ await waitForPromises();
+ expect(spy).toHaveBeenCalled();
});
});
describe('with a one-line selection', () => {
- it('quotes the selection', (done) => {
+ it('quotes the selection', async () => {
stubSelection('<p>This text has been selected.</p>');
ShortcutsIssuable.replyWithSelectedText(true);
- setImmediate(() => {
- expect($(FORM_SELECTOR).val()).toBe('> This text has been selected.\n\n');
- done();
- });
+ await waitForPromises();
+ expect($(FORM_SELECTOR).val()).toBe('> This text has been selected.\n\n');
});
});
describe('with a multi-line selection', () => {
- it('quotes the selected lines as a group', (done) => {
+ it('quotes the selected lines as a group', async () => {
stubSelection(
'<p>Selected line one.</p>\n<p>Selected line two.</p>\n<p>Selected line three.</p>',
);
ShortcutsIssuable.replyWithSelectedText(true);
- setImmediate(() => {
- expect($(FORM_SELECTOR).val()).toBe(
- '> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n',
- );
- done();
- });
+ await waitForPromises();
+ expect($(FORM_SELECTOR).val()).toBe(
+ '> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n',
+ );
});
});
@@ -152,23 +137,19 @@ describe('ShortcutsIssuable', () => {
stubSelection('<p>Selected text.</p>', true);
});
- it('does not add anything to the input', (done) => {
+ it('does not add anything to the input', async () => {
ShortcutsIssuable.replyWithSelectedText(true);
- setImmediate(() => {
- expect($(FORM_SELECTOR).val()).toBe('');
- done();
- });
+ await waitForPromises();
+ expect($(FORM_SELECTOR).val()).toBe('');
});
- it('triggers `focus`', (done) => {
+ it('triggers `focus`', async () => {
const spy = jest.spyOn(document.querySelector(FORM_SELECTOR), 'focus');
ShortcutsIssuable.replyWithSelectedText(true);
- setImmediate(() => {
- expect(spy).toHaveBeenCalled();
- done();
- });
+ await waitForPromises();
+ expect(spy).toHaveBeenCalled();
});
});
@@ -177,26 +158,22 @@ describe('ShortcutsIssuable', () => {
stubSelection('<div class="md">Selected text.</div><p>Invalid selected text.</p>', true);
});
- it('only adds the valid part to the input', (done) => {
+ it('only adds the valid part to the input', async () => {
ShortcutsIssuable.replyWithSelectedText(true);
- setImmediate(() => {
- expect($(FORM_SELECTOR).val()).toBe('> Selected text.\n\n');
- done();
- });
+ await waitForPromises();
+ expect($(FORM_SELECTOR).val()).toBe('> Selected text.\n\n');
});
- it('triggers `focus`', (done) => {
+ it('triggers `focus`', async () => {
const spy = jest.spyOn(document.querySelector(FORM_SELECTOR), 'focus');
ShortcutsIssuable.replyWithSelectedText(true);
- setImmediate(() => {
- expect(spy).toHaveBeenCalled();
- done();
- });
+ await waitForPromises();
+ expect(spy).toHaveBeenCalled();
});
- it('triggers `input`', (done) => {
+ it('triggers `input`', async () => {
let triggered = false;
$(FORM_SELECTOR).on('input', () => {
triggered = true;
@@ -204,10 +181,8 @@ describe('ShortcutsIssuable', () => {
ShortcutsIssuable.replyWithSelectedText(true);
- setImmediate(() => {
- expect(triggered).toBe(true);
- done();
- });
+ await waitForPromises();
+ expect(triggered).toBe(true);
});
});
@@ -231,26 +206,22 @@ describe('ShortcutsIssuable', () => {
});
});
- it('adds the quoted selection to the input', (done) => {
+ it('adds the quoted selection to the input', async () => {
ShortcutsIssuable.replyWithSelectedText(true);
- setImmediate(() => {
- expect($(FORM_SELECTOR).val()).toBe('> *Selected text.*\n\n');
- done();
- });
+ await waitForPromises();
+ expect($(FORM_SELECTOR).val()).toBe('> *Selected text.*\n\n');
});
- it('triggers `focus`', (done) => {
+ it('triggers `focus`', async () => {
const spy = jest.spyOn(document.querySelector(FORM_SELECTOR), 'focus');
ShortcutsIssuable.replyWithSelectedText(true);
- setImmediate(() => {
- expect(spy).toHaveBeenCalled();
- done();
- });
+ await waitForPromises();
+ expect(spy).toHaveBeenCalled();
});
- it('triggers `input`', (done) => {
+ it('triggers `input`', async () => {
let triggered = false;
$(FORM_SELECTOR).on('input', () => {
triggered = true;
@@ -258,10 +229,8 @@ describe('ShortcutsIssuable', () => {
ShortcutsIssuable.replyWithSelectedText(true);
- setImmediate(() => {
- expect(triggered).toBe(true);
- done();
- });
+ await waitForPromises();
+ expect(triggered).toBe(true);
});
});
@@ -285,36 +254,29 @@ describe('ShortcutsIssuable', () => {
});
});
- it('does not add anything to the input', (done) => {
+ it('does not add anything to the input', async () => {
ShortcutsIssuable.replyWithSelectedText(true);
- setImmediate(() => {
- expect($(FORM_SELECTOR).val()).toBe('');
- done();
- });
+ await waitForPromises();
+ expect($(FORM_SELECTOR).val()).toBe('');
});
- it('triggers `focus`', (done) => {
+ it('triggers `focus`', async () => {
const spy = jest.spyOn(document.querySelector(FORM_SELECTOR), 'focus');
ShortcutsIssuable.replyWithSelectedText(true);
- setImmediate(() => {
- expect(spy).toHaveBeenCalled();
- done();
- });
+ await waitForPromises();
+ expect(spy).toHaveBeenCalled();
});
});
describe('with a valid selection with no text content', () => {
- it('returns the proper markdown', (done) => {
+ it('returns the proper markdown', async () => {
stubSelection('<img src="https://gitlab.com/logo.png" alt="logo" />');
ShortcutsIssuable.replyWithSelectedText(true);
- setImmediate(() => {
- expect($(FORM_SELECTOR).val()).toBe('> ![logo](https://gitlab.com/logo.png)\n\n');
-
- done();
- });
+ await waitForPromises();
+ expect($(FORM_SELECTOR).val()).toBe('> ![logo](https://gitlab.com/logo.png)\n\n');
});
});
});
diff --git a/spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap b/spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap
index 22bec77276b..b3d93906445 100644
--- a/spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap
+++ b/spec/frontend/blob/components/__snapshots__/blob_header_spec.js.snap
@@ -13,6 +13,7 @@ exports[`Blob Header Default Actions rendering matches the snapshot 1`] = `
<blob-filepath-stub
blob="[object Object]"
+ showpath="true"
/>
</div>
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_default_actions_spec.js b/spec/frontend/blob/components/blob_header_default_actions_spec.js
index e321bb41774..af605b257de 100644
--- a/spec/frontend/blob/components/blob_header_default_actions_spec.js
+++ b/spec/frontend/blob/components/blob_header_default_actions_spec.js
@@ -1,13 +1,13 @@
import { GlButtonGroup, GlButton } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
import BlobHeaderActions from '~/blob/components/blob_header_default_actions.vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import {
BTN_COPY_CONTENTS_TITLE,
BTN_DOWNLOAD_TITLE,
BTN_RAW_TITLE,
RICH_BLOB_VIEWER,
} from '~/blob/components/constants';
-import { Blob } from './mock_data';
+import { Blob, mockEnvironmentName, mockEnvironmentPath } from './mock_data';
describe('Blob Header Default Actions', () => {
let wrapper;
@@ -17,7 +17,7 @@ describe('Blob Header Default Actions', () => {
const blobHash = 'foo-bar';
function createComponent(propsData = {}) {
- wrapper = mount(BlobHeaderActions, {
+ wrapper = shallowMountExtended(BlobHeaderActions, {
provide: {
blobHash,
},
@@ -39,8 +39,8 @@ describe('Blob Header Default Actions', () => {
});
describe('renders', () => {
- const findCopyButton = () => wrapper.find('[data-testid="copyContentsButton"]');
- const findViewRawButton = () => wrapper.find('[data-testid="viewRawButton"]');
+ const findCopyButton = () => wrapper.findByTestId('copyContentsButton');
+ const findViewRawButton = () => wrapper.findByTestId('viewRawButton');
it('gl-button-group component', () => {
expect(btnGroup.exists()).toBe(true);
@@ -89,4 +89,37 @@ describe('Blob Header Default Actions', () => {
expect(findViewRawButton().exists()).toBe(false);
});
});
+
+ describe('view on environment button', () => {
+ const findEnvironmentButton = () => wrapper.findByTestId('environment');
+
+ it.each`
+ environmentName | environmentPath | isVisible
+ ${null} | ${null} | ${false}
+ ${null} | ${mockEnvironmentPath} | ${false}
+ ${mockEnvironmentName} | ${null} | ${false}
+ ${mockEnvironmentName} | ${mockEnvironmentPath} | ${true}
+ `(
+ 'when environmentName is $environmentName and environmentPath is $environmentPath',
+ ({ environmentName, environmentPath, isVisible }) => {
+ createComponent({ environmentName, environmentPath });
+
+ expect(findEnvironmentButton().exists()).toBe(isVisible);
+ },
+ );
+
+ it('renders the correct attributes', () => {
+ createComponent({
+ environmentName: mockEnvironmentName,
+ environmentPath: mockEnvironmentPath,
+ });
+
+ expect(findEnvironmentButton().attributes()).toMatchObject({
+ title: `View on ${mockEnvironmentName}`,
+ href: mockEnvironmentPath,
+ });
+
+ expect(findEnvironmentButton().props('icon')).toBe('external-link');
+ });
+ });
});
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/blob/components/mock_data.js b/spec/frontend/blob/components/mock_data.js
index 95789ca13cb..9a345921f16 100644
--- a/spec/frontend/blob/components/mock_data.js
+++ b/spec/frontend/blob/components/mock_data.js
@@ -55,3 +55,6 @@ export const SimpleBlobContentMock = {
path: 'foo.js',
plainData: 'Plain',
};
+
+export const mockEnvironmentName = 'my.testing.environment';
+export const mockEnvironmentPath = 'https://my.testing.environment';
diff --git a/spec/frontend/blob/viewer/index_spec.js b/spec/frontend/blob/viewer/index_spec.js
index 9e9f866d40c..fe55a537b89 100644
--- a/spec/frontend/blob/viewer/index_spec.js
+++ b/spec/frontend/blob/viewer/index_spec.js
@@ -41,34 +41,30 @@ describe('Blob viewer', () => {
window.location.hash = '';
});
- it('loads source file after switching views', (done) => {
+ it('loads source file after switching views', async () => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
- setImmediate(() => {
- expect(
- document
- .querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]')
- .classList.contains('hidden'),
- ).toBeFalsy();
+ await axios.waitForAll();
- done();
- });
+ expect(
+ document
+ .querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]')
+ .classList.contains('hidden'),
+ ).toBeFalsy();
});
- it('loads source file when line number is in hash', (done) => {
+ it('loads source file when line number is in hash', async () => {
window.location.hash = '#L1';
new BlobViewer();
- setImmediate(() => {
- expect(
- document
- .querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]')
- .classList.contains('hidden'),
- ).toBeFalsy();
+ await axios.waitForAll();
- done();
- });
+ expect(
+ document
+ .querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]')
+ .classList.contains('hidden'),
+ ).toBeFalsy();
});
it('doesnt reload file if already loaded', () => {
@@ -123,24 +119,20 @@ describe('Blob viewer', () => {
expect(copyButton.blur).not.toHaveBeenCalled();
});
- it('enables after switching to simple view', (done) => {
+ it('enables after switching to simple view', async () => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
- setImmediate(() => {
- expect(copyButton.classList.contains('disabled')).toBeFalsy();
+ await axios.waitForAll();
- done();
- });
+ expect(copyButton.classList.contains('disabled')).toBeFalsy();
});
- it('updates tooltip after switching to simple view', (done) => {
+ it('updates tooltip after switching to simple view', async () => {
document.querySelector('.js-blob-viewer-switch-btn[data-viewer="simple"]').click();
- setImmediate(() => {
- expect(copyButtonTooltip.getAttribute('title')).toBe('Copy file contents');
+ await axios.waitForAll();
- done();
- });
+ expect(copyButtonTooltip.getAttribute('title')).toBe('Copy file contents');
});
});
diff --git a/spec/frontend/boards/board_card_inner_spec.js b/spec/frontend/boards/board_card_inner_spec.js
index e0446811f64..677978d31ca 100644
--- a/spec/frontend/boards/board_card_inner_spec.js
+++ b/spec/frontend/boards/board_card_inner_spec.js
@@ -1,15 +1,13 @@
import { GlLabel, GlLoadingIcon, GlTooltip } from '@gitlab/ui';
import { range } from 'lodash';
import Vuex from 'vuex';
-import setWindowLocation from 'helpers/set_window_location_helper';
+import { nextTick } from 'vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import BoardBlockedIcon from '~/boards/components/board_blocked_icon.vue';
import BoardCardInner from '~/boards/components/board_card_inner.vue';
import { issuableTypes } from '~/boards/constants';
-import eventHub from '~/boards/eventhub';
import defaultStore from '~/boards/stores';
-import { updateHistory } from '~/lib/utils/url_utility';
import { mockLabelList, mockIssue, mockIssueFullPath } from './mock_data';
jest.mock('~/lib/utils/url_utility');
@@ -53,6 +51,7 @@ describe('Board card component', () => {
state: {
...defaultStore.state,
issuableType: issuableTypes.issue,
+ isShowingLabels: true,
},
getters: {
isGroupBoard: () => true,
@@ -261,7 +260,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 +375,7 @@ describe('Board card component', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('.board-card-assignee .avatar-counter').text().trim()).toEqual('99+');
});
@@ -399,58 +398,17 @@ 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');
});
- });
-
- describe('filterByLabel method', () => {
- beforeEach(() => {
- wrapper.setProps({
- updateFilters: true,
- });
- });
-
- describe('when selected label is not in the filter', () => {
- beforeEach(() => {
- jest.spyOn(wrapper.vm, 'performSearch').mockImplementation(() => {});
- setWindowLocation('?');
- wrapper.vm.filterByLabel(label1);
- });
-
- it('calls updateHistory', () => {
- expect(updateHistory).toHaveBeenCalledTimes(1);
- });
-
- it('dispatches performSearch vuex action', () => {
- expect(wrapper.vm.performSearch).toHaveBeenCalledTimes(1);
- });
-
- it('emits updateTokens event', () => {
- expect(eventHub.$emit).toHaveBeenCalledTimes(1);
- expect(eventHub.$emit).toHaveBeenCalledWith('updateTokens');
- });
- });
-
- describe('when selected label is already in the filter', () => {
- beforeEach(() => {
- jest.spyOn(wrapper.vm, 'performSearch').mockImplementation(() => {});
- setWindowLocation('?label_name[]=testing%20123');
- wrapper.vm.filterByLabel(label1);
- });
-
- it('does not call updateHistory', () => {
- expect(updateHistory).not.toHaveBeenCalled();
- });
- it('does not dispatch performSearch vuex action', () => {
- expect(wrapper.vm.performSearch).not.toHaveBeenCalled();
- });
-
- it('does not emit updateTokens event', () => {
- expect(eventHub.$emit).not.toHaveBeenCalled();
+ describe('when label params arent set', () => {
+ it('passes the right target to GlLabel', () => {
+ expect(wrapper.findAll(GlLabel).at(0).props('target')).toEqual(
+ '?label_name[]=testing%20123',
+ );
});
});
});
diff --git a/spec/frontend/boards/board_list_helper.js b/spec/frontend/boards/board_list_helper.js
index d0f14bd37c1..04192489817 100644
--- a/spec/frontend/boards/board_list_helper.js
+++ b/spec/frontend/boards/board_list_helper.js
@@ -1,4 +1,5 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import Vuex from 'vuex';
@@ -33,9 +34,8 @@ export default function createComponent({
},
issuesCount,
} = {}) {
- const localVue = createLocalVue();
- localVue.use(VueApollo);
- localVue.use(Vuex);
+ Vue.use(VueApollo);
+ Vue.use(Vuex);
const fakeApollo = createMockApollo([
[listQuery, jest.fn().mockResolvedValue(boardListQueryResponse(issuesCount))],
@@ -85,7 +85,6 @@ export default function createComponent({
const component = shallowMount(BoardList, {
apolloProvider: fakeApollo,
- localVue,
store,
propsData: {
disabled: false,
diff --git a/spec/frontend/boards/board_list_spec.js b/spec/frontend/boards/board_list_spec.js
index 1981ed5ab7f..fd9d2b6823d 100644
--- a/spec/frontend/boards/board_list_spec.js
+++ b/spec/frontend/boards/board_list_spec.js
@@ -1,6 +1,8 @@
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';
import createComponent from 'jest/boards/board_list_helper';
import BoardCard from '~/boards/components/board_card.vue';
import eventHub from '~/boards/eventhub';
@@ -64,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);
});
@@ -85,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');
@@ -94,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');
});
});
@@ -104,10 +106,6 @@ describe('Board list component', () => {
fetchItemsForList: jest.fn(),
};
- beforeEach(() => {
- wrapper = createComponent();
- });
-
it('does not load issues if already loading', () => {
wrapper = createComponent({
actions,
@@ -126,20 +124,23 @@ describe('Board list component', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findIssueCountLoadingIcon().exists()).toBe(true);
});
it('shows how many more issues to load', async () => {
- // wrapper.vm.showCount = true;
wrapper = createComponent({
data: {
showCount: true,
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
+ await waitForPromises();
+ await nextTick();
+ await nextTick();
+
expect(wrapper.find('.board-list-count').text()).toBe('Showing 1 of 20 issues');
});
});
@@ -155,7 +156,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_filtered_search_spec.js b/spec/frontend/boards/components/board_filtered_search_spec.js
index a8398a138ba..85ba703a6ee 100644
--- a/spec/frontend/boards/components/board_filtered_search_spec.js
+++ b/spec/frontend/boards/components/board_filtered_search_spec.js
@@ -120,7 +120,7 @@ describe('BoardFilteredSearch', () => {
{ type: 'author', value: { data: 'root', operator: '=' } },
{ type: 'assignee', value: { data: 'root', operator: '=' } },
{ type: 'label', value: { data: 'label', operator: '=' } },
- { type: 'label', value: { data: 'label2', operator: '=' } },
+ { type: 'label', value: { data: 'label&2', operator: '=' } },
{ type: 'milestone', value: { data: 'New Milestone', operator: '=' } },
{ type: 'type', value: { data: 'INCIDENT', operator: '=' } },
{ type: 'weight', value: { data: '2', operator: '=' } },
@@ -134,7 +134,7 @@ describe('BoardFilteredSearch', () => {
title: '',
replace: true,
url:
- 'http://test.host/?author_username=root&label_name[]=label&label_name[]=label2&assignee_username=root&milestone_title=New+Milestone&iteration_id=3341&types=INCIDENT&weight=2&release_tag=v1.0.0',
+ 'http://test.host/?author_username=root&label_name[]=label&label_name[]=label%262&assignee_username=root&milestone_title=New%20Milestone&iteration_id=3341&types=INCIDENT&weight=2&release_tag=v1.0.0',
});
});
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index 692fd3ec555..5678da2a246 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -130,7 +130,7 @@ describe('BoardForm', () => {
it('passes correct primary action text and variant', () => {
expect(findModalActionPrimary().text).toBe('Create board');
- expect(findModalActionPrimary().attributes[0].variant).toBe('success');
+ expect(findModalActionPrimary().attributes[0].variant).toBe('confirm');
});
it('does not render delete confirmation message', () => {
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 57ccebf3676..8b0100d069a 100644
--- a/spec/frontend/boards/components/board_new_issue_spec.js
+++ b/spec/frontend/boards/components/board_new_issue_spec.js
@@ -1,15 +1,14 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+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';
import ProjectSelect from '~/boards/components/project_select.vue';
import eventHub from '~/boards/eventhub';
-import { mockList, mockGroupProjects } from '../mock_data';
+import { mockList, mockGroupProjects, mockIssue, mockIssue2 } from '../mock_data';
-const localVue = createLocalVue();
-
-localVue.use(Vuex);
+Vue.use(Vuex);
const addListNewIssuesSpy = jest.fn().mockResolvedValue();
const mockActions = { addListNewIssue: addListNewIssuesSpy };
@@ -17,10 +16,9 @@ const mockActions = { addListNewIssue: addListNewIssuesSpy };
const createComponent = ({
state = { selectedProject: mockGroupProjects[0], fullPath: mockGroupProjects[0].fullPath },
actions = mockActions,
- getters = { isGroupBoard: () => true, isProjectBoard: () => false },
+ getters = { isGroupBoard: () => true, getBoardItemsByList: () => () => [] },
} = {}) =>
shallowMount(BoardNewIssue, {
- localVue,
store: new Vuex.Store({
state,
actions,
@@ -47,7 +45,7 @@ describe('Issue boards new issue form', () => {
beforeEach(async () => {
wrapper = createComponent();
- await wrapper.vm.$nextTick();
+ await nextTick();
});
afterEach(() => {
@@ -68,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: {
@@ -77,15 +75,44 @@ describe('Issue boards new issue form', () => {
assigneeIds: [],
milestoneId: undefined,
projectPath: mockGroupProjects[0].fullPath,
+ moveAfterId: undefined,
},
});
});
+ describe('when list has an existing issues', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ getters: {
+ isGroupBoard: () => true,
+ getBoardItemsByList: () => () => [mockIssue, mockIssue2],
+ },
+ });
+ });
+
+ it('it uses the first issue ID as moveAfterId', async () => {
+ findBoardNewItem().vm.$emit('form-submit', { title: 'Foo' });
+
+ await nextTick();
+ expect(addListNewIssuesSpy).toHaveBeenCalledWith(expect.any(Object), {
+ list: mockList,
+ issueInput: {
+ title: 'Foo',
+ labelIds: [],
+ assigneeIds: [],
+ milestoneId: undefined,
+ projectPath: mockGroupProjects[0].fullPath,
+ moveAfterId: mockIssue.id,
+ },
+ });
+ });
+ });
+
it('emits event `toggle-issue-form` with current list Id suffix on eventHub when `board-new-item` emits form-cancel event', async () => {
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}`);
});
@@ -101,7 +128,7 @@ describe('Issue boards new issue form', () => {
describe('when in project issue board', () => {
beforeEach(() => {
wrapper = createComponent({
- getters: { isGroupBoard: () => false, isProjectBoard: () => true },
+ getters: { isGroupBoard: () => false },
});
});
diff --git a/spec/frontend/boards/components/board_new_item_spec.js b/spec/frontend/boards/components/board_new_item_spec.js
index 0151d9c1c14..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';
@@ -39,6 +40,27 @@ describe('BoardNewItem', () => {
});
describe('template', () => {
+ describe('when the user provides a valid input', () => {
+ it('finds an enabled create button', async () => {
+ expect(wrapper.findByTestId('create-button').props('disabled')).toBe(true);
+
+ wrapper.find(GlFormInput).vm.$emit('input', 'hello');
+ await nextTick();
+
+ expect(wrapper.findByTestId('create-button').props('disabled')).toBe(false);
+ });
+ });
+
+ describe('when the user types in a string with only spaces', () => {
+ it('disables the Create Issue button', async () => {
+ wrapper.find(GlFormInput).vm.$emit('input', ' ');
+
+ await nextTick();
+
+ expect(wrapper.findByTestId('create-button').props('disabled')).toBe(true);
+ });
+ });
+
it('renders gl-form component', () => {
expect(wrapper.findComponent(GlForm).exists()).toBe(true);
});
@@ -80,6 +102,19 @@ describe('BoardNewItem', () => {
]);
});
+ it('emits `form-submit` event with trimmed title', async () => {
+ titleInput().setValue(' Foo ');
+
+ await glForm().trigger('submit');
+
+ expect(wrapper.emitted('form-submit')[0]).toEqual([
+ {
+ title: 'Foo',
+ list: mockList,
+ },
+ ]);
+ });
+
it('emits `scroll-board-list-` event with list.id on eventHub when `submit` is triggered on gl-form', async () => {
jest.spyOn(eventHub, '$emit').mockImplementation();
await glForm().trigger('submit');
@@ -90,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..7f40c426b30 100644
--- a/spec/frontend/boards/components/board_settings_sidebar_spec.js
+++ b/spec/frontend/boards/components/board_settings_sidebar_spec.js
@@ -1,8 +1,9 @@
-import { GlDrawer, GlLabel } from '@gitlab/ui';
+import { GlDrawer, GlLabel, GlModal, GlButton } 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 { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { stubComponent } from 'helpers/stub_component';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue';
@@ -20,8 +21,7 @@ describe('BoardSettingsSidebar', () => {
const labelTitle = mockLabelList.label.title;
const labelColor = mockLabelList.label.color;
const listId = mockLabelList.id;
-
- const findRemoveButton = () => wrapper.findByTestId('remove-list');
+ const modalID = 'board-settings-sidebar-modal';
const createComponent = ({
canAdminList = false,
@@ -46,6 +46,9 @@ describe('BoardSettingsSidebar', () => {
canAdminList,
scopedLabelsAvailable: false,
},
+ directives: {
+ GlModal: createMockDirective(),
+ },
stubs: {
GlDrawer: stubComponent(GlDrawer, {
template: '<div><slot name="header"></slot><slot></slot></div>',
@@ -56,6 +59,8 @@ describe('BoardSettingsSidebar', () => {
};
const findLabel = () => wrapper.find(GlLabel);
const findDrawer = () => wrapper.find(GlDrawer);
+ const findModal = () => wrapper.find(GlModal);
+ const findRemoveButton = () => wrapper.find(GlButton);
afterEach(() => {
jest.restoreAllMocks();
@@ -86,7 +91,7 @@ describe('BoardSettingsSidebar', () => {
findDrawer().vm.$emit('close');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlDrawer).exists()).toBe(false);
});
@@ -96,7 +101,7 @@ describe('BoardSettingsSidebar', () => {
sidebarEventHub.$emit('sidebar.closeAll');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlDrawer).exists()).toBe(false);
});
@@ -161,5 +166,16 @@ describe('BoardSettingsSidebar', () => {
expect(findRemoveButton().exists()).toBe(true);
});
+
+ it('has the correct ID on the button', () => {
+ createComponent({ canAdminList: true, activeId: listId, list: mockLabelList });
+ const binding = getBinding(findRemoveButton().element, 'gl-modal');
+ expect(binding.value).toBe(modalID);
+ });
+
+ it('has the correct ID on the modal', () => {
+ createComponent({ canAdminList: true, activeId: listId, list: mockLabelList });
+ expect(findModal().props('modalId')).toBe(modalID);
+ });
});
});
diff --git a/spec/frontend/boards/components/boards_selector_spec.js b/spec/frontend/boards/components/boards_selector_spec.js
index 9cf7c5774bf..26a5bf34595 100644
--- a/spec/frontend/boards/components/boards_selector_spec.js
+++ b/spec/frontend/boards/components/boards_selector_spec.js
@@ -1,43 +1,40 @@
import { GlDropdown, GlLoadingIcon, GlDropdownSectionHeader } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import MockAdapter from 'axios-mock-adapter';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import Vuex from 'vuex';
import { TEST_HOST } from 'spec/test_constants';
import BoardsSelector from '~/boards/components/boards_selector.vue';
+import { BoardType } from '~/boards/constants';
import groupBoardQuery from '~/boards/graphql/group_board.query.graphql';
import projectBoardQuery from '~/boards/graphql/project_board.query.graphql';
+import groupBoardsQuery from '~/boards/graphql/group_boards.query.graphql';
+import projectBoardsQuery from '~/boards/graphql/project_boards.query.graphql';
+import groupRecentBoardsQuery from '~/boards/graphql/group_recent_boards.query.graphql';
+import projectRecentBoardsQuery from '~/boards/graphql/project_recent_boards.query.graphql';
import defaultStore from '~/boards/stores';
-import axios from '~/lib/utils/axios_utils';
import createMockApollo from 'helpers/mock_apollo_helper';
-import { mockGroupBoardResponse, mockProjectBoardResponse } from '../mock_data';
+import {
+ mockGroupBoardResponse,
+ mockProjectBoardResponse,
+ mockGroupAllBoardsResponse,
+ mockProjectAllBoardsResponse,
+ mockGroupRecentBoardsResponse,
+ mockProjectRecentBoardsResponse,
+ mockSmallProjectAllBoardsResponse,
+ mockEmptyProjectRecentBoardsResponse,
+ boards,
+ recentIssueBoards,
+} from '../mock_data';
const throttleDuration = 1;
Vue.use(VueApollo);
-function boardGenerator(n) {
- return new Array(n).fill().map((board, index) => {
- const id = `${index}`;
- const name = `board${id}`;
-
- return {
- id,
- name,
- };
- });
-}
-
describe('BoardsSelector', () => {
let wrapper;
- let allBoardsResponse;
- let recentBoardsResponse;
- let mock;
let fakeApollo;
let store;
- const boards = boardGenerator(20);
- const recentBoards = boardGenerator(5);
const createStore = ({ isGroupBoard = false, isProjectBoard = false } = {}) => {
store = new Vuex.Store({
@@ -63,17 +60,43 @@ describe('BoardsSelector', () => {
};
const getDropdownItems = () => wrapper.findAll('.js-dropdown-item');
- const getDropdownHeaders = () => wrapper.findAll(GlDropdownSectionHeader);
- const getLoadingIcon = () => wrapper.find(GlLoadingIcon);
- const findDropdown = () => wrapper.find(GlDropdown);
+ const getDropdownHeaders = () => wrapper.findAllComponents(GlDropdownSectionHeader);
+ const getLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
const projectBoardQueryHandlerSuccess = jest.fn().mockResolvedValue(mockProjectBoardResponse);
const groupBoardQueryHandlerSuccess = jest.fn().mockResolvedValue(mockGroupBoardResponse);
- const createComponent = () => {
+ const projectBoardsQueryHandlerSuccess = jest
+ .fn()
+ .mockResolvedValue(mockProjectAllBoardsResponse);
+ const groupBoardsQueryHandlerSuccess = jest.fn().mockResolvedValue(mockGroupAllBoardsResponse);
+
+ const projectRecentBoardsQueryHandlerSuccess = jest
+ .fn()
+ .mockResolvedValue(mockProjectRecentBoardsResponse);
+ const groupRecentBoardsQueryHandlerSuccess = jest
+ .fn()
+ .mockResolvedValue(mockGroupRecentBoardsResponse);
+
+ const smallBoardsQueryHandlerSuccess = jest
+ .fn()
+ .mockResolvedValue(mockSmallProjectAllBoardsResponse);
+ const emptyRecentBoardsQueryHandlerSuccess = jest
+ .fn()
+ .mockResolvedValue(mockEmptyProjectRecentBoardsResponse);
+
+ const createComponent = ({
+ projectBoardsQueryHandler = projectBoardsQueryHandlerSuccess,
+ projectRecentBoardsQueryHandler = projectRecentBoardsQueryHandlerSuccess,
+ } = {}) => {
fakeApollo = createMockApollo([
[projectBoardQuery, projectBoardQueryHandlerSuccess],
[groupBoardQuery, groupBoardQueryHandlerSuccess],
+ [projectBoardsQuery, projectBoardsQueryHandler],
+ [groupBoardsQuery, groupBoardsQueryHandlerSuccess],
+ [projectRecentBoardsQuery, projectRecentBoardsQueryHandler],
+ [groupRecentBoardsQuery, groupRecentBoardsQueryHandlerSuccess],
]);
wrapper = mount(BoardsSelector, {
@@ -91,67 +114,34 @@ describe('BoardsSelector', () => {
attachTo: document.body,
provide: {
fullPath: '',
- recentBoardsEndpoint: `${TEST_HOST}/recent`,
},
});
-
- wrapper.vm.$apollo.addSmartQuery = jest.fn((_, options) => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- [options.loadingKey]: true,
- });
- });
};
afterEach(() => {
wrapper.destroy();
- wrapper = null;
- mock.restore();
+ fakeApollo = null;
});
- describe('fetching all boards', () => {
+ describe('template', () => {
beforeEach(() => {
- mock = new MockAdapter(axios);
-
- allBoardsResponse = Promise.resolve({
- data: {
- group: {
- boards: {
- edges: boards.map((board) => ({ node: board })),
- },
- },
- },
- });
- recentBoardsResponse = Promise.resolve({
- data: recentBoards,
- });
-
- createStore();
+ createStore({ isProjectBoard: true });
createComponent();
-
- mock.onGet(`${TEST_HOST}/recent`).replyOnce(200, recentBoards);
});
describe('loading', () => {
- beforeEach(async () => {
- // Wait for current board to be loaded
- await nextTick();
-
- // Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
- findDropdown().vm.$emit('show');
- });
-
// we are testing loading state, so don't resolve responses until after the tests
afterEach(async () => {
- await Promise.all([allBoardsResponse, recentBoardsResponse]);
await nextTick();
});
it('shows loading spinner', () => {
+ // Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
+ findDropdown().vm.$emit('show');
+
+ expect(getLoadingIcon().exists()).toBe(true);
expect(getDropdownHeaders()).toHaveLength(0);
expect(getDropdownItems()).toHaveLength(0);
- expect(getLoadingIcon().exists()).toBe(true);
});
});
@@ -163,16 +153,13 @@ describe('BoardsSelector', () => {
// Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
findDropdown().vm.$emit('show');
- // 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({
- loadingBoards: false,
- loadingRecentBoards: false,
- });
- await Promise.all([allBoardsResponse, recentBoardsResponse]);
await nextTick();
});
+ it('fetches all issue boards', () => {
+ expect(projectBoardsQueryHandlerSuccess).toHaveBeenCalled();
+ });
+
it('hides loading spinner', async () => {
await nextTick();
expect(getLoadingIcon().exists()).toBe(false);
@@ -180,22 +167,17 @@ describe('BoardsSelector', () => {
describe('filtering', () => {
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({
- boards,
- });
-
await nextTick();
});
it('shows all boards without filtering', () => {
- expect(getDropdownItems()).toHaveLength(boards.length + recentBoards.length);
+ expect(getDropdownItems()).toHaveLength(boards.length + recentIssueBoards.length);
});
it('shows only matching boards when filtering', async () => {
const filterTerm = 'board1';
- const expectedCount = boards.filter((board) => board.name.includes(filterTerm)).length;
+ const expectedCount = boards.filter((board) => board.node.name.includes(filterTerm))
+ .length;
fillSearchBox(filterTerm);
@@ -214,32 +196,21 @@ describe('BoardsSelector', () => {
describe('recent boards section', () => {
it('shows only when boards are greater than 10', 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({
- boards,
- });
-
await nextTick();
+ expect(projectRecentBoardsQueryHandlerSuccess).toHaveBeenCalled();
expect(getDropdownHeaders()).toHaveLength(2);
});
it('does not show when boards are less than 10', 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({
- boards: boards.slice(0, 5),
- });
+ createComponent({ projectBoardsQueryHandler: smallBoardsQueryHandlerSuccess });
await nextTick();
expect(getDropdownHeaders()).toHaveLength(0);
});
- it('does not show when recentBoards api returns empty array', 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({
- recentBoards: [],
+ it('does not show when recentIssueBoards api returns empty array', async () => {
+ createComponent({
+ projectRecentBoardsQueryHandler: emptyRecentBoardsQueryHandlerSuccess,
});
await nextTick();
@@ -256,15 +227,39 @@ describe('BoardsSelector', () => {
});
});
+ describe('fetching all boards', () => {
+ it.each`
+ boardType | queryHandler | notCalledHandler
+ ${BoardType.group} | ${groupBoardsQueryHandlerSuccess} | ${projectBoardsQueryHandlerSuccess}
+ ${BoardType.project} | ${projectBoardsQueryHandlerSuccess} | ${groupBoardsQueryHandlerSuccess}
+ `('fetches $boardType boards', async ({ boardType, queryHandler, notCalledHandler }) => {
+ createStore({
+ isProjectBoard: boardType === BoardType.project,
+ isGroupBoard: boardType === BoardType.group,
+ });
+ createComponent();
+
+ await nextTick();
+
+ // Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
+ findDropdown().vm.$emit('show');
+
+ await nextTick();
+
+ expect(queryHandler).toHaveBeenCalled();
+ expect(notCalledHandler).not.toHaveBeenCalled();
+ });
+ });
+
describe('fetching current board', () => {
it.each`
- boardType | queryHandler | notCalledHandler
- ${'group'} | ${groupBoardQueryHandlerSuccess} | ${projectBoardQueryHandlerSuccess}
- ${'project'} | ${projectBoardQueryHandlerSuccess} | ${groupBoardQueryHandlerSuccess}
+ boardType | queryHandler | notCalledHandler
+ ${BoardType.group} | ${groupBoardQueryHandlerSuccess} | ${projectBoardQueryHandlerSuccess}
+ ${BoardType.project} | ${projectBoardQueryHandlerSuccess} | ${groupBoardQueryHandlerSuccess}
`('fetches $boardType board', async ({ boardType, queryHandler, notCalledHandler }) => {
createStore({
- isProjectBoard: boardType === 'project',
- isGroupBoard: boardType === 'group',
+ isProjectBoard: boardType === BoardType.project,
+ isGroupBoard: boardType === BoardType.group,
});
createComponent();
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/mock_data.js b/spec/frontend/boards/mock_data.js
index a081a60166b..24096fddea6 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -29,6 +29,85 @@ export const listObj = {
},
};
+function boardGenerator(n) {
+ return new Array(n).fill().map((board, index) => {
+ const id = `${index}`;
+ const name = `board${id}`;
+
+ return {
+ node: {
+ id,
+ name,
+ weight: 0,
+ __typename: 'Board',
+ },
+ };
+ });
+}
+
+export const boards = boardGenerator(20);
+export const recentIssueBoards = boardGenerator(5);
+
+export const mockSmallProjectAllBoardsResponse = {
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/114',
+ boards: { edges: boardGenerator(3) },
+ __typename: 'Project',
+ },
+ },
+};
+
+export const mockEmptyProjectRecentBoardsResponse = {
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/114',
+ recentIssueBoards: { edges: [] },
+ __typename: 'Project',
+ },
+ },
+};
+
+export const mockGroupAllBoardsResponse = {
+ data: {
+ group: {
+ id: 'gid://gitlab/Group/114',
+ boards: { edges: boards },
+ __typename: 'Group',
+ },
+ },
+};
+
+export const mockProjectAllBoardsResponse = {
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/1',
+ boards: { edges: boards },
+ __typename: 'Project',
+ },
+ },
+};
+
+export const mockGroupRecentBoardsResponse = {
+ data: {
+ group: {
+ id: 'gid://gitlab/Group/114',
+ recentIssueBoards: { edges: recentIssueBoards },
+ __typename: 'Group',
+ },
+ },
+};
+
+export const mockProjectRecentBoardsResponse = {
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/1',
+ recentIssueBoards: { edges: recentIssueBoards },
+ __typename: 'Project',
+ },
+ },
+};
+
export const mockGroupBoardResponse = {
data: {
workspace: {
@@ -612,6 +691,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, isSignedI
title: __('Milestone'),
symbol: '%',
type: 'milestone',
+ shouldSkipSort: true,
token: MilestoneToken,
unique: true,
fetchMilestones,
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/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
index 7c842d71688..0eca0cb3ee5 100644
--- a/spec/frontend/boards/stores/actions_spec.js
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -315,14 +315,14 @@ describe('fetchMilestones', () => {
'project',
{
query: projectBoardMilestones,
- variables: { fullPath: 'gitlab-org/gitlab', state: 'active' },
+ variables: { fullPath: 'gitlab-org/gitlab' },
},
],
[
'group',
{
query: groupBoardMilestones,
- variables: { fullPath: 'gitlab-org/gitlab', state: 'active' },
+ variables: { fullPath: 'gitlab-org/gitlab' },
},
],
])(
diff --git a/spec/frontend/broadcast_notification_spec.js b/spec/frontend/broadcast_notification_spec.js
index 8d433946632..cd947cd417a 100644
--- a/spec/frontend/broadcast_notification_spec.js
+++ b/spec/frontend/broadcast_notification_spec.js
@@ -30,6 +30,7 @@ describe('broadcast message on dismiss', () => {
expect(Cookies.set).toHaveBeenCalledWith('hide_broadcast_message_1', true, {
expires: new Date(endsAt),
+ secure: false,
});
});
});
diff --git a/spec/frontend/captcha/apollo_captcha_link_spec.js b/spec/frontend/captcha/apollo_captcha_link_spec.js
index e7ff4812ee7..eab52344d1f 100644
--- a/spec/frontend/captcha/apollo_captcha_link_spec.js
+++ b/spec/frontend/captcha/apollo_captcha_link_spec.js
@@ -1,4 +1,4 @@
-import { ApolloLink, Observable } from 'apollo-link';
+import { ApolloLink, Observable } from '@apollo/client/core';
import { apolloCaptchaLink } from '~/captcha/apollo_captcha_link';
import UnsolvedCaptchaError from '~/captcha/unsolved_captcha_error';
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 5c5ea102f12..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,10 +1,10 @@
import { GlDropdown, GlDropdownItem, GlIcon } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import CiEnvironmentsDropdown from '~/ci_variable_list/components/ci_environments_dropdown.vue';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Ci environments dropdown', () => {
let wrapper;
@@ -22,7 +22,6 @@ describe('Ci environments dropdown', () => {
wrapper = mount(CiEnvironmentsDropdown, {
store,
- localVue,
propsData: {
value: term,
},
@@ -74,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', () => {
@@ -111,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_modal_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
index 7c4ff67feb3..085ab1c0c30 100644
--- a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
+++ b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
@@ -1,5 +1,6 @@
import { GlButton, GlFormInput } from '@gitlab/ui';
-import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
+import { shallowMount, mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import { mockTracking } from 'helpers/tracking_helper';
import CiEnvironmentsDropdown from '~/ci_variable_list/components/ci_environments_dropdown.vue';
@@ -9,8 +10,7 @@ import createStore from '~/ci_variable_list/store';
import mockData from '../services/mock_data';
import ModalStub from '../stubs';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Ci variable modal', () => {
let wrapper;
@@ -26,7 +26,6 @@ describe('Ci variable modal', () => {
stubs: {
GlModal: ModalStub,
},
- localVue,
store,
...options,
});
diff --git a/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js
index 03f90f72d87..13e417940a8 100644
--- a/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js
+++ b/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js
@@ -1,10 +1,10 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import CiVariableSettings from '~/ci_variable_list/components/ci_variable_settings.vue';
import createStore from '~/ci_variable_list/store';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Ci variable table', () => {
let wrapper;
@@ -16,7 +16,6 @@ describe('Ci variable table', () => {
store.state.isGroup = groupState;
jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = shallowMount(CiVariableSettings, {
- localVue,
store,
});
};
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 8367c3f6bb8..62f9ae4eb4e 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,11 +1,11 @@
-import { createLocalVue, mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import CiVariableTable from '~/ci_variable_list/components/ci_variable_table.vue';
import createStore from '~/ci_variable_list/store';
import mockData from '../services/mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Ci variable table', () => {
let wrapper;
@@ -14,16 +14,15 @@ describe('Ci variable table', () => {
const createComponent = () => {
store = createStore();
jest.spyOn(store, 'dispatch').mockImplementation();
- wrapper = mount(CiVariableTable, {
+ wrapper = mountExtended(CiVariableTable, {
attachTo: document.body,
- localVue,
store,
});
};
- const findRevealButton = () => wrapper.find({ ref: 'secret-value-reveal-button' });
- const findEditButton = () => wrapper.find({ ref: 'edit-ci-variable' });
- const findEmptyVariablesPlaceholder = () => wrapper.find({ ref: 'empty-variables' });
+ const findRevealButton = () => wrapper.findByText('Reveal values');
+ const findEditButton = () => wrapper.findByLabelText('Edit');
+ const findEmptyVariablesPlaceholder = () => wrapper.findByText('There are no variables yet.');
beforeEach(() => {
createComponent();
@@ -37,18 +36,35 @@ describe('Ci variable table', () => {
expect(store.dispatch).toHaveBeenCalledWith('fetchVariables');
});
- describe('Renders correct data', () => {
- it('displays empty message when variables are not present', () => {
+ describe('When table is empty', () => {
+ beforeEach(() => {
+ store.state.variables = [];
+ });
+
+ it('displays empty message', () => {
expect(findEmptyVariablesPlaceholder().exists()).toBe(true);
});
- it('displays correct amount of variables present and no empty message', () => {
+ it('hides the reveal button', () => {
+ expect(findRevealButton().exists()).toBe(false);
+ });
+ });
+
+ describe('When table has variables', () => {
+ beforeEach(() => {
store.state.variables = mockData.mockVariables;
+ });
+
+ it('does not display the empty message', () => {
+ expect(findEmptyVariablesPlaceholder().exists()).toBe(false);
+ });
+
+ it('displays the reveal button', () => {
+ expect(findRevealButton().exists()).toBe(true);
+ });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.findAll('.js-ci-variable-row').length).toBe(1);
- expect(findEmptyVariablesPlaceholder().exists()).toBe(false);
- });
+ it('displays the correct amount of variables', async () => {
+ expect(wrapper.findAll('.js-ci-variable-row')).toHaveLength(1);
});
});
diff --git a/spec/frontend/clusters/agents/components/activity_events_list_spec.js b/spec/frontend/clusters/agents/components/activity_events_list_spec.js
index 4abbd77dfb7..6b374b6620d 100644
--- a/spec/frontend/clusters/agents/components/activity_events_list_spec.js
+++ b/spec/frontend/clusters/agents/components/activity_events_list_spec.js
@@ -70,8 +70,9 @@ describe('ActivityEvents', () => {
});
describe('when there are no agentEvents', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createWrapper({ queryResponse: jest.fn().mockResolvedValue(mockEmptyResponse) });
+ await waitForPromises();
});
it('displays an empty state with the correct illustration', () => {
@@ -83,9 +84,11 @@ describe('ActivityEvents', () => {
describe('when the agentEvents are present', () => {
const length = mockResponse.data?.project?.clusterAgent?.activityEvents?.nodes?.length;
- beforeEach(() => {
+ beforeEach(async () => {
createWrapper();
+ await waitForPromises();
});
+
it('renders an activity-history-item components for every event', () => {
expect(findAllActivityHistoryItems()).toHaveLength(length);
});
diff --git a/spec/frontend/clusters/agents/components/show_spec.js b/spec/frontend/clusters/agents/components/show_spec.js
index 2a3c11f4b47..f2f073544e3 100644
--- a/spec/frontend/clusters/agents/components/show_spec.js
+++ b/spec/frontend/clusters/agents/components/show_spec.js
@@ -11,12 +11,14 @@ import { useFakeDate } from 'helpers/fake_date';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import { MAX_LIST_COUNT, TOKEN_STATUS_ACTIVE } from '~/clusters/agents/constants';
const localVue = createLocalVue();
localVue.use(VueApollo);
describe('ClusterAgentShow', () => {
let wrapper;
+ let agentQueryResponse;
useFakeDate([2021, 2, 15]);
const provide = {
@@ -40,7 +42,7 @@ describe('ClusterAgentShow', () => {
};
const createWrapper = ({ clusterAgent, queryResponse = null }) => {
- const agentQueryResponse =
+ agentQueryResponse =
queryResponse ||
jest.fn().mockResolvedValue({ data: { project: { id: 'project-1', clusterAgent } } });
const apolloProvider = createMockApollo([[getAgentQuery, agentQueryResponse]]);
@@ -80,8 +82,21 @@ describe('ClusterAgentShow', () => {
});
describe('default behaviour', () => {
- beforeEach(() => {
- return createWrapper({ clusterAgent: defaultClusterAgent });
+ beforeEach(async () => {
+ createWrapper({ clusterAgent: defaultClusterAgent });
+ await waitForPromises();
+ });
+
+ it('sends expected params', () => {
+ const variables = {
+ agentName: provide.agentName,
+ projectPath: provide.projectPath,
+ tokenStatus: TOKEN_STATUS_ACTIVE,
+ first: MAX_LIST_COUNT,
+ last: null,
+ };
+
+ expect(agentQueryResponse).toHaveBeenCalledWith(variables);
});
it('displays the agent name', () => {
@@ -117,11 +132,13 @@ describe('ClusterAgentShow', () => {
createdByUser: null,
};
- beforeEach(() => {
- return createWrapper({ clusterAgent: missingUser });
+ beforeEach(async () => {
+ createWrapper({ clusterAgent: missingUser });
+ await waitForPromises();
});
- it('displays agent create information with unknown user', () => {
+ it('displays agent create information with unknown user', async () => {
+ await waitForPromises();
expect(findCreatedText()).toMatchInterpolatedText('Created by Unknown user 2 days ago');
});
});
@@ -132,23 +149,30 @@ describe('ClusterAgentShow', () => {
tokens: null,
};
- beforeEach(() => {
- return createWrapper({ clusterAgent: missingTokens });
+ beforeEach(async () => {
+ createWrapper({ clusterAgent: missingTokens });
+ await waitForPromises();
});
- it('displays token header with no count', () => {
+ it('displays token header with no count', async () => {
+ await waitForPromises();
expect(findTokenCount()).toMatchInterpolatedText(`${ClusterAgentShow.i18n.tokens}`);
});
});
describe('when the token list has additional pages', () => {
- const pageInfo = {
+ const pageInfoResponse = {
hasNextPage: true,
hasPreviousPage: false,
startCursor: 'prev',
endCursor: 'next',
};
+ const pageInfo = {
+ ...pageInfoResponse,
+ __typename: 'PageInfo',
+ };
+
const tokenPagination = {
...defaultClusterAgent,
tokens: {
@@ -157,8 +181,9 @@ describe('ClusterAgentShow', () => {
},
};
- beforeEach(() => {
- return createWrapper({ clusterAgent: tokenPagination });
+ beforeEach(async () => {
+ createWrapper({ clusterAgent: tokenPagination });
+ await waitForPromises();
});
it('should render pagination buttons', () => {
@@ -166,7 +191,7 @@ describe('ClusterAgentShow', () => {
});
it('should pass pageInfo to the pagination component', () => {
- expect(findPaginationButtons().props()).toMatchObject(pageInfo);
+ expect(findPaginationButtons().props()).toMatchObject(pageInfoResponse);
});
});
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 d041cd1e164..dd278bcd2ce 100644
--- a/spec/frontend/clusters/forms/components/integration_form_spec.js
+++ b/spec/frontend/clusters/forms/components/integration_form_spec.js
@@ -1,11 +1,11 @@
import { GlToggle, GlButton } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+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';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('ClusterIntegrationForm', () => {
let wrapper;
@@ -19,7 +19,6 @@ describe('ClusterIntegrationForm', () => {
const createWrapper = (storeValues = defaultStoreValues) => {
wrapper = shallowMount(IntegrationForm, {
- localVue,
store: createStore(storeValues),
provide: {
autoDevopsHelpPath: 'topics/autodevops/index',
@@ -76,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_table_spec.js b/spec/frontend/clusters_list/components/agent_table_spec.js
index 887c17bb4ad..dc7f0ebae74 100644
--- a/spec/frontend/clusters_list/components/agent_table_spec.js
+++ b/spec/frontend/clusters_list/components/agent_table_spec.js
@@ -1,65 +1,32 @@
import { GlLink, GlIcon } from '@gitlab/ui';
+import { sprintf } from '~/locale';
import AgentTable from '~/clusters_list/components/agent_table.vue';
-import AgentOptions from '~/clusters_list/components/agent_options.vue';
-import { ACTIVE_CONNECTION_TIME } from '~/clusters_list/constants';
+import DeleteAgentButton from '~/clusters_list/components/delete_agent_button.vue';
+import { I18N_AGENT_TABLE } from '~/clusters_list/constants';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { stubComponent } from 'helpers/stub_component';
import timeagoMixin from '~/vue_shared/mixins/timeago';
-
-const connectedTimeNow = new Date();
-const connectedTimeInactive = new Date(connectedTimeNow.getTime() - ACTIVE_CONNECTION_TIME);
+import { clusterAgents, connectedTimeNow, connectedTimeInactive } from './mock_data';
const provideData = {
- projectPath: 'path/to/project',
+ gitlabVersion: '14.8',
};
const propsData = {
- agents: [
- {
- name: 'agent-1',
- id: 'agent-1-id',
- configFolder: {
- webPath: '/agent/full/path',
- },
- webPath: '/agent-1',
- status: 'unused',
- lastContact: null,
- tokens: null,
- },
- {
- name: 'agent-2',
- id: 'agent-2-id',
- webPath: '/agent-2',
- status: 'active',
- lastContact: connectedTimeNow.getTime(),
- tokens: {
- nodes: [
- {
- lastUsedAt: connectedTimeNow,
- },
- ],
- },
- },
- {
- name: 'agent-3',
- id: 'agent-3-id',
- webPath: '/agent-3',
- status: 'inactive',
- lastContact: connectedTimeInactive.getTime(),
- tokens: {
- nodes: [
- {
- lastUsedAt: connectedTimeInactive,
- },
- ],
- },
- },
- ],
+ agents: clusterAgents,
};
-const AgentOptionsStub = stubComponent(AgentOptions, {
+const DeleteAgentButtonStub = stubComponent(DeleteAgentButton, {
template: `<div></div>`,
});
+const outdatedTitle = I18N_AGENT_TABLE.versionOutdatedTitle;
+const mismatchTitle = I18N_AGENT_TABLE.versionMismatchTitle;
+const mismatchOutdatedTitle = I18N_AGENT_TABLE.versionMismatchOutdatedTitle;
+const outdatedText = sprintf(I18N_AGENT_TABLE.versionOutdatedText, {
+ version: provideData.gitlabVersion,
+});
+const mismatchText = I18N_AGENT_TABLE.versionMismatchText;
+
describe('AgentTable', () => {
let wrapper;
@@ -67,16 +34,17 @@ describe('AgentTable', () => {
const findStatusIcon = (at) => wrapper.findAllComponents(GlIcon).at(at);
const findStatusText = (at) => wrapper.findAllByTestId('cluster-agent-connection-status').at(at);
const findLastContactText = (at) => wrapper.findAllByTestId('cluster-agent-last-contact').at(at);
+ const findVersionText = (at) => wrapper.findAllByTestId('cluster-agent-version').at(at);
const findConfiguration = (at) =>
wrapper.findAllByTestId('cluster-agent-configuration-link').at(at);
- const findAgentOptions = () => wrapper.findAllComponents(AgentOptions);
+ const findDeleteAgentButton = () => wrapper.findAllComponents(DeleteAgentButton);
beforeEach(() => {
wrapper = mountExtended(AgentTable, {
propsData,
provide: provideData,
stubs: {
- AgentOptions: AgentOptionsStub,
+ DeleteAgentButton: DeleteAgentButtonStub,
},
});
});
@@ -92,7 +60,7 @@ describe('AgentTable', () => {
agentName | link | lineNumber
${'agent-1'} | ${'/agent-1'} | ${0}
${'agent-2'} | ${'/agent-2'} | ${1}
- `('displays agent link', ({ agentName, link, lineNumber }) => {
+ `('displays agent link for $agentName', ({ agentName, link, lineNumber }) => {
expect(findAgentLink(lineNumber).text()).toBe(agentName);
expect(findAgentLink(lineNumber).attributes('href')).toBe(link);
});
@@ -102,33 +70,92 @@ describe('AgentTable', () => {
${'Never connected'} | ${'status-neutral'} | ${0}
${'Connected'} | ${'status-success'} | ${1}
${'Not connected'} | ${'severity-critical'} | ${2}
- `('displays agent connection status', ({ status, iconName, lineNumber }) => {
- expect(findStatusText(lineNumber).text()).toBe(status);
- expect(findStatusIcon(lineNumber).props('name')).toBe(iconName);
- });
+ `(
+ 'displays agent connection status as "$status" at line $lineNumber',
+ ({ status, iconName, lineNumber }) => {
+ expect(findStatusText(lineNumber).text()).toBe(status);
+ expect(findStatusIcon(lineNumber).props('name')).toBe(iconName);
+ },
+ );
it.each`
lastContact | lineNumber
${'Never'} | ${0}
${timeagoMixin.methods.timeFormatted(connectedTimeNow)} | ${1}
${timeagoMixin.methods.timeFormatted(connectedTimeInactive)} | ${2}
- `('displays agent last contact time', ({ lastContact, lineNumber }) => {
- expect(findLastContactText(lineNumber).text()).toBe(lastContact);
- });
+ `(
+ 'displays agent last contact time as "$lastContact" at line $lineNumber',
+ ({ lastContact, lineNumber }) => {
+ expect(findLastContactText(lineNumber).text()).toBe(lastContact);
+ },
+ );
+
+ describe.each`
+ agent | version | podsNumber | versionMismatch | versionOutdated | title | texts | lineNumber
+ ${'agent-1'} | ${''} | ${1} | ${false} | ${false} | ${''} | ${''} | ${0}
+ ${'agent-2'} | ${'14.8'} | ${2} | ${false} | ${false} | ${''} | ${''} | ${1}
+ ${'agent-3'} | ${'14.5'} | ${1} | ${false} | ${true} | ${outdatedTitle} | ${[outdatedText]} | ${2}
+ ${'agent-4'} | ${'14.7'} | ${2} | ${true} | ${false} | ${mismatchTitle} | ${[mismatchText]} | ${3}
+ ${'agent-5'} | ${'14.3'} | ${2} | ${true} | ${true} | ${mismatchOutdatedTitle} | ${[mismatchText, outdatedText]} | ${4}
+ `(
+ 'agent version column at line $lineNumber',
+ ({
+ agent,
+ version,
+ podsNumber,
+ versionMismatch,
+ versionOutdated,
+ title,
+ texts,
+ lineNumber,
+ }) => {
+ const findIcon = () => findVersionText(lineNumber).find(GlIcon);
+ const findPopover = () => wrapper.findByTestId(`popover-${agent}`);
+ const versionWarning = versionMismatch || versionOutdated;
+
+ it('shows the correct agent version', () => {
+ expect(findVersionText(lineNumber).text()).toBe(version);
+ });
+
+ if (versionWarning) {
+ it(`shows a warning icon when agent versions mismatch is ${versionMismatch} and outdated is ${versionOutdated} and the number of pods is ${podsNumber}`, () => {
+ expect(findIcon().props('name')).toBe('warning');
+ });
+
+ it(`renders correct title for the popover when agent versions mismatch is ${versionMismatch} and outdated is ${versionOutdated}`, () => {
+ expect(findPopover().props('title')).toBe(title);
+ });
+
+ it(`renders correct text for the popover when agent versions mismatch is ${versionMismatch} and outdated is ${versionOutdated}`, () => {
+ texts.forEach((text) => {
+ expect(findPopover().text()).toContain(text);
+ });
+ });
+ } else {
+ it(`doesn't show a warning icon with a popover when agent versions mismatch is ${versionMismatch} and outdated is ${versionOutdated} and the number of pods is ${podsNumber}`, () => {
+ expect(findIcon().exists()).toBe(false);
+ expect(findPopover().exists()).toBe(false);
+ });
+ }
+ },
+ );
it.each`
agentPath | hasLink | lineNumber
${'.gitlab/agents/agent-1'} | ${true} | ${0}
${'.gitlab/agents/agent-2'} | ${false} | ${1}
- `('displays config file path', ({ agentPath, hasLink, lineNumber }) => {
- const findLink = findConfiguration(lineNumber).find(GlLink);
+ `(
+ 'displays config file path as "$agentPath" at line $lineNumber',
+ ({ agentPath, hasLink, lineNumber }) => {
+ const findLink = findConfiguration(lineNumber).find(GlLink);
- expect(findLink.exists()).toBe(hasLink);
- expect(findConfiguration(lineNumber).text()).toBe(agentPath);
- });
+ expect(findLink.exists()).toBe(hasLink);
+ expect(findConfiguration(lineNumber).text()).toBe(agentPath);
+ },
+ );
it('displays actions menu for each agent', () => {
- expect(findAgentOptions()).toHaveLength(3);
+ expect(findDeleteAgentButton()).toHaveLength(5);
});
});
});
diff --git a/spec/frontend/clusters_list/components/agents_spec.js b/spec/frontend/clusters_list/components/agents_spec.js
index c9ca10f6bf7..3cfa4b92bc0 100644
--- a/spec/frontend/clusters_list/components/agents_spec.js
+++ b/spec/frontend/clusters_list/components/agents_spec.js
@@ -1,12 +1,18 @@
-import { GlAlert, GlKeysetPagination, GlLoadingIcon } from '@gitlab/ui';
+import { GlAlert, GlKeysetPagination, GlLoadingIcon, GlBanner } 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';
-import { ACTIVE_CONNECTION_TIME } from '~/clusters_list/constants';
+import {
+ ACTIVE_CONNECTION_TIME,
+ AGENT_FEEDBACK_KEY,
+ AGENT_FEEDBACK_ISSUE,
+} from '~/clusters_list/constants';
import getAgentsQuery from '~/clusters_list/graphql/queries/get_agents.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
const localVue = createLocalVue();
localVue.use(VueApollo);
@@ -21,13 +27,26 @@ describe('Agents', () => {
projectPath: 'path/to/project',
};
- const createWrapper = ({ props = {}, agents = [], pageInfo = null, trees = [], count = 0 }) => {
+ const createWrapper = async ({
+ props = {},
+ glFeatures = {},
+ agents = [],
+ pageInfo = null,
+ trees = [],
+ count = 0,
+ }) => {
const provide = provideData;
const apolloQueryResponse = {
data: {
project: {
id: '1',
- clusterAgents: { nodes: agents, pageInfo, tokens: { nodes: [] }, count },
+ clusterAgents: {
+ nodes: agents,
+ pageInfo,
+ connections: { nodes: [] },
+ tokens: { nodes: [] },
+ count,
+ },
repository: { tree: { trees: { nodes: trees, pageInfo } } },
},
},
@@ -44,35 +63,48 @@ describe('Agents', () => {
...defaultProps,
...props,
},
- provide: provideData,
+ provide: {
+ ...provideData,
+ glFeatures,
+ },
+ stubs: {
+ GlBanner,
+ LocalStorageSync,
+ },
});
- return wrapper.vm.$nextTick();
+ await nextTick();
};
- const findAgentTable = () => wrapper.find(AgentTable);
- const findEmptyState = () => wrapper.find(AgentEmptyState);
- const findPaginationButtons = () => wrapper.find(GlKeysetPagination);
+ const findAgentTable = () => wrapper.findComponent(AgentTable);
+ const findEmptyState = () => wrapper.findComponent(AgentEmptyState);
+ const findPaginationButtons = () => wrapper.findComponent(GlKeysetPagination);
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findBanner = () => wrapper.findComponent(GlBanner);
afterEach(() => {
- if (wrapper) {
- wrapper.destroy();
- }
+ wrapper.destroy();
+
+ localStorage.removeItem(AGENT_FEEDBACK_KEY);
});
describe('when there is a list of agents', () => {
let testDate = new Date();
const agents = [
{
+ __typename: 'ClusterAgent',
id: '1',
name: 'agent-1',
webPath: '/agent-1',
+ connections: null,
tokens: null,
},
{
+ __typename: 'ClusterAgent',
id: '2',
name: 'agent-2',
webPath: '/agent-2',
+ connections: null,
tokens: {
nodes: [
{
@@ -103,6 +135,7 @@ describe('Agents', () => {
configFolder: undefined,
status: 'unused',
lastContact: null,
+ connections: null,
tokens: null,
},
{
@@ -116,6 +149,7 @@ describe('Agents', () => {
webPath: '/agent-2',
status: 'active',
lastContact: new Date(testDate).getTime(),
+ connections: null,
tokens: {
nodes: [
{
@@ -143,6 +177,49 @@ describe('Agents', () => {
expect(wrapper.emitted().onAgentsLoad).toEqual([[count]]);
});
+ describe.each`
+ featureFlagEnabled | localStorageItemExists | bannerShown
+ ${true} | ${false} | ${true}
+ ${true} | ${true} | ${false}
+ ${false} | ${true} | ${false}
+ ${false} | ${false} | ${false}
+ `(
+ 'when the feature flag enabled is $featureFlagEnabled and dismissed localStorage item exists is $localStorageItemExists',
+ ({ featureFlagEnabled, localStorageItemExists, bannerShown }) => {
+ const glFeatures = {
+ showGitlabAgentFeedback: featureFlagEnabled,
+ };
+ beforeEach(() => {
+ if (localStorageItemExists) {
+ localStorage.setItem(AGENT_FEEDBACK_KEY, true);
+ }
+
+ return createWrapper({ glFeatures, agents, count, trees });
+ });
+
+ it(`should ${bannerShown ? 'show' : 'hide'} the feedback banner`, () => {
+ expect(findBanner().exists()).toBe(bannerShown);
+ });
+ },
+ );
+
+ describe('when the agent feedback banner is present', () => {
+ const glFeatures = {
+ showGitlabAgentFeedback: true,
+ };
+ beforeEach(() => {
+ return createWrapper({ glFeatures, agents, count, trees });
+ });
+
+ it('should render the correct title', () => {
+ expect(findBanner().props('title')).toBe('Tell us what you think');
+ });
+
+ it('should render the correct issue link', () => {
+ expect(findBanner().props('buttonLink')).toBe(AGENT_FEEDBACK_ISSUE);
+ });
+ });
+
describe('when the agent has recently connected tokens', () => {
it('should set agent status to active', () => {
expect(findAgentTable().props('agents')).toMatchObject(expectedAgentsList);
@@ -179,7 +256,10 @@ describe('Agents', () => {
beforeEach(() => {
return createWrapper({
agents,
- pageInfo,
+ pageInfo: {
+ ...pageInfo,
+ __typename: 'PageInfo',
+ },
});
});
@@ -216,6 +296,10 @@ describe('Agents', () => {
expect(findAgentTable().exists()).toBe(false);
expect(findEmptyState().exists()).toBe(true);
});
+
+ it('should not show agent feedback alert', () => {
+ expect(findAlert().exists()).toBe(false);
+ });
});
describe('when agents query has errored', () => {
@@ -224,7 +308,7 @@ describe('Agents', () => {
});
it('displays an alert message', () => {
- expect(wrapper.find(GlAlert).exists()).toBe(true);
+ expect(findAlert().text()).toBe('An error occurred while loading your Agents');
});
});
@@ -239,14 +323,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_actions_spec.js b/spec/frontend/clusters_list/components/clusters_actions_spec.js
index cb8303ca4b2..331690fc642 100644
--- a/spec/frontend/clusters_list/components/clusters_actions_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_actions_spec.js
@@ -10,9 +10,10 @@ describe('ClustersActionsComponent', () => {
const newClusterPath = 'path/to/create/cluster';
const addClusterPath = 'path/to/connect/existing/cluster';
- const provideData = {
+ const defaultProvide = {
newClusterPath,
addClusterPath,
+ canAddCluster: true,
};
const findDropdown = () => wrapper.findComponent(GlDropdown);
@@ -21,13 +22,21 @@ describe('ClustersActionsComponent', () => {
const findConnectClusterLink = () => wrapper.findByTestId('connect-cluster-link');
const findConnectNewAgentLink = () => wrapper.findByTestId('connect-new-agent-link');
- beforeEach(() => {
+ const createWrapper = (provideData = {}) => {
wrapper = shallowMountExtended(ClustersActions, {
- provide: provideData,
+ provide: {
+ ...defaultProvide,
+ ...provideData,
+ },
directives: {
GlModalDirective: createMockDirective(),
+ GlTooltip: createMockDirective(),
},
});
+ };
+
+ beforeEach(() => {
+ createWrapper();
});
afterEach(() => {
@@ -52,4 +61,24 @@ describe('ClustersActionsComponent', () => {
expect(binding.value).toBe(INSTALL_AGENT_MODAL_ID);
});
+
+ it('shows tooltip', () => {
+ const tooltip = getBinding(findDropdown().element, 'gl-tooltip');
+ expect(tooltip.value).toBe(CLUSTERS_ACTIONS.connectWithAgent);
+ });
+
+ describe('when user cannot add clusters', () => {
+ beforeEach(() => {
+ createWrapper({ canAddCluster: false });
+ });
+
+ it('disables dropdown', () => {
+ expect(findDropdown().props('disabled')).toBe(true);
+ });
+
+ it('shows tooltip explaining why dropdown is disabled', () => {
+ const tooltip = getBinding(findDropdown().element, 'gl-tooltip');
+ expect(tooltip.value).toBe(CLUSTERS_ACTIONS.dropdownDisabledHint);
+ });
+ });
});
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/clusters_view_all_spec.js b/spec/frontend/clusters_list/components/clusters_view_all_spec.js
index 6ef56beddee..2c1e3d909cc 100644
--- a/spec/frontend/clusters_list/components/clusters_view_all_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_view_all_spec.js
@@ -1,5 +1,5 @@
import { GlCard, GlLoadingIcon, GlButton, GlSprintf, GlBadge } from '@gitlab/ui';
-import { createLocalVue } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ClustersViewAll from '~/clusters_list/components/clusters_view_all.vue';
@@ -16,8 +16,7 @@ import {
} from '~/clusters_list/constants';
import { sprintf } from '~/locale';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
const addClusterPath = '/path/to/add/cluster';
const defaultBranchName = 'default-branch';
@@ -33,8 +32,9 @@ describe('ClustersViewAllComponent', () => {
defaultBranchName,
};
- const provideData = {
+ const defaultProvide = {
addClusterPath,
+ canAddCluster: true,
};
const entryData = {
@@ -46,32 +46,43 @@ describe('ClustersViewAllComponent', () => {
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findAgentsComponent = () => wrapper.findComponent(Agents);
const findClustersComponent = () => wrapper.findComponent(Clusters);
+ const findInstallAgentButtonTooltip = () => wrapper.findByTestId('install-agent-button-tooltip');
+ const findConnectExistingClusterButtonTooltip = () =>
+ wrapper.findByTestId('connect-existing-cluster-button-tooltip');
const findCardsContainer = () => wrapper.findByTestId('clusters-cards-container');
const findAgentCardTitle = () => wrapper.findByTestId('agent-card-title');
const findRecommendedBadge = () => wrapper.findComponent(GlBadge);
const findClustersCardTitle = () => wrapper.findByTestId('clusters-card-title');
const findFooterButton = (line) => findCards().at(line).findComponent(GlButton);
+ const getTooltipText = (el) => {
+ const binding = getBinding(el, 'gl-tooltip');
+
+ return binding.value;
+ };
const createStore = (initialState) =>
new Vuex.Store({
state: initialState,
});
- const createWrapper = ({ initialState }) => {
+ const createWrapper = ({ initialState = entryData, provideData } = {}) => {
wrapper = shallowMountExtended(ClustersViewAll, {
- localVue,
store: createStore(initialState),
propsData,
- provide: provideData,
+ provide: {
+ ...defaultProvide,
+ ...provideData,
+ },
directives: {
GlModalDirective: createMockDirective(),
+ GlTooltip: createMockDirective(),
},
stubs: { GlCard, GlSprintf },
});
};
beforeEach(() => {
- createWrapper({ initialState: entryData });
+ createWrapper();
});
afterEach(() => {
@@ -127,15 +138,20 @@ describe('ClustersViewAllComponent', () => {
expect(findAgentsComponent().props('defaultBranchName')).toBe(defaultBranchName);
});
+ it('should show install new Agent button in the footer', () => {
+ expect(findFooterButton(0).exists()).toBe(true);
+ expect(findFooterButton(0).props('disabled')).toBe(false);
+ });
+
+ it('does not show tooltip for install new Agent button', () => {
+ expect(getTooltipText(findInstallAgentButtonTooltip().element)).toBe('');
+ });
+
describe('when there are no agents', () => {
it('should show the empty title', () => {
expect(findAgentCardTitle().text()).toBe(AGENT_CARD_INFO.emptyTitle);
});
- it('should show install new Agent button in the footer', () => {
- expect(findFooterButton(0).exists()).toBe(true);
- });
-
it('should render correct modal id for the agent link', () => {
const binding = getBinding(findFooterButton(0).element, 'gl-modal-directive');
@@ -175,6 +191,22 @@ describe('ClustersViewAllComponent', () => {
});
});
});
+
+ describe('when the user cannot add clusters', () => {
+ beforeEach(() => {
+ createWrapper({ provideData: { canAddCluster: false } });
+ });
+
+ it('should disable the button', () => {
+ expect(findFooterButton(0).props('disabled')).toBe(true);
+ });
+
+ it('should show a tooltip explaining why the button is disabled', () => {
+ expect(getTooltipText(findInstallAgentButtonTooltip().element)).toBe(
+ AGENT_CARD_INFO.installAgentDisabledHint,
+ );
+ });
+ });
});
describe('clusters tab', () => {
@@ -191,13 +223,34 @@ describe('ClustersViewAllComponent', () => {
expect(findClustersCardTitle().text()).toBe(CERTIFICATE_BASED_CARD_INFO.emptyTitle);
});
- it('should show install new Agent button in the footer', () => {
+ it('should show install new cluster button in the footer', () => {
expect(findFooterButton(1).exists()).toBe(true);
+ expect(findFooterButton(1).props('disabled')).toBe(false);
});
it('should render correct href for the button in the footer', () => {
expect(findFooterButton(1).attributes('href')).toBe(addClusterPath);
});
+
+ it('does not show tooltip for install new cluster button', () => {
+ expect(getTooltipText(findConnectExistingClusterButtonTooltip().element)).toBe('');
+ });
+ });
+
+ describe('when the user cannot add clusters', () => {
+ beforeEach(() => {
+ createWrapper({ provideData: { canAddCluster: false } });
+ });
+
+ it('should disable the button', () => {
+ expect(findFooterButton(1).props('disabled')).toBe(true);
+ });
+
+ it('should show a tooltip explaining why the button is disabled', () => {
+ expect(getTooltipText(findConnectExistingClusterButtonTooltip().element)).toBe(
+ CERTIFICATE_BASED_CARD_INFO.connectExistingClusterDisabledHint,
+ );
+ });
});
describe('when the clusters are present', () => {
diff --git a/spec/frontend/clusters_list/components/agent_options_spec.js b/spec/frontend/clusters_list/components/delete_agent_button_spec.js
index 05bab247816..82850b9dea4 100644
--- a/spec/frontend/clusters_list/components/agent_options_spec.js
+++ b/spec/frontend/clusters_list/components/delete_agent_button_spec.js
@@ -1,13 +1,15 @@
-import { GlDropdown, GlDropdownItem, GlModal, GlFormInput } from '@gitlab/ui';
-import Vue from 'vue';
+import { GlButton, GlModal, GlFormInput } from '@gitlab/ui';
+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';
import getAgentsQuery from '~/clusters_list/graphql/queries/get_agents.query.graphql';
import deleteAgentMutation from '~/clusters_list/graphql/mutations/delete_agent.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
-import AgentOptions from '~/clusters_list/components/agent_options.vue';
-import { MAX_LIST_COUNT } from '~/clusters_list/constants';
+import waitForPromises from 'helpers/wait_for_promises';
+import DeleteAgentButton from '~/clusters_list/components/delete_agent_button.vue';
+import { MAX_LIST_COUNT, DELETE_AGENT_BUTTON } from '~/clusters_list/constants';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { getAgentResponse, mockDeleteResponse, mockErrorDeleteResponse } from '../mocks/apollo';
Vue.use(VueApollo);
@@ -21,18 +23,23 @@ const agent = {
webPath: 'agent-webPath',
};
-describe('AgentOptions', () => {
+describe('DeleteAgentButton', () => {
let wrapper;
let toast;
let apolloProvider;
let deleteResponse;
const findModal = () => wrapper.findComponent(GlModal);
- const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findDeleteBtn = () => wrapper.findComponent(GlDropdownItem);
+ const findDeleteBtn = () => wrapper.findComponent(GlButton);
const findInput = () => wrapper.findComponent(GlFormInput);
const findPrimaryAction = () => findModal().props('actionPrimary');
const findPrimaryActionAttributes = (attr) => findPrimaryAction().attributes[0][attr];
+ const findDeleteAgentButtonTooltip = () => wrapper.findByTestId('delete-agent-button-tooltip');
+ const getTooltipText = (el) => {
+ const binding = getBinding(el, 'gl-tooltip');
+
+ return binding.value;
+ };
const createMockApolloProvider = ({ mutationResponse }) => {
deleteResponse = jest.fn().mockResolvedValue(mutationResponse);
@@ -53,10 +60,14 @@ describe('AgentOptions', () => {
});
};
- const createWrapper = ({ mutationResponse = mockDeleteResponse } = {}) => {
+ const createWrapper = async ({
+ mutationResponse = mockDeleteResponse,
+ provideData = {},
+ } = {}) => {
apolloProvider = createMockApolloProvider({ mutationResponse });
- const provide = {
+ const defaultProvide = {
projectPath,
+ canAdminCluster: true,
};
const propsData = {
defaultBranchName,
@@ -66,9 +77,15 @@ describe('AgentOptions', () => {
toast = jest.fn();
- wrapper = shallowMountExtended(AgentOptions, {
+ wrapper = shallowMountExtended(DeleteAgentButton, {
apolloProvider,
- provide,
+ provide: {
+ ...defaultProvide,
+ ...provideData,
+ },
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
propsData,
mocks: { $toast: { show: toast } },
stubs: { GlModal },
@@ -76,13 +93,14 @@ describe('AgentOptions', () => {
wrapper.vm.$refs.modal.hide = jest.fn();
writeQuery();
- return wrapper.vm.$nextTick();
+ await nextTick();
};
const submitAgentToDelete = async () => {
findDeleteBtn().vm.$emit('click');
findInput().vm.$emit('input', agent.name);
await findModal().vm.$emit('primary');
+ await waitForPromises();
};
beforeEach(() => {
@@ -98,7 +116,13 @@ describe('AgentOptions', () => {
describe('delete agent action', () => {
it('displays a delete button', () => {
- expect(findDeleteBtn().text()).toBe('Delete agent');
+ expect(findDeleteBtn().attributes('aria-label')).toBe(DELETE_AGENT_BUTTON.deleteButton);
+ });
+
+ it('shows a tooltip for the button', () => {
+ expect(getTooltipText(findDeleteAgentButtonTooltip().element)).toBe(
+ DELETE_AGENT_BUTTON.deleteButton,
+ );
});
describe('when clicking the delete button', () => {
@@ -111,6 +135,22 @@ describe('AgentOptions', () => {
});
});
+ describe('when user cannot delete clusters', () => {
+ beforeEach(() => {
+ createWrapper({ provideData: { canAdminCluster: false } });
+ });
+
+ it('disables the button', () => {
+ expect(findDeleteBtn().attributes('disabled')).toBe('true');
+ });
+
+ it('shows a disabled tooltip', () => {
+ expect(getTooltipText(findDeleteAgentButtonTooltip().element)).toBe(
+ DELETE_AGENT_BUTTON.disabledHint,
+ );
+ });
+ });
+
describe.each`
condition | agentName | isDisabled | mutationCalled
${'the input with agent name is missing'} | ${''} | ${true} | ${false}
@@ -173,8 +213,7 @@ describe('AgentOptions', () => {
describe('when getting an error deleting agent', () => {
beforeEach(async () => {
await createWrapper({ mutationResponse: mockErrorDeleteResponse });
-
- submitAgentToDelete();
+ await submitAgentToDelete();
});
it('displays the error message', () => {
@@ -187,17 +226,17 @@ describe('AgentOptions', () => {
const loadingResponse = new Promise(() => {});
await createWrapper({ mutationResponse: loadingResponse });
- submitAgentToDelete();
+ await submitAgentToDelete();
});
- it('reenables the options dropdown', async () => {
+ it('reenables the button', async () => {
expect(findPrimaryActionAttributes('loading')).toBe(true);
- expect(findDropdown().attributes('disabled')).toBe('true');
+ expect(findDeleteBtn().attributes('disabled')).toBe('true');
await findModal().vm.$emit('hide');
expect(findPrimaryActionAttributes('loading')).toBe(false);
- expect(findDropdown().attributes('disabled')).toBeUndefined();
+ expect(findDeleteBtn().attributes('disabled')).toBeUndefined();
});
it('clears the agent name input', async () => {
diff --git a/spec/frontend/clusters_list/components/install_agent_modal_spec.js b/spec/frontend/clusters_list/components/install_agent_modal_spec.js
index 4d1429c9e50..37432ed0193 100644
--- a/spec/frontend/clusters_list/components/install_agent_modal_spec.js
+++ b/spec/frontend/clusters_list/components/install_agent_modal_spec.js
@@ -1,6 +1,7 @@
-import { GlAlert, GlButton, GlFormInputGroup } from '@gitlab/ui';
-import { createLocalVue } from '@vue/test-utils';
+import { GlAlert, GlButton, GlFormInputGroup, GlSprintf } from '@gitlab/ui';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
+import { sprintf } from '~/locale';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { mockTracking } from 'helpers/tracking_helper';
import AvailableAgentsDropdown from '~/clusters_list/components/available_agents_dropdown.vue';
@@ -27,15 +28,14 @@ import {
createAgentTokenResponse,
createAgentTokenErrorResponse,
getAgentResponse,
+ kasDisabledErrorResponse,
} from '../mocks/apollo';
import ModalStub from '../stubs';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
const projectPath = 'path/to/project';
const kasAddress = 'kas.example.com';
-const kasEnabled = true;
const emptyStateImage = 'path/to/image';
const defaultBranchName = 'default';
const maxAgents = MAX_LIST_COUNT;
@@ -49,7 +49,8 @@ describe('InstallAgentModal', () => {
const apolloQueryResponse = {
data: {
project: {
- id: '1',
+ __typename: 'Project',
+ id: 'project-1',
clusterAgents: { nodes: [] },
agentConfigurations: { nodes: configurations },
},
@@ -65,7 +66,7 @@ describe('InstallAgentModal', () => {
.wrappers.find((button) => button.props('variant') === variant);
const findActionButton = () => findButtonByVariant('confirm');
const findCancelButton = () => findButtonByVariant('default');
- const findSecondaryButton = () => wrapper.findByTestId('agent-secondary-button');
+ const findPrimaryButton = () => wrapper.findByTestId('agent-primary-button');
const findImage = () => wrapper.findByRole('img', { alt: I18N_AGENT_MODAL.empty_state.altText });
const expectDisabledAttribute = (element, disabled) => {
@@ -80,7 +81,6 @@ describe('InstallAgentModal', () => {
const provide = {
projectPath,
kasAddress,
- kasEnabled,
emptyStateImage,
};
@@ -92,9 +92,9 @@ describe('InstallAgentModal', () => {
wrapper = shallowMountExtended(InstallAgentModal, {
attachTo: document.body,
stubs: {
+ GlSprintf,
GlModal: ModalStub,
},
- localVue,
apolloProvider,
provide,
propsData,
@@ -118,7 +118,7 @@ describe('InstallAgentModal', () => {
createWrapper();
writeQuery();
- await wrapper.vm.$nextTick();
+ await waitForPromises();
wrapper.vm.setAgentName('agent-name');
findActionButton().vm.$emit('click');
@@ -126,11 +126,12 @@ describe('InstallAgentModal', () => {
return waitForPromises();
};
- beforeEach(() => {
+ beforeEach(async () => {
apolloProvider = createMockApollo([
[getAgentConfigurations, jest.fn().mockResolvedValue(apolloQueryResponse)],
]);
createWrapper();
+ await waitForPromises();
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
});
@@ -293,9 +294,9 @@ describe('InstallAgentModal', () => {
expect(findImage().attributes('src')).toBe(emptyStateImage);
});
- it('renders a secondary button', () => {
- expect(findSecondaryButton().isVisible()).toBe(true);
- expect(findSecondaryButton().text()).toBe(i18n.secondaryButton);
+ it('renders a primary button', () => {
+ expect(findPrimaryButton().isVisible()).toBe(true);
+ expect(findPrimaryButton().text()).toBe(i18n.primaryButton);
});
it('sends the event with the modalType', () => {
@@ -306,4 +307,35 @@ describe('InstallAgentModal', () => {
});
});
});
+
+ describe('when KAS is disabled', () => {
+ const i18n = I18N_AGENT_MODAL.empty_state;
+ beforeEach(async () => {
+ apolloProvider = createMockApollo([
+ [getAgentConfigurations, jest.fn().mockResolvedValue(kasDisabledErrorResponse)],
+ ]);
+
+ createWrapper();
+ await waitForPromises();
+ });
+
+ it('renders empty state image', () => {
+ expect(findImage().attributes('src')).toBe(emptyStateImage);
+ });
+
+ it('renders an instruction to enable the KAS', () => {
+ expect(findModal().text()).toContain(
+ sprintf(i18n.enableKasText, { linkStart: '', linkEnd: '' }),
+ );
+ });
+
+ it('renders a cancel button', () => {
+ expect(findCancelButton().isVisible()).toBe(true);
+ expect(findCancelButton().text()).toBe(i18n.done);
+ });
+
+ it("doesn't render a secondary button", () => {
+ expect(findPrimaryButton().exists()).toBe(false);
+ });
+ });
});
diff --git a/spec/frontend/clusters_list/components/mock_data.js b/spec/frontend/clusters_list/components/mock_data.js
index e388d791b89..3d18b22d727 100644
--- a/spec/frontend/clusters_list/components/mock_data.js
+++ b/spec/frontend/clusters_list/components/mock_data.js
@@ -1,3 +1,5 @@
+import { ACTIVE_CONNECTION_TIME } from '~/clusters_list/constants';
+
export const agentConfigurationsResponse = {
data: {
project: {
@@ -10,3 +12,113 @@ export const agentConfigurationsResponse = {
},
},
};
+
+export const connectedTimeNow = new Date();
+export const connectedTimeInactive = new Date(connectedTimeNow.getTime() - ACTIVE_CONNECTION_TIME);
+
+export const clusterAgents = [
+ {
+ name: 'agent-1',
+ id: 'agent-1-id',
+ configFolder: {
+ webPath: '/agent/full/path',
+ },
+ webPath: '/agent-1',
+ status: 'unused',
+ lastContact: null,
+ tokens: null,
+ },
+ {
+ name: 'agent-2',
+ id: 'agent-2-id',
+ webPath: '/agent-2',
+ status: 'active',
+ lastContact: connectedTimeNow.getTime(),
+ connections: {
+ nodes: [
+ {
+ metadata: { version: 'v14.8' },
+ },
+ {
+ metadata: { version: 'v14.8' },
+ },
+ ],
+ },
+ tokens: {
+ nodes: [
+ {
+ lastUsedAt: connectedTimeNow,
+ },
+ ],
+ },
+ },
+ {
+ name: 'agent-3',
+ id: 'agent-3-id',
+ webPath: '/agent-3',
+ status: 'inactive',
+ lastContact: connectedTimeInactive.getTime(),
+ connections: {
+ nodes: [
+ {
+ metadata: { version: 'v14.5' },
+ },
+ ],
+ },
+ tokens: {
+ nodes: [
+ {
+ lastUsedAt: connectedTimeInactive,
+ },
+ ],
+ },
+ },
+ {
+ name: 'agent-4',
+ id: 'agent-4-id',
+ webPath: '/agent-4',
+ status: 'inactive',
+ lastContact: connectedTimeInactive.getTime(),
+ connections: {
+ nodes: [
+ {
+ metadata: { version: 'v14.7' },
+ },
+ {
+ metadata: { version: 'v14.8' },
+ },
+ ],
+ },
+ tokens: {
+ nodes: [
+ {
+ lastUsedAt: connectedTimeInactive,
+ },
+ ],
+ },
+ },
+ {
+ name: 'agent-5',
+ id: 'agent-5-id',
+ webPath: '/agent-5',
+ status: 'inactive',
+ lastContact: connectedTimeInactive.getTime(),
+ connections: {
+ nodes: [
+ {
+ metadata: { version: 'v14.5' },
+ },
+ {
+ metadata: { version: 'v14.3' },
+ },
+ ],
+ },
+ tokens: {
+ nodes: [
+ {
+ lastUsedAt: connectedTimeInactive,
+ },
+ ],
+ },
+ },
+];
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/clusters_list/mocks/apollo.js b/spec/frontend/clusters_list/mocks/apollo.js
index c4a31ed4394..b0f2978a230 100644
--- a/spec/frontend/clusters_list/mocks/apollo.js
+++ b/spec/frontend/clusters_list/mocks/apollo.js
@@ -10,6 +10,9 @@ const token = {
const tokens = {
nodes: [token],
};
+const connections = {
+ nodes: [],
+};
const pageInfo = {
endCursor: '',
hasNextPage: false,
@@ -23,6 +26,7 @@ export const createAgentResponse = {
createClusterAgent: {
clusterAgent: {
...agent,
+ connections,
tokens,
},
errors: [],
@@ -35,6 +39,7 @@ export const createAgentErrorResponse = {
createClusterAgent: {
clusterAgent: {
...agent,
+ connections,
tokens,
},
errors: ['could not create agent'],
@@ -65,8 +70,9 @@ export const createAgentTokenErrorResponse = {
export const getAgentResponse = {
data: {
project: {
+ __typename: 'Project',
id: 'project-1',
- clusterAgents: { nodes: [{ ...agent, tokens }], pageInfo, count },
+ clusterAgents: { nodes: [{ ...agent, connections, tokens }], pageInfo, count },
repository: {
tree: {
trees: { nodes: [{ ...agent, path: null }], pageInfo },
@@ -76,6 +82,11 @@ export const getAgentResponse = {
},
};
+export const kasDisabledErrorResponse = {
+ data: {},
+ errors: [{ message: 'Gitlab::Kas::Client::ConfigurationError' }],
+};
+
export const mockDeleteResponse = {
data: { clusterAgentDelete: { errors: [] } },
};
diff --git a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
index 97d9be110c8..36d2c2cabc5 100644
--- a/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
+++ b/spec/frontend/code_navigation/components/__snapshots__/popover_spec.js.snap
@@ -14,7 +14,6 @@ exports[`Code navigation popover component renders popover 1`] = `
contentclass="gl-py-0"
navclass="gl-hidden"
queryparamname="tab"
- theme="indigo"
value="0"
>
<gl-tab-stub
diff --git a/spec/frontend/code_navigation/components/app_spec.js b/spec/frontend/code_navigation/components/app_spec.js
index 798f3bc0ee2..9306c15e676 100644
--- a/spec/frontend/code_navigation/components/app_spec.js
+++ b/spec/frontend/code_navigation/components/app_spec.js
@@ -1,15 +1,15 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import App from '~/code_navigation/components/app.vue';
import Popover from '~/code_navigation/components/popover.vue';
import createState from '~/code_navigation/store/state';
-const localVue = createLocalVue();
const fetchData = jest.fn();
const showDefinition = jest.fn();
let wrapper;
-localVue.use(Vuex);
+Vue.use(Vuex);
function factory(initialState = {}) {
const store = new Vuex.Store({
@@ -24,7 +24,7 @@ function factory(initialState = {}) {
},
});
- wrapper = shallowMount(App, { store, localVue });
+ wrapper = shallowMount(App, { store });
}
describe('Code navigation app component', () => {
diff --git a/spec/frontend/code_quality_walkthrough/components/step_spec.js b/spec/frontend/code_quality_walkthrough/components/step_spec.js
index bdbcda5f902..b43629c2f96 100644
--- a/spec/frontend/code_quality_walkthrough/components/step_spec.js
+++ b/spec/frontend/code_quality_walkthrough/components/step_spec.js
@@ -118,7 +118,7 @@ describe('When the code_quality_walkthrough URL parameter is present', () => {
expect(Cookies.set).toHaveBeenCalledWith(
EXPERIMENT_NAME,
{ commit_ci_file: true, data: dummyContext },
- { expires: 365 },
+ { expires: 365, secure: false },
);
});
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..203a4d23160 100644
--- a/spec/frontend/commit/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/commit/pipelines/pipelines_table_spec.js
@@ -1,11 +1,14 @@
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';
import Api from '~/api';
import PipelinesTable from '~/commit/pipelines/pipelines_table.vue';
+import httpStatusCodes from '~/lib/utils/http_status';
+import createFlash from '~/flash';
import { TOAST_MESSAGE } from '~/pipelines/constants';
import axios from '~/lib/utils/axios_utils';
@@ -13,6 +16,8 @@ const $toast = {
show: jest.fn(),
};
+jest.mock('~/flash');
+
describe('Pipelines table in Commits and Merge requests', () => {
let wrapper;
let pipeline;
@@ -183,36 +188,61 @@ describe('Pipelines table in Commits and Merge requests', () => {
mergeRequestId: 3,
});
- jest.spyOn(Api, 'postMergeRequestPipeline').mockReturnValue(Promise.resolve());
-
await waitForPromises();
});
+ describe('success', () => {
+ beforeEach(() => {
+ jest.spyOn(Api, 'postMergeRequestPipeline').mockReturnValue(Promise.resolve());
+ });
+ it('displays a toast message during pipeline creation', async () => {
+ await findRunPipelineBtn().trigger('click');
- it('displays a toast message during pipeline creation', async () => {
- await findRunPipelineBtn().trigger('click');
+ expect($toast.show).toHaveBeenCalledWith(TOAST_MESSAGE);
+ });
- expect($toast.show).toHaveBeenCalledWith(TOAST_MESSAGE);
- });
+ it('on desktop, shows a loading button', async () => {
+ await findRunPipelineBtn().trigger('click');
- it('on desktop, shows a loading button', async () => {
- await findRunPipelineBtn().trigger('click');
+ expect(findRunPipelineBtn().props('loading')).toBe(true);
- expect(findRunPipelineBtn().props('loading')).toBe(true);
+ await waitForPromises();
- await waitForPromises();
+ expect(findRunPipelineBtn().props('loading')).toBe(false);
+ });
- expect(findRunPipelineBtn().props('loading')).toBe(false);
+ it('on mobile, shows a loading button', async () => {
+ await findRunPipelineBtnMobile().trigger('click');
+
+ expect(findRunPipelineBtn().props('loading')).toBe(true);
+
+ await waitForPromises();
+
+ expect(findRunPipelineBtn().props('disabled')).toBe(false);
+ expect(findRunPipelineBtn().props('loading')).toBe(false);
+ });
});
- it('on mobile, shows a loading button', async () => {
- await findRunPipelineBtnMobile().trigger('click');
+ describe('failure', () => {
+ const permissionsMsg = 'You do not have permission to run a pipeline on this branch.';
- expect(findRunPipelineBtn().props('loading')).toBe(true);
+ it.each`
+ status | message
+ ${httpStatusCodes.BAD_REQUEST} | ${permissionsMsg}
+ ${httpStatusCodes.UNAUTHORIZED} | ${permissionsMsg}
+ ${httpStatusCodes.INTERNAL_SERVER_ERROR} | ${'An error occurred while trying to run a new pipeline for this merge request.'}
+ `('displays permissions error message', async ({ status, message }) => {
+ const response = { response: { status } };
- await waitForPromises();
+ jest
+ .spyOn(Api, 'postMergeRequestPipeline')
+ .mockImplementation(() => Promise.reject(response));
+
+ await findRunPipelineBtn().trigger('click');
- expect(findRunPipelineBtn().props('disabled')).toBe(false);
- expect(findRunPipelineBtn().props('loading')).toBe(false);
+ await waitForPromises();
+
+ expect(createFlash).toHaveBeenCalledWith({ message });
+ });
});
});
@@ -238,7 +268,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/confirm_modal_spec.js b/spec/frontend/confirm_modal_spec.js
index 5e5345cbd2b..53991349ee5 100644
--- a/spec/frontend/confirm_modal_spec.js
+++ b/spec/frontend/confirm_modal_spec.js
@@ -1,4 +1,3 @@
-import Vue from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import initConfirmModal from '~/confirm_modal';
@@ -50,7 +49,6 @@ describe('ConfirmModal', () => {
const findModal = () => document.querySelector('.gl-modal');
const findModalOkButton = (modal, variant) =>
modal.querySelector(`.modal-footer .btn-${variant}`);
- const findModalCancelButton = (modal) => modal.querySelector('.modal-footer .btn-secondary');
const modalIsHidden = () => findModal() === null;
const serializeModal = (modal, buttonIndex) => {
@@ -90,20 +88,6 @@ describe('ConfirmModal', () => {
expect(findModal()).not.toBe(null);
expect(modalIsHidden()).toBe(false);
});
-
- describe('Cancel Button', () => {
- beforeEach(() => {
- findModalCancelButton(findModal()).click();
-
- return Vue.nextTick();
- });
-
- it('closes the modal', () => {
- setImmediate(() => {
- expect(modalIsHidden()).toBe(true);
- });
- });
- });
});
});
diff --git a/spec/frontend/content_editor/test_utils.js b/spec/frontend/content_editor/test_utils.js
index b236c630e13..84eaa3c5f44 100644
--- a/spec/frontend/content_editor/test_utils.js
+++ b/spec/frontend/content_editor/test_utils.js
@@ -132,7 +132,7 @@ export const triggerNodeInputRule = ({ tiptapEditor, inputRuleText }) => {
export const triggerMarkInputRule = ({ tiptapEditor, inputRuleText }) => {
const { view } = tiptapEditor;
- tiptapEditor.chain().setContent(inputRuleText).setTextSelection(0).run();
+ tiptapEditor.chain().setContent(inputRuleText).setTextSelection(1).run();
const { state } = tiptapEditor;
const { selection } = state;
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/create_eks_cluster_spec.js b/spec/frontend/create_cluster/eks_cluster/components/create_eks_cluster_spec.js
index 95810e882a1..c8020cf8308 100644
--- a/spec/frontend/create_cluster/eks_cluster/components/create_eks_cluster_spec.js
+++ b/spec/frontend/create_cluster/eks_cluster/components/create_eks_cluster_spec.js
@@ -1,12 +1,12 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import CreateEksCluster from '~/create_cluster/eks_cluster/components/create_eks_cluster.vue';
import EksClusterConfigurationForm from '~/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue';
import ServiceCredentialsForm from '~/create_cluster/eks_cluster/components/service_credentials_form.vue';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('CreateEksCluster', () => {
let vm;
@@ -33,7 +33,6 @@ describe('CreateEksCluster', () => {
externalLinkIcon,
kubernetesIntegrationHelpPath,
},
- localVue,
store,
});
});
diff --git a/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js
index 53a6f12c381..1509d26c99d 100644
--- a/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js
+++ b/spec/frontend/create_cluster/eks_cluster/components/eks_cluster_configuration_form_spec.js
@@ -1,14 +1,13 @@
import { GlFormCheckbox } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import Vue from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import EksClusterConfigurationForm from '~/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue';
import eksClusterFormState from '~/create_cluster/eks_cluster/store/state';
import clusterDropdownStoreState from '~/create_cluster/store/cluster_dropdown/state';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('EksClusterConfigurationForm', () => {
let store;
@@ -151,7 +150,6 @@ describe('EksClusterConfigurationForm', () => {
const buildWrapper = () => {
vm = shallowMount(EksClusterConfigurationForm, {
- localVue,
store,
propsData: {
gitlabManagedClusterHelpPath: '',
@@ -225,108 +223,98 @@ describe('EksClusterConfigurationForm', () => {
});
});
- it('sets isLoadingRoles to RoleDropdown loading property', () => {
+ it('sets isLoadingRoles to RoleDropdown loading property', async () => {
rolesState.isLoadingItems = true;
- return Vue.nextTick().then(() => {
- expect(findRoleDropdown().props('loading')).toBe(rolesState.isLoadingItems);
- });
+ await nextTick();
+ expect(findRoleDropdown().props('loading')).toBe(rolesState.isLoadingItems);
});
it('sets roles to RoleDropdown items property', () => {
expect(findRoleDropdown().props('items')).toBe(rolesState.items);
});
- it('sets RoleDropdown hasErrors to true when loading roles failed', () => {
+ it('sets RoleDropdown hasErrors to true when loading roles failed', async () => {
rolesState.loadingItemsError = new Error();
- return Vue.nextTick().then(() => {
- expect(findRoleDropdown().props('hasErrors')).toEqual(true);
- });
+ await nextTick();
+ expect(findRoleDropdown().props('hasErrors')).toEqual(true);
});
it('disables KeyPairDropdown when no region is selected', () => {
expect(findKeyPairDropdown().props('disabled')).toBe(true);
});
- it('enables KeyPairDropdown when no region is selected', () => {
+ it('enables KeyPairDropdown when no region is selected', async () => {
state.selectedRegion = { name: 'west-1 ' };
- return Vue.nextTick().then(() => {
- expect(findKeyPairDropdown().props('disabled')).toBe(false);
- });
+ await nextTick();
+ expect(findKeyPairDropdown().props('disabled')).toBe(false);
});
- it('sets isLoadingKeyPairs to KeyPairDropdown loading property', () => {
+ it('sets isLoadingKeyPairs to KeyPairDropdown loading property', async () => {
keyPairsState.isLoadingItems = true;
- return Vue.nextTick().then(() => {
- expect(findKeyPairDropdown().props('loading')).toBe(keyPairsState.isLoadingItems);
- });
+ await nextTick();
+ expect(findKeyPairDropdown().props('loading')).toBe(keyPairsState.isLoadingItems);
});
it('sets keyPairs to KeyPairDropdown items property', () => {
expect(findKeyPairDropdown().props('items')).toBe(keyPairsState.items);
});
- it('sets KeyPairDropdown hasErrors to true when loading key pairs fails', () => {
+ it('sets KeyPairDropdown hasErrors to true when loading key pairs fails', async () => {
keyPairsState.loadingItemsError = new Error();
- return Vue.nextTick().then(() => {
- expect(findKeyPairDropdown().props('hasErrors')).toEqual(true);
- });
+ await nextTick();
+ expect(findKeyPairDropdown().props('hasErrors')).toEqual(true);
});
it('disables VpcDropdown when no region is selected', () => {
expect(findVpcDropdown().props('disabled')).toBe(true);
});
- it('enables VpcDropdown when no region is selected', () => {
+ it('enables VpcDropdown when no region is selected', async () => {
state.selectedRegion = { name: 'west-1 ' };
- return Vue.nextTick().then(() => {
- expect(findVpcDropdown().props('disabled')).toBe(false);
- });
+ await nextTick();
+ expect(findVpcDropdown().props('disabled')).toBe(false);
});
- it('sets isLoadingVpcs to VpcDropdown loading property', () => {
+ it('sets isLoadingVpcs to VpcDropdown loading property', async () => {
vpcsState.isLoadingItems = true;
- return Vue.nextTick().then(() => {
- expect(findVpcDropdown().props('loading')).toBe(vpcsState.isLoadingItems);
- });
+ await nextTick();
+ expect(findVpcDropdown().props('loading')).toBe(vpcsState.isLoadingItems);
});
it('sets vpcs to VpcDropdown items property', () => {
expect(findVpcDropdown().props('items')).toBe(vpcsState.items);
});
- it('sets VpcDropdown hasErrors to true when loading vpcs fails', () => {
+ it('sets VpcDropdown hasErrors to true when loading vpcs fails', async () => {
vpcsState.loadingItemsError = new Error();
- return Vue.nextTick().then(() => {
- expect(findVpcDropdown().props('hasErrors')).toEqual(true);
- });
+ await nextTick();
+ expect(findVpcDropdown().props('hasErrors')).toEqual(true);
});
it('disables SubnetDropdown when no vpc is selected', () => {
expect(findSubnetDropdown().props('disabled')).toBe(true);
});
- it('enables SubnetDropdown when a vpc is selected', () => {
+ it('enables SubnetDropdown when a vpc is selected', async () => {
state.selectedVpc = { name: 'vpc-1 ' };
- return Vue.nextTick().then(() => {
- expect(findSubnetDropdown().props('disabled')).toBe(false);
- });
+ await nextTick();
+ expect(findSubnetDropdown().props('disabled')).toBe(false);
});
- it('sets isLoadingSubnets to SubnetDropdown loading property', () => {
+ it('sets isLoadingSubnets to SubnetDropdown loading property', async () => {
subnetsState.isLoadingItems = true;
- return Vue.nextTick().then(() => {
- expect(findSubnetDropdown().props('loading')).toBe(subnetsState.isLoadingItems);
- });
+ await nextTick();
+ expect(findSubnetDropdown().props('loading')).toBe(subnetsState.isLoadingItems);
});
it('sets subnets to SubnetDropdown items property', () => {
@@ -362,32 +350,29 @@ describe('EksClusterConfigurationForm', () => {
expect(findSecurityGroupDropdown().props('disabled')).toBe(true);
});
- it('enables SecurityGroupDropdown when a vpc is selected', () => {
+ it('enables SecurityGroupDropdown when a vpc is selected', async () => {
state.selectedVpc = { name: 'vpc-1 ' };
- return Vue.nextTick().then(() => {
- expect(findSecurityGroupDropdown().props('disabled')).toBe(false);
- });
+ await nextTick();
+ expect(findSecurityGroupDropdown().props('disabled')).toBe(false);
});
- it('sets isLoadingSecurityGroups to SecurityGroupDropdown loading property', () => {
+ it('sets isLoadingSecurityGroups to SecurityGroupDropdown loading property', async () => {
securityGroupsState.isLoadingItems = true;
- return Vue.nextTick().then(() => {
- expect(findSecurityGroupDropdown().props('loading')).toBe(securityGroupsState.isLoadingItems);
- });
+ await nextTick();
+ expect(findSecurityGroupDropdown().props('loading')).toBe(securityGroupsState.isLoadingItems);
});
it('sets securityGroups to SecurityGroupDropdown items property', () => {
expect(findSecurityGroupDropdown().props('items')).toBe(securityGroupsState.items);
});
- it('sets SecurityGroupDropdown hasErrors to true when loading security groups fails', () => {
+ it('sets SecurityGroupDropdown hasErrors to true when loading security groups fails', async () => {
securityGroupsState.loadingItemsError = new Error();
- return Vue.nextTick().then(() => {
- expect(findSecurityGroupDropdown().props('hasErrors')).toEqual(true);
- });
+ await nextTick();
+ expect(findSecurityGroupDropdown().props('hasErrors')).toEqual(true);
});
it('dispatches setClusterName when cluster name input changes', () => {
diff --git a/spec/frontend/create_cluster/eks_cluster/components/service_credentials_form_spec.js b/spec/frontend/create_cluster/eks_cluster/components/service_credentials_form_spec.js
index a0510d46794..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,11 +1,11 @@
import { GlButton } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+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';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('ServiceCredentialsForm', () => {
let vm;
@@ -33,7 +33,6 @@ describe('ServiceCredentialsForm', () => {
createRoleArnHelpPath: '',
externalLinkIcon: '',
},
- localVue,
store,
});
});
@@ -66,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', () => {
@@ -87,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 2b6f2134553..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,4 +1,5 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+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';
@@ -19,15 +20,12 @@ const LABELS = {
DEFAULT: 'Select machine type',
};
-const localVue = createLocalVue();
-
-localVue.use(Vuex);
+Vue.use(Vuex);
const createComponent = (store, propsData = componentConfig) =>
shallowMount(GkeMachineTypeDropdown, {
propsData,
store,
- localVue,
});
const createStore = (initialState = {}, getters = {}) =>
@@ -75,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);
@@ -83,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', () => {
@@ -115,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,
});
@@ -125,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_network_dropdown_spec.js b/spec/frontend/create_cluster/gke_cluster/components/gke_network_dropdown_spec.js
index 23a56766037..addb0ef72a0 100644
--- a/spec/frontend/create_cluster/gke_cluster/components/gke_network_dropdown_spec.js
+++ b/spec/frontend/create_cluster/gke_cluster/components/gke_network_dropdown_spec.js
@@ -1,12 +1,11 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import ClusterFormDropdown from '~/create_cluster/components/cluster_form_dropdown.vue';
import GkeNetworkDropdown from '~/create_cluster/gke_cluster/components/gke_network_dropdown.vue';
import createClusterDropdownState from '~/create_cluster/store/cluster_dropdown/state';
-const localVue = createLocalVue();
-
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('GkeNetworkDropdown', () => {
let wrapper;
@@ -54,7 +53,6 @@ describe('GkeNetworkDropdown', () => {
shallowMount(GkeNetworkDropdown, {
propsData,
store,
- localVue,
});
afterEach(() => {
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 2b0acc8cf5d..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,4 +1,5 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+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';
@@ -19,9 +20,7 @@ const LABELS = {
EMPTY: 'No projects found',
};
-const localVue = createLocalVue();
-
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('GkeProjectIdDropdown', () => {
let wrapper;
@@ -52,7 +51,6 @@ describe('GkeProjectIdDropdown', () => {
shallowMount(GkeProjectIdDropdown, {
propsData,
store,
- localVue,
});
const bootstrap = (initialState, getters) => {
@@ -80,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,
@@ -105,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,
});
@@ -118,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_submit_button_spec.js b/spec/frontend/create_cluster/gke_cluster/components/gke_submit_button_spec.js
index 014ed6013bd..2bf9158628c 100644
--- a/spec/frontend/create_cluster/gke_cluster/components/gke_submit_button_spec.js
+++ b/spec/frontend/create_cluster/gke_cluster/components/gke_submit_button_spec.js
@@ -1,10 +1,9 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import GkeSubmitButton from '~/create_cluster/gke_cluster/components/gke_submit_button.vue';
-const localVue = createLocalVue();
-
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('GkeSubmitButton', () => {
let wrapper;
@@ -21,7 +20,6 @@ describe('GkeSubmitButton', () => {
const buildWrapper = () =>
shallowMount(GkeSubmitButton, {
store,
- localVue,
});
const bootstrap = () => {
diff --git a/spec/frontend/create_cluster/gke_cluster/components/gke_subnetwork_dropdown_spec.js b/spec/frontend/create_cluster/gke_cluster/components/gke_subnetwork_dropdown_spec.js
index cfa8a678a9b..9df680d94b5 100644
--- a/spec/frontend/create_cluster/gke_cluster/components/gke_subnetwork_dropdown_spec.js
+++ b/spec/frontend/create_cluster/gke_cluster/components/gke_subnetwork_dropdown_spec.js
@@ -1,12 +1,11 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import ClusterFormDropdown from '~/create_cluster/components/cluster_form_dropdown.vue';
import GkeSubnetworkDropdown from '~/create_cluster/gke_cluster/components/gke_subnetwork_dropdown.vue';
import createClusterDropdownState from '~/create_cluster/store/cluster_dropdown/state';
-const localVue = createLocalVue();
-
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('GkeSubnetworkDropdown', () => {
let wrapper;
@@ -41,7 +40,6 @@ describe('GkeSubnetworkDropdown', () => {
shallowMount(GkeSubnetworkDropdown, {
propsData,
store,
- localVue,
});
afterEach(() => {
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/base_spec.js b/spec/frontend/cycle_analytics/base_spec.js
index 9a9415cc12a..7b1ef71da63 100644
--- a/spec/frontend/cycle_analytics/base_spec.js
+++ b/spec/frontend/cycle_analytics/base_spec.js
@@ -3,11 +3,11 @@ import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
import BaseComponent from '~/cycle_analytics/components/base.vue';
import PathNavigation from '~/cycle_analytics/components/path_navigation.vue';
import StageTable from '~/cycle_analytics/components/stage_table.vue';
import ValueStreamFilters from '~/cycle_analytics/components/value_stream_filters.vue';
-import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue';
import { NOT_ENOUGH_DATA_ERROR } from '~/cycle_analytics/constants';
import initState from '~/cycle_analytics/store/state';
import {
diff --git a/spec/frontend/cycle_analytics/filter_bar_spec.js b/spec/frontend/cycle_analytics/filter_bar_spec.js
index 407f21bd956..36933790cf7 100644
--- a/spec/frontend/cycle_analytics/filter_bar_spec.js
+++ b/spec/frontend/cycle_analytics/filter_bar_spec.js
@@ -1,4 +1,5 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import Vuex from 'vuex';
@@ -15,8 +16,7 @@ import * as utils from '~/vue_shared/components/filtered_search_bar/filtered_sea
import initialFiltersState from '~/vue_shared/components/filtered_search_bar/store/modules/filters/state';
import UrlSync from '~/vue_shared/components/url_sync.vue';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
const milestoneTokenType = 'milestone';
const labelsTokenType = 'labels';
@@ -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,
});
@@ -77,7 +77,6 @@ describe('Filter bar', () => {
const createComponent = (initialStore) => {
return shallowMount(FilterBar, {
- localVue,
store: initialStore,
propsData: {
groupPath: 'foo',
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/utils_spec.js b/spec/frontend/cycle_analytics/utils_spec.js
index a6d6d022781..51405a1ba4d 100644
--- a/spec/frontend/cycle_analytics/utils_spec.js
+++ b/spec/frontend/cycle_analytics/utils_spec.js
@@ -1,13 +1,10 @@
-import metricsData from 'test_fixtures/projects/analytics/value_stream_analytics/summary.json';
import {
transformStagesForPathNavigation,
medianTimeToParsedSeconds,
formatMedianValues,
filterStagesByHiddenStatus,
- prepareTimeMetricsData,
buildCycleAnalyticsInitialData,
} from '~/cycle_analytics/utils';
-import { slugify } from '~/lib/utils/text_utility';
import {
selectedStage,
allowedStages,
@@ -89,34 +86,6 @@ describe('Value stream analytics utils', () => {
});
});
- describe('prepareTimeMetricsData', () => {
- let prepared;
- const [first, second] = metricsData;
- const firstKey = slugify(first.title);
- const secondKey = slugify(second.title);
-
- beforeEach(() => {
- prepared = prepareTimeMetricsData([first, second], {
- [firstKey]: { description: 'Is a value that is good' },
- });
- });
-
- it('will add a `key` based on the title', () => {
- expect(prepared).toMatchObject([{ key: firstKey }, { key: secondKey }]);
- });
-
- it('will add a `label` key', () => {
- expect(prepared).toMatchObject([{ label: 'New Issues' }, { label: 'Commits' }]);
- });
-
- it('will add a popover description using the key if it is provided', () => {
- expect(prepared).toMatchObject([
- { description: 'Is a value that is good' },
- { description: '' },
- ]);
- });
- });
-
describe('buildCycleAnalyticsInitialData', () => {
let res = null;
const projectId = '5';
diff --git a/spec/frontend/cycle_analytics/value_stream_metrics_spec.js b/spec/frontend/cycle_analytics/value_stream_metrics_spec.js
index 082db2cc312..7a539b262fc 100644
--- a/spec/frontend/cycle_analytics/value_stream_metrics_spec.js
+++ b/spec/frontend/cycle_analytics/value_stream_metrics_spec.js
@@ -1,20 +1,22 @@
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 ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
import { METRIC_TYPE_SUMMARY } from '~/api/analytics_api';
-import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue';
+import { METRICS_POPOVER_CONTENT } from '~/analytics/shared/constants';
+import { prepareTimeMetricsData } from '~/analytics/shared/utils';
+import MetricTile from '~/analytics/shared/components/metric_tile.vue';
import createFlash from '~/flash';
-import { redirectTo } from '~/lib/utils/url_utility';
import { group } from './mock_data';
jest.mock('~/flash');
-jest.mock('~/lib/utils/url_utility');
describe('ValueStreamMetrics', () => {
let wrapper;
let mockGetValueStreamSummaryMetrics;
+ let mockFilterFn;
const { full_path: requestPath } = group;
const fakeReqName = 'Mock metrics';
@@ -24,17 +26,18 @@ describe('ValueStreamMetrics', () => {
name: fakeReqName,
});
- const createComponent = ({ requestParams = {} } = {}) => {
+ const createComponent = (props = {}) => {
return shallowMount(ValueStreamMetrics, {
propsData: {
requestPath,
- requestParams,
+ requestParams: {},
requests: [metricsRequestFactory()],
+ ...props,
},
});
};
- const findMetrics = () => wrapper.findAllComponents(GlSingleStat);
+ const findMetrics = () => wrapper.findAllComponents(MetricTile);
const expectToHaveRequest = (fields) => {
expect(mockGetValueStreamSummaryMetrics).toHaveBeenCalledWith({
@@ -55,19 +58,19 @@ describe('ValueStreamMetrics', () => {
});
it('will display a loader with pending requests', async () => {
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.findComponent(GlSkeletonLoading).exists()).toBe(true);
});
- it('renders hidden GlSingleStat components for each metric', async () => {
+ it('renders hidden MetricTile components for each metric', async () => {
await waitForPromises();
// 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 });
- await wrapper.vm.$nextTick();
+ await nextTick();
const components = findMetrics();
@@ -88,35 +91,52 @@ describe('ValueStreamMetrics', () => {
});
describe.each`
- index | value | title | unit | clickable
- ${0} | ${metricsData[0].value} | ${metricsData[0].title} | ${metricsData[0].unit} | ${false}
- ${1} | ${metricsData[1].value} | ${metricsData[1].title} | ${metricsData[1].unit} | ${false}
- ${2} | ${metricsData[2].value} | ${metricsData[2].title} | ${metricsData[2].unit} | ${false}
- ${3} | ${metricsData[3].value} | ${metricsData[3].title} | ${metricsData[3].unit} | ${true}
- `('metric tiles', ({ index, value, title, unit, clickable }) => {
- it(`renders a single stat component for "${title}" with value and unit`, () => {
+ index | identifier | value | label
+ ${0} | ${metricsData[0].identifier} | ${metricsData[0].value} | ${metricsData[0].title}
+ ${1} | ${metricsData[1].identifier} | ${metricsData[1].value} | ${metricsData[1].title}
+ ${2} | ${metricsData[2].identifier} | ${metricsData[2].value} | ${metricsData[2].title}
+ ${3} | ${metricsData[3].identifier} | ${metricsData[3].value} | ${metricsData[3].title}
+ `('metric tiles', ({ identifier, index, value, label }) => {
+ it(`renders a metric tile component for "${label}"`, () => {
const metric = findMetrics().at(index);
- expect(metric.props()).toMatchObject({ value, title, unit: unit ?? '' });
+ expect(metric.props('metric')).toMatchObject({ identifier, value, label });
expect(metric.isVisible()).toBe(true);
});
-
- it(`${
- clickable ? 'redirects' : "doesn't redirect"
- } when the user clicks the "${title}" metric`, () => {
- const metric = findMetrics().at(index);
- metric.vm.$emit('click');
- if (clickable) {
- expect(redirectTo).toHaveBeenCalledWith(metricsData[index].links[0].url);
- } else {
- expect(redirectTo).not.toHaveBeenCalled();
- }
- });
});
it('will not display a loading icon', () => {
expect(wrapper.find(GlSkeletonLoading).exists()).toBe(false);
});
+ describe('filterFn', () => {
+ const transferedMetricsData = prepareTimeMetricsData(metricsData, METRICS_POPOVER_CONTENT);
+
+ it('with a filter function, will call the function with the metrics data', async () => {
+ const filteredData = [
+ { identifier: 'issues', value: '3', title: 'New Issues', description: 'foo' },
+ ];
+ mockFilterFn = jest.fn(() => filteredData);
+
+ wrapper = createComponent({
+ filterFn: mockFilterFn,
+ });
+
+ await waitForPromises();
+
+ expect(mockFilterFn).toHaveBeenCalledWith(transferedMetricsData);
+ expect(wrapper.vm.metrics).toEqual(filteredData);
+ });
+
+ it('without a filter function, it will only update the metrics', async () => {
+ wrapper = createComponent();
+
+ await waitForPromises();
+
+ expect(mockFilterFn).not.toHaveBeenCalled();
+ expect(wrapper.vm.metrics).toEqual(transferedMetricsData);
+ });
+ });
+
describe('with additional params', () => {
beforeEach(async () => {
wrapper = createComponent({
diff --git a/spec/frontend/deploy_freeze/components/deploy_freeze_settings_spec.js b/spec/frontend/deploy_freeze/components/deploy_freeze_settings_spec.js
index 392652292cf..cc044800e5e 100644
--- a/spec/frontend/deploy_freeze/components/deploy_freeze_settings_spec.js
+++ b/spec/frontend/deploy_freeze/components/deploy_freeze_settings_spec.js
@@ -1,4 +1,5 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import DeployFreezeModal from '~/deploy_freeze/components/deploy_freeze_modal.vue';
import DeployFreezeSettings from '~/deploy_freeze/components/deploy_freeze_settings.vue';
@@ -6,8 +7,7 @@ import DeployFreezeTable from '~/deploy_freeze/components/deploy_freeze_table.vu
import createStore from '~/deploy_freeze/store';
import { timezoneDataFixture } from '../helpers';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Deploy freeze settings', () => {
let wrapper;
@@ -20,7 +20,6 @@ describe('Deploy freeze settings', () => {
});
jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = shallowMount(DeployFreezeSettings, {
- localVue,
store,
});
});
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 403d0dce3fc..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,13 +1,13 @@
import { GlModal } from '@gitlab/ui';
-import { createLocalVue, mount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+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';
import { RECEIVE_FREEZE_PERIODS_SUCCESS } from '~/deploy_freeze/store/mutation_types';
import { freezePeriodsFixture, timezoneDataFixture } from '../helpers';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Deploy freeze table', () => {
let wrapper;
@@ -21,7 +21,6 @@ describe('Deploy freeze table', () => {
jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = mount(DeployFreezeTable, {
attachTo: document.body,
- localVue,
store,
});
};
@@ -57,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', () => {
@@ -69,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_freeze/components/timezone_dropdown_spec.js b/spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js
index 5f4d4071f29..aea81daecef 100644
--- a/spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js
+++ b/spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js
@@ -1,12 +1,12 @@
import { GlDropdownItem, GlDropdown } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import createStore from '~/deploy_freeze/store';
import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown.vue';
import { findTzByName, formatTz, timezoneDataFixture } from '../helpers';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Deploy freeze timezone dropdown', () => {
let wrapper;
@@ -19,7 +19,6 @@ describe('Deploy freeze timezone dropdown', () => {
});
wrapper = shallowMount(TimezoneDropdown, {
store,
- localVue,
propsData: {
value: selectedTimezone,
timezoneData: timezoneDataFixture,
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..0cef18c60de 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();
@@ -30,6 +31,10 @@ describe('Design reply form component', () => {
});
}
+ beforeEach(() => {
+ gon.features = { markdownContinueLists: true };
+ });
+
afterEach(() => {
wrapper.destroy();
});
@@ -64,24 +69,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 +111,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 +149,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 +163,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..a818a86bef6 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,17 +245,19 @@ 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`, () => {
jest.spyOn(Cookies, 'set');
wrapper.trigger('click');
- expect(Cookies.set).toHaveBeenCalledWith(cookieKey, 'true', { expires: 365 * 10 });
+ expect(Cookies.set).toHaveBeenCalledWith(cookieKey, 'true', {
+ expires: 365 * 10,
+ secure: false,
+ });
});
});
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 ed105b112be..e00dda2015e 100644
--- a/spec/frontend/design_management/components/list/item_spec.js
+++ b/spec/frontend/design_management/components/list/item_spec.js
@@ -1,11 +1,11 @@
import { GlIcon, GlLoadingIcon, GlIntersectionObserver } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+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';
-const localVue = createLocalVue();
-localVue.use(VueRouter);
+Vue.use(VueRouter);
const router = new VueRouter();
// Referenced from: gitlab_schema.graphql:DesignVersionEvent
@@ -34,7 +34,6 @@ describe('Design management list item component', () => {
} = {}) {
wrapper = extendedWrapper(
shallowMount(Item, {
- localVue,
router,
propsData: {
id: imgId,
@@ -72,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', () => {
@@ -92,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', () => {
@@ -102,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 cf872046f53..412f3de911e 100644
--- a/spec/frontend/design_management/components/toolbar/index_spec.js
+++ b/spec/frontend/design_management/components/toolbar/index_spec.js
@@ -1,12 +1,12 @@
import { GlButton } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+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';
import { DESIGNS_ROUTE_NAME } from '~/design_management/router/constants';
-const localVue = createLocalVue();
-localVue.use(VueRouter);
+Vue.use(VueRouter);
const router = new VueRouter();
const RouterLinkStub = {
@@ -28,7 +28,6 @@ describe('Design management toolbar component', () => {
updatedAt.setHours(updatedAt.getHours() - 1);
wrapper = shallowMount(Toolbar, {
- localVue,
router,
propsData: {
id: '1',
@@ -61,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 98e2313e9f2..55d0fabe402 100644
--- a/spec/frontend/design_management/pages/design/index_spec.js
+++ b/spec/frontend/design_management/pages/design/index_spec.js
@@ -1,5 +1,6 @@
import { GlAlert } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import { ApolloMutation } from 'vue-apollo';
import VueRouter from 'vue-router';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
@@ -78,8 +79,7 @@ const createDiscussionMutationVariables = {
},
};
-const localVue = createLocalVue();
-localVue.use(VueRouter);
+Vue.use(VueRouter);
describe('Design management design index page', () => {
let wrapper;
@@ -128,7 +128,6 @@ describe('Design management design index page', () => {
...data,
};
},
- localVue,
router,
});
}
@@ -154,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);
});
@@ -201,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 },
{
@@ -213,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', () => {
@@ -234,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 },
{
@@ -249,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 },
{
@@ -275,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', () => {
@@ -300,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
@@ -328,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/design_management/pages/index_spec.js b/spec/frontend/design_management/pages/index_spec.js
index dd0f7972553..a240a41959f 100644
--- a/spec/frontend/design_management/pages/index_spec.js
+++ b/spec/frontend/design_management/pages/index_spec.js
@@ -1,10 +1,12 @@
import { GlEmptyState } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+
import VueApollo, { ApolloMutation } from 'vue-apollo';
import VueRouter from 'vue-router';
import VueDraggable from 'vuedraggable';
import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import permissionsQuery from 'shared_queries/design_management/design_permissions.query.graphql';
import getDesignListQuery from 'shared_queries/design_management/get_design_list.query.graphql';
@@ -48,9 +50,8 @@ jest.spyOn(utils, 'getPageLayoutElement').mockReturnValue(mockPageEl);
const scrollIntoViewMock = jest.fn();
HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
-const localVue = createLocalVue();
const router = createRouter();
-localVue.use(VueRouter);
+Vue.use(VueRouter);
const mockDesigns = [
{
@@ -115,8 +116,7 @@ describe('Design management index page', () => {
const findDesignToolbarWrapper = () => wrapper.find('[data-testid="design-toolbar-wrapper"]');
async function moveDesigns(localWrapper) {
- await jest.runOnlyPendingTimers();
- await nextTick();
+ await waitForPromises();
localWrapper.find(VueDraggable).vm.$emit('input', reorderedDesigns);
localWrapper.find(VueDraggable).vm.$emit('change', {
@@ -159,7 +159,6 @@ describe('Design management index page', () => {
};
},
mocks: { $apollo },
- localVue,
router,
stubs: { DesignDestroyer, ApolloMutation, VueDraggable, ...stubs },
attachTo: document.body,
@@ -175,7 +174,7 @@ describe('Design management index page', () => {
function createComponentWithApollo({
moveHandler = jest.fn().mockResolvedValue(moveDesignMutationResponse),
}) {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
moveDesignHandler = moveHandler;
const requestHandlers = [
@@ -186,7 +185,6 @@ describe('Design management index page', () => {
fakeApollo = createMockApollo(requestHandlers);
wrapper = shallowMount(Index, {
- localVue,
apolloProvider: fakeApollo,
router,
stubs: { VueDraggable },
@@ -746,9 +744,7 @@ describe('Design management index page', () => {
describe('with mocked Apollo client', () => {
it('has a design with id 1 as a first one', async () => {
createComponentWithApollo({});
-
- await jest.runOnlyPendingTimers();
- await nextTick();
+ await waitForPromises();
expect(findDesigns()).toHaveLength(3);
expect(findDesigns().at(0).props('id')).toBe('1');
@@ -761,21 +757,18 @@ describe('Design management index page', () => {
expect(moveDesignHandler).toHaveBeenCalled();
- await nextTick();
+ await waitForPromises();
expect(findDesigns().at(0).props('id')).toBe('2');
});
it('prevents reordering when reorderDesigns mutation is in progress', async () => {
createComponentWithApollo({});
-
await moveDesigns(wrapper);
expect(draggableAttributes().disabled).toBe(true);
- await jest.runOnlyPendingTimers(); // kick off the mocked GQL stuff (promises)
- await nextTick(); // kick off the DOM update
- await nextTick(); // kick off the DOM update for finally block
+ await waitForPromises();
expect(draggableAttributes().disabled).toBe(false);
});
@@ -786,8 +779,7 @@ describe('Design management index page', () => {
});
await moveDesigns(wrapper);
-
- await nextTick();
+ await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ message: 'Houston, we have a problem' });
});
@@ -798,10 +790,7 @@ describe('Design management index page', () => {
});
await moveDesigns(wrapper);
-
- await nextTick(); // kick off the DOM update
- await jest.runOnlyPendingTimers(); // kick off the mocked GQL stuff (promises)
- await nextTick(); // kick off the DOM update for flash
+ await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({
message: 'Something went wrong when reordering designs. Please try again',
diff --git a/spec/frontend/design_management/router_spec.js b/spec/frontend/design_management/router_spec.js
index ac5e6895408..03ab79712a4 100644
--- a/spec/frontend/design_management/router_spec.js
+++ b/spec/frontend/design_management/router_spec.js
@@ -1,5 +1,5 @@
-import { mount, createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import VueRouter from 'vue-router';
import App from '~/design_management/components/app.vue';
import DesignDetail from '~/design_management/pages/design/index.vue';
@@ -9,8 +9,7 @@ import { DESIGNS_ROUTE_NAME, DESIGN_ROUTE_NAME } from '~/design_management/route
import '~/commons/bootstrap';
function factory(routeArg) {
- const localVue = createLocalVue();
- localVue.use(VueRouter);
+ Vue.use(VueRouter);
window.gon = { sprite_icons: '' };
@@ -20,7 +19,6 @@ function factory(routeArg) {
}
return mount(App, {
- localVue,
router,
mocks: {
$apollo: {
diff --git a/spec/frontend/design_management/utils/cache_update_spec.js b/spec/frontend/design_management/utils/cache_update_spec.js
index fa6a666bb37..5e2c37e24a1 100644
--- a/spec/frontend/design_management/utils/cache_update_spec.js
+++ b/spec/frontend/design_management/utils/cache_update_spec.js
@@ -1,4 +1,4 @@
-import { InMemoryCache } from 'apollo-cache-inmemory';
+import { InMemoryCache } from '@apollo/client/core';
import {
updateStoreAfterDesignsDelete,
updateStoreAfterAddImageDiffNote,
diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js
index d50ac0529d6..76e4a944d87 100644
--- a/spec/frontend/diffs/components/app_spec.js
+++ b/spec/frontend/diffs/components/app_spec.js
@@ -69,6 +69,12 @@ describe('diffs/components/app', () => {
},
provide,
store,
+ stubs: {
+ DynamicScroller: {
+ template: `<div><slot :item="$store.state.diffs.diffFiles[0]"></slot></div>`,
+ },
+ DynamicScrollerItem: true,
+ },
});
}
@@ -154,22 +160,6 @@ describe('diffs/components/app', () => {
});
});
- it.each`
- props | state | expected
- ${{ isFluidLayout: true }} | ${{ isParallelView: false }} | ${false}
- ${{}} | ${{ isParallelView: false }} | ${true}
- ${{}} | ${{ showTreeList: true, diffFiles: [{}], isParallelView: false }} | ${false}
- ${{}} | ${{ showTreeList: false, diffFiles: [{}], isParallelView: false }} | ${true}
- ${{}} | ${{ showTreeList: false, diffFiles: [], isParallelView: false }} | ${true}
- `(
- 'uses container-limiting classes ($expected) with state ($state) and props ($props)',
- ({ props, state, expected }) => {
- createComponent(props, ({ state: origState }) => Object.assign(origState.diffs, state));
-
- expect(wrapper.find('.container-limited.limit-container-width').exists()).toBe(expected);
- },
- );
-
it('displays loading icon on loading', () => {
createComponent({}, ({ state }) => {
state.diffs.isLoading = true;
@@ -498,7 +488,6 @@ describe('diffs/components/app', () => {
expect(wrapper.find(CompareVersions).exists()).toBe(true);
expect(wrapper.find(CompareVersions).props()).toEqual(
expect.objectContaining({
- isLimitedContainer: false,
diffFilesCountText: null,
}),
);
diff --git a/spec/frontend/diffs/components/collapsed_files_warning_spec.js b/spec/frontend/diffs/components/collapsed_files_warning_spec.js
index 46caeb01132..8cc342e45a7 100644
--- a/spec/frontend/diffs/components/collapsed_files_warning_spec.js
+++ b/spec/frontend/diffs/components/collapsed_files_warning_spec.js
@@ -1,8 +1,8 @@
-import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMount, mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import CollapsedFilesWarning from '~/diffs/components/collapsed_files_warning.vue';
-import { CENTERED_LIMITED_CONTAINER_CLASSES, EVT_EXPAND_ALL_FILES } from '~/diffs/constants';
+import { EVT_EXPAND_ALL_FILES } from '~/diffs/constants';
import eventHub from '~/diffs/event_hub';
import createStore from '~/diffs/store/modules';
@@ -13,21 +13,19 @@ const propsData = {
mergeable: true,
resolutionPath: 'a-path',
};
-const limitedClasses = CENTERED_LIMITED_CONTAINER_CLASSES.split(' ');
async function files(store, count) {
const copies = Array(count).fill(file);
store.state.diffs.diffFiles.push(...copies);
- return nextTick();
+ await nextTick();
}
describe('CollapsedFilesWarning', () => {
- const localVue = createLocalVue();
let store;
let wrapper;
- localVue.use(Vuex);
+ Vue.use(Vuex);
const getAlertActionButton = () =>
wrapper.find(CollapsedFilesWarning).find('button.gl-alert-action:first-child');
@@ -43,7 +41,6 @@ describe('CollapsedFilesWarning', () => {
wrapper = mounter(CollapsedFilesWarning, {
propsData: { ...propsData, ...props },
- localVue,
store,
});
};
@@ -54,20 +51,6 @@ describe('CollapsedFilesWarning', () => {
describe('when there is more than one file', () => {
it.each`
- limited | containerClasses
- ${true} | ${limitedClasses}
- ${false} | ${[]}
- `(
- 'has the correct container classes when limited is $limited',
- async ({ limited, containerClasses }) => {
- createComponent({ limited });
- await files(store, 2);
-
- expect(wrapper.classes()).toEqual(['col-12'].concat(containerClasses));
- },
- );
-
- it.each`
present | dismissed
${false} | ${true}
${true} | ${false}
@@ -86,7 +69,7 @@ describe('CollapsedFilesWarning', () => {
getAlertCloseButton().element.click();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('[data-testid="root"]').exists()).toBe(false);
});
diff --git a/spec/frontend/diffs/components/compare_versions_spec.js b/spec/frontend/diffs/components/compare_versions_spec.js
index c48935bc4f0..21f3ee26bf8 100644
--- a/spec/frontend/diffs/components/compare_versions_spec.js
+++ b/spec/frontend/diffs/components/compare_versions_spec.js
@@ -1,4 +1,5 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import getDiffWithCommit from 'test_fixtures/merge_request_diffs/with_commit.json';
import setWindowLocation from 'helpers/set_window_location_helper';
@@ -8,8 +9,7 @@ import CompareVersionsComponent from '~/diffs/components/compare_versions.vue';
import { createStore } from '~/mr_notes/stores';
import diffsMockData from '../mock_data/merge_request_diffs';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
const NEXT_COMMIT_URL = `${TEST_HOST}/?commit_id=next`;
const PREV_COMMIT_URL = `${TEST_HOST}/?commit_id=prev`;
@@ -30,7 +30,6 @@ describe('CompareVersions', () => {
}
wrapper = mount(CompareVersionsComponent, {
- localVue,
store,
propsData: {
mergeRequestDiffs: diffsMockData,
@@ -39,7 +38,6 @@ describe('CompareVersions', () => {
},
});
};
- const findLimitedContainer = () => wrapper.find('.container-limited.limit-container-width');
const findCompareSourceDropdown = () => wrapper.find('.mr-version-dropdown');
const findCompareTargetDropdown = () => wrapper.find('.mr-version-compare-dropdown');
const getCommitNavButtonsElement = () => wrapper.find('.commit-nav-buttons');
@@ -99,18 +97,6 @@ describe('CompareVersions', () => {
expect(inlineBtn.html()).toContain('Inline');
expect(parallelBtn.html()).toContain('Side-by-side');
});
-
- it('adds container-limiting classes when showFileTree is false with inline diffs', () => {
- createWrapper({ isLimitedContainer: true });
-
- expect(findLimitedContainer().exists()).toBe(true);
- });
-
- it('does not add container-limiting classes when showFileTree is false with inline diffs', () => {
- createWrapper({ isLimitedContainer: false });
-
- expect(findLimitedContainer().exists()).toBe(false);
- });
});
describe('noChangedFiles', () => {
@@ -233,14 +219,13 @@ describe('CompareVersions', () => {
expect(link.element.getAttribute('href')).toEqual(PREV_COMMIT_URL);
});
- it('triggers the correct Vuex action on click', () => {
+ it('triggers the correct Vuex action on click', async () => {
const link = getPrevCommitNavElement();
link.trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.moveToNeighboringCommit).toHaveBeenCalledWith({
- direction: 'previous',
- });
+ await nextTick();
+ expect(wrapper.vm.moveToNeighboringCommit).toHaveBeenCalledWith({
+ direction: 'previous',
});
});
@@ -268,13 +253,12 @@ describe('CompareVersions', () => {
expect(link.element.getAttribute('href')).toEqual(NEXT_COMMIT_URL);
});
- it('triggers the correct Vuex action on click', () => {
+ it('triggers the correct Vuex action on click', async () => {
const link = getNextCommitNavElement();
link.trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.moveToNeighboringCommit).toHaveBeenCalledWith({ direction: 'next' });
- });
+ await nextTick();
+ expect(wrapper.vm.moveToNeighboringCommit).toHaveBeenCalledWith({ direction: 'next' });
});
it('renders a disabled button when there is no next commit', () => {
diff --git a/spec/frontend/diffs/components/diff_content_spec.js b/spec/frontend/diffs/components/diff_content_spec.js
index 0a7dfc02c65..7d2afe105a5 100644
--- a/spec/frontend/diffs/components/diff_content_spec.js
+++ b/spec/frontend/diffs/components/diff_content_spec.js
@@ -1,5 +1,6 @@
import { GlLoadingIcon } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import DiffContentComponent from '~/diffs/components/diff_content.vue';
import DiffDiscussions from '~/diffs/components/diff_discussions.vue';
@@ -11,8 +12,7 @@ import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_prev
import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_diffable.vue';
import diffFileMockData from '../mock_data/diff_file';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('DiffContent', () => {
let wrapper;
@@ -88,7 +88,6 @@ describe('DiffContent', () => {
...defaultProps,
...props,
},
- localVue,
store: fakeStore,
provide: { glFeatures },
});
diff --git a/spec/frontend/diffs/components/diff_discussion_reply_spec.js b/spec/frontend/diffs/components/diff_discussion_reply_spec.js
index 9443a441ec2..f03c0357a0e 100644
--- a/spec/frontend/diffs/components/diff_discussion_reply_spec.js
+++ b/spec/frontend/diffs/components/diff_discussion_reply_spec.js
@@ -1,11 +1,11 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import DiffDiscussionReply from '~/diffs/components/diff_discussion_reply.vue';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
import NoteSignedOutWidget from '~/notes/components/note_signed_out_widget.vue';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('DiffDiscussionReply', () => {
let wrapper;
@@ -15,7 +15,6 @@ describe('DiffDiscussionReply', () => {
const createComponent = (props = {}, slots = {}) => {
wrapper = shallowMount(DiffDiscussionReply, {
store,
- localVue,
propsData: {
...props,
},
diff --git a/spec/frontend/diffs/components/diff_discussions_spec.js b/spec/frontend/diffs/components/diff_discussions_spec.js
index bd6f4cd2545..2da68adddf6 100644
--- a/spec/frontend/diffs/components/diff_discussions_spec.js
+++ b/spec/frontend/diffs/components/diff_discussions_spec.js
@@ -1,5 +1,5 @@
import { GlIcon } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import DiffDiscussions from '~/diffs/components/diff_discussions.vue';
import { createStore } from '~/mr_notes/stores';
import DiscussionNotes from '~/notes/components/discussion_notes.vue';
@@ -8,8 +8,6 @@ import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item
import '~/behaviors/markdown/render_gfm';
import discussionsMockData from '../mock_data/diff_discussions';
-const localVue = createLocalVue();
-
describe('DiffDiscussions', () => {
let store;
let wrapper;
@@ -17,13 +15,12 @@ describe('DiffDiscussions', () => {
const createComponent = (props) => {
store = createStore();
- wrapper = mount(localVue.extend(DiffDiscussions), {
+ wrapper = mount(DiffDiscussions, {
store,
propsData: {
discussions: getDiscussionsMockData(),
...props,
},
- localVue,
});
};
@@ -74,7 +71,7 @@ describe('DiffDiscussions', () => {
expect(diffNotesToggle.text().trim()).toBe('1');
expect(diffNotesToggle.classes()).toEqual(
- expect.arrayContaining(['btn-transparent', 'badge', 'badge-pill']),
+ expect.arrayContaining(['js-diff-notes-toggle', 'gl-translate-x-n50', 'design-note-pin']),
);
});
@@ -90,8 +87,8 @@ describe('DiffDiscussions', () => {
createComponent({ renderAvatarBadge: true });
const noteableDiscussion = wrapper.find(NoteableDiscussion);
- expect(noteableDiscussion.find('.badge-pill').exists()).toBe(true);
- expect(noteableDiscussion.find('.badge-pill').text().trim()).toBe('1');
+ expect(noteableDiscussion.find('.design-note-pin').exists()).toBe(true);
+ expect(noteableDiscussion.find('.design-note-pin').text().trim()).toBe('1');
});
});
});
diff --git a/spec/frontend/diffs/components/diff_expansion_cell_spec.js b/spec/frontend/diffs/components/diff_expansion_cell_spec.js
index f53f10d955d..cd472920bb9 100644
--- a/spec/frontend/diffs/components/diff_expansion_cell_spec.js
+++ b/spec/frontend/diffs/components/diff_expansion_cell_spec.js
@@ -91,7 +91,9 @@ describe('DiffExpansionCell', () => {
});
expect(findExpandUp(wrapper).exists()).toBe(true);
- expect(findExpandDown(wrapper).exists()).toBe(false);
+ expect(findExpandDown(wrapper).exists()).toBe(true);
+ expect(findExpandUp(wrapper).attributes('disabled')).not.toBeDefined();
+ expect(findExpandDown(wrapper).attributes('disabled')).toBeDefined();
expect(findExpandAll(wrapper)).not.toBe(null);
});
});
@@ -112,8 +114,10 @@ describe('DiffExpansionCell', () => {
isBottom: true,
});
- expect(findExpandUp(wrapper).exists()).toBe(false);
expect(findExpandDown(wrapper).exists()).toBe(true);
+ expect(findExpandUp(wrapper).exists()).toBe(true);
+ expect(findExpandDown(wrapper).attributes('disabled')).not.toBeDefined();
+ expect(findExpandUp(wrapper).attributes('disabled')).toBeDefined();
expect(findExpandAll(wrapper)).not.toBe(null);
});
});
diff --git a/spec/frontend/diffs/components/diff_file_header_spec.js b/spec/frontend/diffs/components/diff_file_header_spec.js
index 342b4bfcc50..f22bd312a6d 100644
--- a/spec/frontend/diffs/components/diff_file_header_spec.js
+++ b/spec/frontend/diffs/components/diff_file_header_spec.js
@@ -1,4 +1,5 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import { cloneDeep } from 'lodash';
import Vuex from 'vuex';
@@ -37,8 +38,7 @@ const diffFile = Object.freeze(
}),
);
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('DiffFileHeader component', () => {
let wrapper;
@@ -82,7 +82,7 @@ describe('DiffFileHeader component', () => {
const findExpandButton = () => wrapper.find({ ref: 'expandDiffToFullFileButton' });
const findFileActions = () => wrapper.find('.file-actions');
const findModeChangedLine = () => wrapper.find({ ref: 'fileMode' });
- const findLfsLabel = () => wrapper.find('.label-lfs');
+ const findLfsLabel = () => wrapper.find('[data-testid="label-lfs"]');
const findToggleDiscussionsButton = () => wrapper.find({ ref: 'toggleDiscussionsButton' });
const findExternalLink = () => wrapper.find({ ref: 'externalLink' });
const findReplacedFileButton = () => wrapper.find({ ref: 'replacedFileButton' });
@@ -103,7 +103,6 @@ describe('DiffFileHeader component', () => {
...props,
},
...options,
- localVue,
store,
});
};
@@ -126,30 +125,27 @@ describe('DiffFileHeader component', () => {
expect(findCollapseIcon().props('name')).toBe(icon);
});
- it('when header is clicked emits toggleFile', () => {
+ it('when header is clicked emits toggleFile', async () => {
createComponent();
findHeader().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().toggleFile).toBeDefined();
- });
+ await nextTick();
+ expect(wrapper.emitted().toggleFile).toBeDefined();
});
- it('when collapseIcon is clicked emits toggleFile', () => {
+ it('when collapseIcon is clicked emits toggleFile', async () => {
createComponent({ props: { collapsible: true } });
findCollapseIcon().vm.$emit('click', new Event('click'));
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().toggleFile).toBeDefined();
- });
+ await nextTick();
+ expect(wrapper.emitted().toggleFile).toBeDefined();
});
- it('when other element in header is clicked does not emits toggleFile', () => {
+ it('when other element in header is clicked does not emits toggleFile', async () => {
createComponent({ props: { collapsible: true } });
findTitleLink().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().toggleFile).not.toBeDefined();
- });
+ await nextTick();
+ expect(wrapper.emitted().toggleFile).not.toBeDefined();
});
describe('copy to clipboard', () => {
diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js
index dc0ed621a64..a0aa4c784bf 100644
--- a/spec/frontend/diffs/components/diff_file_spec.js
+++ b/spec/frontend/diffs/components/diff_file_spec.js
@@ -1,6 +1,6 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import DiffContentComponent from 'jh_else_ce/diffs/components/diff_content.vue';
@@ -70,9 +70,7 @@ function markFileToBeRendered(store, index = 0) {
}
function createComponent({ file, first = false, last = false, options = {}, props = {} }) {
- const localVue = createLocalVue();
-
- localVue.use(Vuex);
+ Vue.use(Vuex);
const store = new Vuex.Store({
...createNotesStore(),
@@ -85,7 +83,6 @@ function createComponent({ file, first = false, last = false, options = {}, prop
const wrapper = shallowMount(DiffFileComponent, {
store,
- localVue,
propsData: {
file,
canCurrentUserFork: false,
@@ -98,7 +95,6 @@ function createComponent({ file, first = false, last = false, options = {}, prop
});
return {
- localVue,
wrapper,
store,
};
@@ -164,7 +160,7 @@ describe('DiffFile', () => {
last,
}));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(eventHub.$emit).toHaveBeenCalledTimes(events.length);
events.forEach((event) => {
@@ -188,13 +184,13 @@ describe('DiffFile', () => {
makeFileAutomaticallyCollapsed(store);
- await wrapper.vm.$nextTick(); // Wait for store updates to flow into the component
+ await nextTick(); // Wait for store updates to flow into the component
toggleFile(wrapper);
- await wrapper.vm.$nextTick(); // Wait for the load to resolve
- await wrapper.vm.$nextTick(); // Wait for the idleCallback
- await wrapper.vm.$nextTick(); // Wait for nextTick inside postRender
+ await nextTick(); // Wait for the load to resolve
+ await nextTick(); // Wait for the idleCallback
+ await nextTick(); // Wait for nextTick inside postRender
expect(eventHub.$emit).toHaveBeenCalledTimes(2);
expect(eventHub.$emit).toHaveBeenCalledWith(EVT_PERF_MARK_FIRST_DIFF_FILE_SHOWN);
@@ -218,7 +214,7 @@ describe('DiffFile', () => {
markFileToBeRendered(store);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(DiffContentComponent).exists()).toBe(true);
});
@@ -268,7 +264,7 @@ describe('DiffFile', () => {
it('performs the normal file toggle when the file is collapsed', async () => {
makeFileAutomaticallyCollapsed(store);
- await wrapper.vm.$nextTick();
+ await nextTick();
eventHub.$emit(EVT_EXPAND_ALL_FILES);
@@ -278,7 +274,7 @@ describe('DiffFile', () => {
it('does nothing when the file is not collapsed', async () => {
eventHub.$emit(EVT_EXPAND_ALL_FILES);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.handleToggle).not.toHaveBeenCalled();
});
@@ -290,7 +286,7 @@ describe('DiffFile', () => {
});
it('should not have any content at all', async () => {
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findDiffContentArea(wrapper).element.children.length).toBe(0);
});
@@ -396,7 +392,7 @@ describe('DiffFile', () => {
readableText,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
toggleFile(wrapper);
};
@@ -444,7 +440,7 @@ describe('DiffFile', () => {
makeFileAutomaticallyCollapsed(store);
wrapper.vm.requestDiff();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findLoader(wrapper).exists()).toBe(true);
});
@@ -455,7 +451,7 @@ describe('DiffFile', () => {
({ wrapper, store } = createComponent({ file: getUnreadableFile() }));
makeFileAutomaticallyCollapsed(store);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findDiffContentArea(wrapper).html()).toContain(
'Files with large changes are collapsed by default.',
@@ -474,7 +470,7 @@ describe('DiffFile', () => {
markFileToBeRendered(store);
changeViewerType(store, mode);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.classes('has-body')).toBe(true);
expect(wrapper.find(DiffContentComponent).exists()).toBe(true);
@@ -500,7 +496,7 @@ describe('DiffFile', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
const button = wrapper.find('[data-testid="blob-button"]');
@@ -525,7 +521,7 @@ describe('DiffFile', () => {
({ wrapper, store } = createComponent({ file, props: { viewDiffsFileByFile: true } }));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findLoader(wrapper).exists()).toBe(true);
});
diff --git a/spec/frontend/diffs/components/diff_gutter_avatars_spec.js b/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
index 5884a9ebd3a..c18f0b721da 100644
--- a/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
+++ b/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue';
import discussionsMockData from '../mock_data/diff_discussions';
@@ -35,12 +36,11 @@ describe('DiffGutterAvatars', () => {
expect(findCollapseButton().exists()).toBe(true);
});
- it('should emit toggleDiscussions event on button click', () => {
+ it('should emit toggleDiscussions event on button click', async () => {
findCollapseButton().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().toggleLineDiscussions).toBeTruthy();
- });
+ await nextTick();
+ expect(wrapper.emitted().toggleLineDiscussions).toBeTruthy();
});
});
@@ -65,20 +65,18 @@ describe('DiffGutterAvatars', () => {
expect(findMoreCount().text()).toBe('+2');
});
- it('should emit toggleDiscussions event on avatars click', () => {
+ it('should emit toggleDiscussions event on avatars click', async () => {
findUserAvatars().at(0).trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().toggleLineDiscussions).toBeTruthy();
- });
+ await nextTick();
+ expect(wrapper.emitted().toggleLineDiscussions).toBeTruthy();
});
- it('should emit toggleDiscussions event on more count text click', () => {
+ it('should emit toggleDiscussions event on more count text click', async () => {
findMoreCount().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().toggleLineDiscussions).toBeTruthy();
- });
+ await nextTick();
+ expect(wrapper.emitted().toggleLineDiscussions).toBeTruthy();
});
});
diff --git a/spec/frontend/diffs/components/diff_view_spec.js b/spec/frontend/diffs/components/diff_view_spec.js
index 3af66526050..9b8f0421b7c 100644
--- a/spec/frontend/diffs/components/diff_view_spec.js
+++ b/spec/frontend/diffs/components/diff_view_spec.js
@@ -50,8 +50,11 @@ describe('DiffView', () => {
};
it('renders a match line', () => {
- const wrapper = createWrapper({ diffLines: [{ isMatchLineLeft: true }] });
+ const wrapper = createWrapper({
+ diffLines: [{ isMatchLineLeft: true, left: { rich_text: 'matched text', lineDraft: {} } }],
+ });
expect(wrapper.find(DiffExpansionCell).exists()).toBe(true);
+ expect(wrapper.text()).toContain('matched text');
});
it.each`
diff --git a/spec/frontend/diffs/components/image_diff_overlay_spec.js b/spec/frontend/diffs/components/image_diff_overlay_spec.js
index 8c1a8041f6c..70191620eb6 100644
--- a/spec/frontend/diffs/components/image_diff_overlay_spec.js
+++ b/spec/frontend/diffs/components/image_diff_overlay_spec.js
@@ -1,5 +1,5 @@
import { GlIcon } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue';
import { createStore } from '~/mr_notes/stores';
import { imageDiffDiscussions } from '../mock_data/diff_discussions';
@@ -19,7 +19,7 @@ describe('Diffs image diff overlay component', () => {
extendStore(store);
dispatch = jest.spyOn(store, 'dispatch').mockImplementation();
- wrapper = shallowMount(ImageDiffOverlay, {
+ wrapper = mount(ImageDiffOverlay, {
store,
parentComponent: {
data() {
diff --git a/spec/frontend/diffs/components/merge_conflict_warning_spec.js b/spec/frontend/diffs/components/merge_conflict_warning_spec.js
index 2f303f25f66..4e47249f5b4 100644
--- a/spec/frontend/diffs/components/merge_conflict_warning_spec.js
+++ b/spec/frontend/diffs/components/merge_conflict_warning_spec.js
@@ -1,13 +1,11 @@
import { shallowMount, mount } from '@vue/test-utils';
import MergeConflictWarning from '~/diffs/components/merge_conflict_warning.vue';
-import { CENTERED_LIMITED_CONTAINER_CLASSES } from '~/diffs/constants';
const propsData = {
limited: true,
mergeable: true,
resolutionPath: 'a-path',
};
-const limitedClasses = CENTERED_LIMITED_CONTAINER_CLASSES.split(' ');
function findResolveButton(wrapper) {
return wrapper.find('.gl-alert-actions a.gl-button:first-child');
@@ -32,19 +30,6 @@ describe('MergeConflictWarning', () => {
});
it.each`
- limited | containerClasses
- ${true} | ${limitedClasses}
- ${false} | ${[]}
- `(
- 'has the correct container classes when limited is $limited',
- ({ limited, containerClasses }) => {
- createComponent({ limited });
-
- expect(wrapper.classes()).toEqual(containerClasses);
- },
- );
-
- it.each`
present | resolutionPath
${false} | ${''}
${true} | ${'some-path'}
diff --git a/spec/frontend/diffs/components/no_changes_spec.js b/spec/frontend/diffs/components/no_changes_spec.js
index 164c58dc8e4..6903b844e5e 100644
--- a/spec/frontend/diffs/components/no_changes_spec.js
+++ b/spec/frontend/diffs/components/no_changes_spec.js
@@ -1,12 +1,12 @@
import { GlButton } from '@gitlab/ui';
-import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
+import { shallowMount, mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import NoChanges from '~/diffs/components/no_changes.vue';
import { createStore } from '~/mr_notes/stores';
import diffsMockData from '../mock_data/merge_request_diffs';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
const TEST_TARGET_BRANCH = 'foo';
const TEST_SOURCE_BRANCH = 'dev/update';
@@ -17,7 +17,6 @@ describe('Diff no changes empty state', () => {
function createComponent(mountFn = shallowMount) {
wrapper = mountFn(NoChanges, {
- localVue,
store,
propsData: {
changesEmptyStateIllustration: '',
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/diffs/components/tree_list_spec.js b/spec/frontend/diffs/components/tree_list_spec.js
index 31044b0818c..963805f4792 100644
--- a/spec/frontend/diffs/components/tree_list_spec.js
+++ b/spec/frontend/diffs/components/tree_list_spec.js
@@ -1,4 +1,5 @@
-import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
+import { shallowMount, mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import TreeList from '~/diffs/components/tree_list.vue';
import createStore from '~/diffs/store/modules';
@@ -8,13 +9,11 @@ describe('Diffs tree list component', () => {
let wrapper;
let store;
const getFileRows = () => wrapper.findAll('.file-row');
- const localVue = createLocalVue();
- localVue.use(Vuex);
+ Vue.use(Vuex);
const createComponent = (mountFn = mount) => {
wrapper = mountFn(TreeList, {
store,
- localVue,
propsData: { hideFileStats: false },
});
};
@@ -92,12 +91,11 @@ describe('Diffs tree list component', () => {
expect(getFileRows().at(1).html()).toContain('app');
});
- it('hides file stats', () => {
+ it('hides file stats', async () => {
wrapper.setProps({ hideFileStats: true });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find('.file-row-stats').exists()).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.find('.file-row-stats').exists()).toBe(false);
});
it('calls toggleTreeOpen when clicking folder', () => {
@@ -118,20 +116,18 @@ describe('Diffs tree list component', () => {
});
});
- it('renders as file list when renderTreeList is false', () => {
+ it('renders as file list when renderTreeList is false', async () => {
wrapper.vm.$store.state.diffs.renderTreeList = false;
- return wrapper.vm.$nextTick().then(() => {
- expect(getFileRows()).toHaveLength(1);
- });
+ await nextTick();
+ expect(getFileRows()).toHaveLength(1);
});
- it('renders file paths when renderTreeList is false', () => {
+ it('renders file paths when renderTreeList is false', async () => {
wrapper.vm.$store.state.diffs.renderTreeList = false;
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find('.file-row').html()).toContain('index.js');
- });
+ await nextTick();
+ expect(wrapper.find('.file-row').html()).toContain('index.js');
});
});
@@ -143,14 +139,13 @@ describe('Diffs tree list component', () => {
store.state.diffs.viewedDiffFileIds = viewedDiffFileIds;
});
- it('passes the viewedDiffFileIds to the FileTree', () => {
+ it('passes the viewedDiffFileIds to the FileTree', async () => {
createComponent(shallowMount);
- return wrapper.vm.$nextTick().then(() => {
- // Have to use $attrs['viewed-files'] because we are passing down an object
- // and attributes('') stringifies values (e.g. [object])...
- expect(wrapper.find(FileTree).vm.$attrs['viewed-files']).toBe(viewedDiffFileIds);
- });
+ await nextTick();
+ // Have to use $attrs['viewed-files'] because we are passing down an object
+ // and attributes('') stringifies values (e.g. [object])...
+ expect(wrapper.find(FileTree).vm.$attrs['viewed-files']).toBe(viewedDiffFileIds);
});
});
});
diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js
index b5003a54917..d6a2aa104cd 100644
--- a/spec/frontend/diffs/store/actions_spec.js
+++ b/spec/frontend/diffs/store/actions_spec.js
@@ -202,7 +202,7 @@ describe('DiffsStoreActions', () => {
testAction(
fetchDiffFilesBatch,
{},
- { endpointBatch, diffViewType: 'inline' },
+ { endpointBatch, diffViewType: 'inline', diffFiles: [] },
[
{ type: types.SET_BATCH_LOADING_STATE, payload: 'loading' },
{ type: types.SET_RETRIEVING_BATCHES, payload: true },
@@ -544,10 +544,8 @@ describe('DiffsStoreActions', () => {
[{ type: types.SET_DIFF_VIEW_TYPE, payload: INLINE_DIFF_VIEW_TYPE }],
[],
() => {
- setImmediate(() => {
- expect(Cookies.get('diff_view')).toEqual(INLINE_DIFF_VIEW_TYPE);
- done();
- });
+ expect(Cookies.get('diff_view')).toEqual(INLINE_DIFF_VIEW_TYPE);
+ done();
},
);
});
@@ -562,10 +560,8 @@ describe('DiffsStoreActions', () => {
[{ type: types.SET_DIFF_VIEW_TYPE, payload: PARALLEL_DIFF_VIEW_TYPE }],
[],
() => {
- setImmediate(() => {
- expect(Cookies.get(DIFF_VIEW_COOKIE_NAME)).toEqual(PARALLEL_DIFF_VIEW_TYPE);
- done();
- });
+ expect(Cookies.get(DIFF_VIEW_COOKIE_NAME)).toEqual(PARALLEL_DIFF_VIEW_TYPE);
+ done();
},
);
});
diff --git a/spec/frontend/editor/source_editor_yaml_ext_spec.js b/spec/frontend/editor/source_editor_yaml_ext_spec.js
index a861d9c7a45..b603b0e3a98 100644
--- a/spec/frontend/editor/source_editor_yaml_ext_spec.js
+++ b/spec/frontend/editor/source_editor_yaml_ext_spec.js
@@ -353,58 +353,78 @@ foo:
});
describe('highlight', () => {
- const highlightPathOnSetup = 'abc';
const value = `foo:
bar:
- baz
- boo
- abc: def
+abc: def
`;
let instance;
let highlightLinesSpy;
let removeHighlightsSpy;
- beforeEach(() => {
- instance = getEditorInstanceWithExtension({ highlightPath: highlightPathOnSetup }, { value });
- highlightLinesSpy = jest.fn();
- removeHighlightsSpy = jest.fn();
- spyOnApi(baseExtension, {
- highlightLines: highlightLinesSpy,
- removeHighlights: removeHighlightsSpy,
- });
- });
-
afterEach(() => {
jest.clearAllMocks();
});
- it('saves the highlighted path in highlightPath', () => {
- const path = 'foo.bar';
- instance.highlight(path);
- expect(yamlExtension.obj.highlightPath).toEqual(path);
- });
-
- it('calls highlightLines with a number of lines', () => {
- const path = 'foo.bar';
- instance.highlight(path);
- expect(highlightLinesSpy).toHaveBeenCalledWith(instance, [2, 4]);
- });
-
- it('calls removeHighlights if path is null', () => {
- instance.highlight(null);
- expect(removeHighlightsSpy).toHaveBeenCalledWith(instance);
- expect(highlightLinesSpy).not.toHaveBeenCalled();
- expect(yamlExtension.obj.highlightPath).toBeNull();
- });
-
- it('throws an error if path is invalid and does not change the highlighted path', () => {
- expect(() => instance.highlight('invalidPath[0]')).toThrow(
- 'The node invalidPath[0] could not be found inside the document.',
- );
- expect(yamlExtension.obj.highlightPath).toEqual(highlightPathOnSetup);
- expect(highlightLinesSpy).not.toHaveBeenCalled();
- expect(removeHighlightsSpy).not.toHaveBeenCalled();
- });
+ it.each`
+ highlightPathOnSetup | path | keepOnNotFound | expectHighlightLinesToBeCalled | withLines | expectRemoveHighlightsToBeCalled | storedHighlightPath
+ ${null} | ${undefined} | ${false} | ${false} | ${undefined} | ${true} | ${null}
+ ${'abc'} | ${'abc'} | ${undefined} | ${false} | ${undefined} | ${false} | ${'abc'}
+ ${null} | ${null} | ${false} | ${false} | ${undefined} | ${false} | ${null}
+ ${null} | ${''} | ${false} | ${false} | ${undefined} | ${true} | ${null}
+ ${null} | ${''} | ${true} | ${false} | ${undefined} | ${true} | ${null}
+ ${'abc'} | ${''} | ${false} | ${false} | ${undefined} | ${true} | ${null}
+ ${'abc'} | ${'foo.bar'} | ${false} | ${true} | ${[2, 4]} | ${false} | ${'foo.bar'}
+ ${'abc'} | ${['foo', 'bar']} | ${false} | ${true} | ${[2, 4]} | ${false} | ${['foo', 'bar']}
+ ${'abc'} | ${'invalid'} | ${true} | ${false} | ${undefined} | ${false} | ${'abc'}
+ ${'abc'} | ${'invalid'} | ${false} | ${false} | ${undefined} | ${true} | ${null}
+ ${'abc'} | ${'invalid'} | ${undefined} | ${false} | ${undefined} | ${true} | ${null}
+ ${'abc'} | ${['invalid']} | ${undefined} | ${false} | ${undefined} | ${true} | ${null}
+ ${'abc'} | ${['invalid']} | ${true} | ${false} | ${undefined} | ${false} | ${'abc'}
+ ${'abc'} | ${[]} | ${true} | ${false} | ${undefined} | ${true} | ${null}
+ ${'abc'} | ${[]} | ${false} | ${false} | ${undefined} | ${true} | ${null}
+ `(
+ 'returns correct result for highlightPathOnSetup=$highlightPathOnSetup, path=$path' +
+ ' and keepOnNotFound=$keepOnNotFound',
+ ({
+ highlightPathOnSetup,
+ path,
+ keepOnNotFound,
+ expectHighlightLinesToBeCalled,
+ withLines,
+ expectRemoveHighlightsToBeCalled,
+ storedHighlightPath,
+ }) => {
+ instance = getEditorInstanceWithExtension(
+ { highlightPath: highlightPathOnSetup },
+ { value },
+ );
+
+ highlightLinesSpy = jest.fn();
+ removeHighlightsSpy = jest.fn();
+ spyOnApi(baseExtension, {
+ highlightLines: highlightLinesSpy,
+ removeHighlights: removeHighlightsSpy,
+ });
+
+ instance.highlight(path, keepOnNotFound);
+
+ if (expectHighlightLinesToBeCalled) {
+ expect(highlightLinesSpy).toHaveBeenCalledWith(instance, withLines);
+ } else {
+ expect(highlightLinesSpy).not.toHaveBeenCalled();
+ }
+
+ if (expectRemoveHighlightsToBeCalled) {
+ expect(removeHighlightsSpy).toHaveBeenCalled();
+ } else {
+ expect(removeHighlightsSpy).not.toHaveBeenCalled();
+ }
+
+ expect(yamlExtension.obj.highlightPath).toEqual(storedHighlightPath);
+ },
+ );
});
describe('locate', () => {
@@ -446,10 +466,10 @@ foo:
expect(instance.locate(path)).toEqual(expected);
});
- it('throws an error if a path cannot be found inside the yaml', () => {
+ it('returns [null, null] if a path cannot be found inside the yaml', () => {
const path = 'baz[8]';
const instance = getEditorInstanceWithExtension(options);
- expect(() => instance.locate(path)).toThrow();
+ expect(instance.locate(path)).toEqual([null, null]);
});
it('returns the expected line numbers for a path to an array entry inside the yaml', () => {
diff --git a/spec/frontend/emoji/awards_app/store/actions_spec.js b/spec/frontend/emoji/awards_app/store/actions_spec.js
index 02b643244d2..0761256ed23 100644
--- a/spec/frontend/emoji/awards_app/store/actions_spec.js
+++ b/spec/frontend/emoji/awards_app/store/actions_spec.js
@@ -87,6 +87,26 @@ describe('Awards app actions', () => {
describe('toggleAward', () => {
let mock;
+ const optimisticAwardId = Number.MAX_SAFE_INTEGER - 1;
+ const makeOptimisticAddMutation = (
+ id = optimisticAwardId,
+ name = null,
+ userId = window.gon.current_user_id,
+ ) => ({
+ type: 'ADD_NEW_AWARD',
+ payload: {
+ id,
+ name,
+ user: {
+ id: userId,
+ },
+ },
+ });
+ const makeOptimisticRemoveMutation = (id = optimisticAwardId) => ({
+ type: 'REMOVE_AWARD',
+ payload: id,
+ });
+
beforeEach(() => {
mock = new MockAdapter(axios);
});
@@ -110,8 +130,10 @@ describe('Awards app actions', () => {
mock.onPost(`${relativeRootUrl || ''}/awards`).reply(200, { id: 1 });
});
- it('commits ADD_NEW_AWARD', async () => {
+ it('adds an optimistic award, removes it, and then commits ADD_NEW_AWARD', async () => {
testAction(actions.toggleAward, null, { path: '/awards', awards: [] }, [
+ makeOptimisticAddMutation(),
+ makeOptimisticRemoveMutation(),
{ type: 'ADD_NEW_AWARD', payload: { id: 1 } },
]);
});
@@ -127,7 +149,7 @@ describe('Awards app actions', () => {
actions.toggleAward,
null,
{ path: '/awards', awards: [] },
- [],
+ [makeOptimisticAddMutation(), makeOptimisticRemoveMutation()],
[],
() => {
expect(Sentry.captureException).toHaveBeenCalled();
@@ -137,7 +159,7 @@ describe('Awards app actions', () => {
});
});
- describe('removing a award', () => {
+ describe('removing an award', () => {
const mockData = { id: 1, name: 'thumbsup', user: { id: 1 } };
describe('success', () => {
@@ -160,6 +182,9 @@ describe('Awards app actions', () => {
});
describe('error', () => {
+ const currentUserId = 1;
+ const name = 'thumbsup';
+
beforeEach(() => {
mock.onDelete(`${relativeRootUrl || ''}/awards/1`).reply(500);
});
@@ -167,13 +192,13 @@ describe('Awards app actions', () => {
it('calls Sentry.captureException', async () => {
await testAction(
actions.toggleAward,
- 'thumbsup',
+ name,
{
path: '/awards',
- currentUserId: 1,
+ currentUserId,
awards: [mockData],
},
- [],
+ [makeOptimisticRemoveMutation(1), makeOptimisticAddMutation(1, name, currentUserId)],
[],
() => {
expect(Sentry.captureException).toHaveBeenCalled();
diff --git a/spec/frontend/emoji/components/utils_spec.js b/spec/frontend/emoji/components/utils_spec.js
index 36521eb1051..03eeb6b6bf7 100644
--- a/spec/frontend/emoji/components/utils_spec.js
+++ b/spec/frontend/emoji/components/utils_spec.js
@@ -31,6 +31,7 @@ describe('addToFrequentlyUsed', () => {
expect(Cookies.set).toHaveBeenCalledWith('frequently_used_emojis', 'thumbsup', {
expires: 365,
+ secure: false,
});
});
@@ -41,6 +42,7 @@ describe('addToFrequentlyUsed', () => {
expect(Cookies.set).toHaveBeenCalledWith('frequently_used_emojis', 'thumbsdown,thumbsup', {
expires: 365,
+ secure: false,
});
});
@@ -51,6 +53,7 @@ describe('addToFrequentlyUsed', () => {
expect(Cookies.set).toHaveBeenCalledWith('frequently_used_emojis', 'thumbsup', {
expires: 365,
+ secure: false,
});
});
});
diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js
index cf47a1cd7bb..d7acf75fc95 100644
--- a/spec/frontend/environment.js
+++ b/spec/frontend/environment.js
@@ -29,6 +29,9 @@ class CustomEnvironment extends JSDOMEnvironment {
},
warn(...args) {
+ if (args[0].includes('The updateQuery callback for fetchMore is deprecated')) {
+ return;
+ }
throw new ErrorWithStack(
`Unexpected call of console.warn() with:\n\n${args.join(', ')}`,
this.warn,
diff --git a/spec/frontend/environments/canary_ingress_spec.js b/spec/frontend/environments/canary_ingress_spec.js
index 6c7a786e652..d58f9f9b8a2 100644
--- a/spec/frontend/environments/canary_ingress_spec.js
+++ b/spec/frontend/environments/canary_ingress_spec.js
@@ -3,6 +3,7 @@ import { mount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import CanaryIngress from '~/environments/components/canary_ingress.vue';
import { CANARY_UPDATE_MODAL } from '~/environments/constants';
+import { rolloutStatus } from './graphql/mock_data';
describe('/environments/components/canary_ingress.vue', () => {
let wrapper;
@@ -13,16 +14,18 @@ describe('/environments/components/canary_ingress.vue', () => {
.at(x / 5)
.vm.$emit('click');
- const createComponent = () => {
+ const createComponent = (props = {}, options = {}) => {
wrapper = mount(CanaryIngress, {
propsData: {
canaryIngress: {
canary_weight: 60,
},
+ ...props,
},
directives: {
GlModal: createMockDirective(),
},
+ ...options,
});
};
@@ -94,9 +97,25 @@ describe('/environments/components/canary_ingress.vue', () => {
});
it('is set to open the change modal', () => {
- const options = canaryWeightDropdown.findAll(GlDropdownItem);
- expect(options).toHaveLength(21);
- options.wrappers.forEach((w, i) => expect(w.text()).toBe((i * 5).toString()));
+ canaryWeightDropdown
+ .findAll(GlDropdownItem)
+ .wrappers.forEach((w) =>
+ expect(getBinding(w.element, 'gl-modal')).toMatchObject({ value: CANARY_UPDATE_MODAL }),
+ );
+ });
+ });
+
+ describe('graphql', () => {
+ beforeEach(() => {
+ createComponent({
+ graphql: true,
+ canaryIngress: rolloutStatus.canaryIngress,
+ });
+ });
+
+ it('shows the correct weight', () => {
+ const canaryWeightDropdown = wrapper.find('[data-testid="canary-weight"]');
+ expect(canaryWeightDropdown.props('text')).toBe('50');
});
});
});
diff --git a/spec/frontend/environments/canary_update_modal_spec.js b/spec/frontend/environments/canary_update_modal_spec.js
index c7129ee1320..22d13558a84 100644
--- a/spec/frontend/environments/canary_update_modal_spec.js
+++ b/spec/frontend/environments/canary_update_modal_spec.js
@@ -1,5 +1,6 @@
import { GlAlert, GlModal } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import CanaryUpdateModal from '~/environments/components/canary_update_modal.vue';
import updateCanaryIngress from '~/environments/graphql/mutations/update_canary_ingress.mutation.graphql';
@@ -86,7 +87,7 @@ describe('/environments/components/canary_update_modal.vue', () => {
mutate.mockResolvedValue({ data: { environmentsCanaryIngressUpdate: { errors: [] } } });
modal.vm.$emit('primary');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findAlert().exists()).toBe(false);
});
@@ -95,7 +96,7 @@ describe('/environments/components/canary_update_modal.vue', () => {
mutate.mockResolvedValue({ data: { environmentsCanaryIngressUpdate: { errors: ['error'] } } });
modal.vm.$emit('primary');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findAlert().text()).toBe('error');
});
@@ -105,7 +106,7 @@ describe('/environments/components/canary_update_modal.vue', () => {
modal.vm.$emit('primary');
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findAlert().text()).toBe('Something went wrong. Please try again later');
});
@@ -114,12 +115,12 @@ describe('/environments/components/canary_update_modal.vue', () => {
mutate.mockResolvedValue({ data: { environmentsCanaryIngressUpdate: { errors: ['error'] } } });
modal.vm.$emit('primary');
- await wrapper.vm.$nextTick();
+ await nextTick();
const alert = findAlert();
alert.vm.$emit('dismiss');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(alert.exists()).toBe(false);
});
diff --git a/spec/frontend/environments/commit_spec.js b/spec/frontend/environments/commit_spec.js
new file mode 100644
index 00000000000..32eb4b77528
--- /dev/null
+++ b/spec/frontend/environments/commit_spec.js
@@ -0,0 +1,71 @@
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import Commit from '~/environments/components/commit.vue';
+import { resolvedEnvironment } from './graphql/mock_data';
+
+describe('~/environments/components/commit.vue', () => {
+ let commit;
+ let wrapper;
+
+ beforeEach(() => {
+ commit = resolvedEnvironment.lastDeployment.commit;
+ });
+
+ const createWrapper = ({ propsData = {} } = {}) =>
+ mountExtended(Commit, {
+ propsData: {
+ commit,
+ ...propsData,
+ },
+ });
+
+ afterEach(() => {
+ wrapper?.destroy();
+ });
+
+ describe('with gitlab user', () => {
+ beforeEach(() => {
+ wrapper = createWrapper();
+ });
+
+ it('links to the user profile', () => {
+ const link = wrapper.findByRole('link', { name: commit.author.name });
+ expect(link.attributes('href')).toBe(commit.author.path);
+ });
+
+ it('displays the user avatar', () => {
+ const avatar = wrapper.findByRole('img', { name: 'avatar' });
+ expect(avatar.attributes('src')).toBe(commit.author.avatarUrl);
+ });
+
+ it('links the commit message to the commit', () => {
+ const message = wrapper.findByRole('link', { name: commit.message });
+
+ expect(message.attributes('href')).toBe(commit.commitPath);
+ });
+ });
+ describe('without gitlab user', () => {
+ beforeEach(() => {
+ commit = {
+ ...commit,
+ author: null,
+ };
+ wrapper = createWrapper();
+ });
+
+ it('links to the user profile', () => {
+ const link = wrapper.findByRole('link', { name: commit.authorName });
+ expect(link.attributes('href')).toBe(`mailto:${commit.authorEmail}`);
+ });
+
+ it('displays the user avatar', () => {
+ const avatar = wrapper.findByRole('img', { name: 'avatar' });
+ expect(avatar.attributes('src')).toBe(commit.authorGravatarUrl);
+ });
+
+ it('displays the commit message', () => {
+ const message = wrapper.findByRole('link', { name: commit.message });
+
+ expect(message.attributes('href')).toBe(commit.commitPath);
+ });
+ });
+});
diff --git a/spec/frontend/environments/deploy_board_component_spec.js b/spec/frontend/environments/deploy_board_component_spec.js
index 24e94867afd..f0fb4d1027c 100644
--- a/spec/frontend/environments/deploy_board_component_spec.js
+++ b/spec/frontend/environments/deploy_board_component_spec.js
@@ -1,9 +1,10 @@
import { GlTooltip, GlIcon, GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import CanaryIngress from '~/environments/components/canary_ingress.vue';
import DeployBoard from '~/environments/components/deploy_board.vue';
import { deployBoardMockData, environment } from './mock_data';
+import { rolloutStatus } from './graphql/mock_data';
const logsPath = `gitlab-org/gitlab-test/-/logs?environment_name=${environment.name}`;
@@ -24,11 +25,11 @@ describe('Deploy Board', () => {
describe('with valid data', () => {
beforeEach((done) => {
wrapper = createComponent();
- wrapper.vm.$nextTick(done);
+ nextTick(done);
});
it('should render percentage with completion value provided', () => {
- expect(wrapper.vm.$refs.percentage.innerText).toEqual(`${deployBoardMockData.completion}%`);
+ expect(wrapper.find({ ref: 'percentage' }).text()).toBe(`${deployBoardMockData.completion}%`);
});
it('should render total instance count', () => {
@@ -57,20 +58,74 @@ describe('Deploy Board', () => {
it('sets up a tooltip for the legend', () => {
const iconSpan = wrapper.find('[data-testid="legend-tooltip-target"]');
- const tooltip = wrapper.find(GlTooltip);
- const icon = iconSpan.find(GlIcon);
+ const tooltip = wrapper.findComponent(GlTooltip);
+ const icon = iconSpan.findComponent(GlIcon);
expect(tooltip.props('target')()).toBe(iconSpan.element);
expect(icon.props('name')).toBe('question');
});
it('renders the canary weight selector', () => {
- const canary = wrapper.find(CanaryIngress);
+ const canary = wrapper.findComponent(CanaryIngress);
expect(canary.exists()).toBe(true);
expect(canary.props('canaryIngress')).toEqual({ canary_weight: 50 });
});
});
+ describe('with new valid data', () => {
+ beforeEach(async () => {
+ wrapper = createComponent({
+ graphql: true,
+ deployBoardData: rolloutStatus,
+ });
+ await nextTick();
+ });
+
+ it('should render percentage with completion value provided', () => {
+ expect(wrapper.find({ ref: 'percentage' }).text()).toBe(`${rolloutStatus.completion}%`);
+ });
+
+ it('should render total instance count', () => {
+ const renderedTotal = wrapper.find('.deploy-board-instances-text');
+ const actualTotal = rolloutStatus.instances.length;
+ const output = `${actualTotal > 1 ? 'Instances' : 'Instance'} (${actualTotal})`;
+
+ expect(renderedTotal.text()).toEqual(output);
+ });
+
+ it('should render all instances', () => {
+ const instances = wrapper.findAll('.deploy-board-instances-container a');
+
+ expect(instances).toHaveLength(rolloutStatus.instances.length);
+ expect(
+ instances.at(1).classes(`deployment-instance-${rolloutStatus.instances[2].status}`),
+ ).toBe(true);
+ });
+
+ it('should render an abort and a rollback button with the provided url', () => {
+ const buttons = wrapper.findAll('.deploy-board-actions a');
+
+ expect(buttons.at(0).attributes('href')).toEqual(rolloutStatus.rollbackUrl);
+ expect(buttons.at(1).attributes('href')).toEqual(rolloutStatus.abortUrl);
+ });
+
+ it('sets up a tooltip for the legend', () => {
+ const iconSpan = wrapper.find('[data-testid="legend-tooltip-target"]');
+ const tooltip = wrapper.findComponent(GlTooltip);
+ const icon = iconSpan.findComponent(GlIcon);
+
+ expect(tooltip.props('target')()).toBe(iconSpan.element);
+ expect(icon.props('name')).toBe('question');
+ });
+
+ it('renders the canary weight selector', () => {
+ const canary = wrapper.findComponent(CanaryIngress);
+ expect(canary.exists()).toBe(true);
+ expect(canary.props('canaryIngress')).toEqual({ canaryWeight: 50 });
+ expect(canary.props('graphql')).toBe(true);
+ });
+ });
+
describe('with empty state', () => {
beforeEach((done) => {
wrapper = createComponent({
@@ -79,7 +134,7 @@ describe('Deploy Board', () => {
isEmpty: true,
logsPath,
});
- wrapper.vm.$nextTick(done);
+ nextTick(done);
});
it('should render the empty state', () => {
@@ -98,11 +153,11 @@ describe('Deploy Board', () => {
isEmpty: false,
logsPath,
});
- wrapper.vm.$nextTick(done);
+ nextTick(done);
});
it('should render loading spinner', () => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
});
@@ -116,7 +171,7 @@ describe('Deploy Board', () => {
deployBoardData: deployBoardMockData,
});
({ statuses } = wrapper.vm);
- wrapper.vm.$nextTick(done);
+ nextTick(done);
});
it('with all the possible statuses', () => {
diff --git a/spec/frontend/environments/deploy_board_wrapper_spec.js b/spec/frontend/environments/deploy_board_wrapper_spec.js
new file mode 100644
index 00000000000..c8e6df4d324
--- /dev/null
+++ b/spec/frontend/environments/deploy_board_wrapper_spec.js
@@ -0,0 +1,124 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlCollapse, GlIcon } from '@gitlab/ui';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { stubTransition } from 'helpers/stub_transition';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { __, s__ } from '~/locale';
+import DeployBoardWrapper from '~/environments/components/deploy_board_wrapper.vue';
+import DeployBoard from '~/environments/components/deploy_board.vue';
+import setEnvironmentToChangeCanaryMutation from '~/environments/graphql/mutations/set_environment_to_change_canary.mutation.graphql';
+import { resolvedEnvironment, rolloutStatus } from './graphql/mock_data';
+
+Vue.use(VueApollo);
+
+describe('~/environments/components/deploy_board_wrapper.vue', () => {
+ let wrapper;
+ let mockApollo;
+
+ const findDeployBoard = () => wrapper.findComponent(DeployBoard);
+
+ const createWrapper = ({ propsData = {} } = {}) => {
+ mockApollo = createMockApollo();
+ return mountExtended(DeployBoardWrapper, {
+ propsData: { environment: resolvedEnvironment, rolloutStatus, ...propsData },
+ provide: { helpPagePath: '/help' },
+ stubs: { transition: stubTransition() },
+ apolloProvider: mockApollo,
+ });
+ };
+
+ const expandCollapsedSection = async () => {
+ const button = wrapper.findByRole('button', { name: __('Expand') });
+ await button.trigger('click');
+
+ return button;
+ };
+
+ afterEach(() => {
+ wrapper?.destroy();
+ });
+
+ it('is labeled Kubernetes Pods', () => {
+ wrapper = createWrapper();
+
+ expect(wrapper.findByText(s__('DeployBoard|Kubernetes Pods')).exists()).toBe(true);
+ });
+
+ describe('collapse', () => {
+ let icon;
+ let collapse;
+
+ beforeEach(() => {
+ wrapper = createWrapper();
+ collapse = wrapper.findComponent(GlCollapse);
+ icon = wrapper.findComponent(GlIcon);
+ });
+
+ it('is collapsed by default', () => {
+ expect(collapse.attributes('visible')).toBeUndefined();
+ expect(icon.props('name')).toBe('angle-right');
+ });
+
+ it('opens on click', async () => {
+ const button = await expandCollapsedSection();
+
+ expect(button.attributes('aria-label')).toBe(__('Collapse'));
+ expect(collapse.attributes('visible')).toBe('visible');
+ expect(icon.props('name')).toBe('angle-down');
+
+ const deployBoard = findDeployBoard();
+ expect(deployBoard.exists()).toBe(true);
+ });
+ });
+
+ describe('deploy board', () => {
+ it('passes the rollout status on and sets graphql to true', async () => {
+ wrapper = createWrapper();
+ await expandCollapsedSection();
+
+ const deployBoard = findDeployBoard();
+ expect(deployBoard.props('deployBoardData')).toEqual(rolloutStatus);
+ expect(deployBoard.props('graphql')).toBe(true);
+ });
+
+ it('sets the update to the canary via graphql', () => {
+ wrapper = createWrapper();
+ jest.spyOn(mockApollo.defaultClient, 'mutate');
+ const deployBoard = findDeployBoard();
+ deployBoard.vm.$emit('changeCanaryWeight', 15);
+ expect(mockApollo.defaultClient.mutate).toHaveBeenCalledWith({
+ mutation: setEnvironmentToChangeCanaryMutation,
+ variables: { environment: resolvedEnvironment, weight: 15 },
+ });
+ });
+
+ describe('is loading', () => {
+ it('should set the loading prop', async () => {
+ wrapper = createWrapper({
+ propsData: { rolloutStatus: { ...rolloutStatus, status: 'loading' } },
+ });
+
+ await expandCollapsedSection();
+
+ const deployBoard = findDeployBoard();
+
+ expect(deployBoard.props('isLoading')).toBe(true);
+ });
+ });
+
+ describe('is empty', () => {
+ it('should set the empty prop', async () => {
+ wrapper = createWrapper({
+ propsData: { rolloutStatus: { ...rolloutStatus, status: 'not_found' } },
+ });
+
+ await expandCollapsedSection();
+
+ const deployBoard = findDeployBoard();
+
+ expect(deployBoard.props('isEmpty')).toBe(true);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/environments/deployment_spec.js b/spec/frontend/environments/deployment_spec.js
index 37209bdc86c..6cc363e000b 100644
--- a/spec/frontend/environments/deployment_spec.js
+++ b/spec/frontend/environments/deployment_spec.js
@@ -1,17 +1,32 @@
+import { GlCollapse } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { useFakeDate } from 'helpers/fake_date';
+import { stubTransition } from 'helpers/stub_transition';
+import { formatDate } from '~/lib/utils/datetime_utility';
+import { __, s__ } from '~/locale';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Deployment from '~/environments/components/deployment.vue';
+import Commit from '~/environments/components/commit.vue';
import DeploymentStatusBadge from '~/environments/components/deployment_status_badge.vue';
import { resolvedEnvironment } from './graphql/mock_data';
describe('~/environments/components/deployment.vue', () => {
+ useFakeDate(2022, 0, 8, 16);
+
+ let deployment;
let wrapper;
+ beforeEach(() => {
+ deployment = resolvedEnvironment.lastDeployment;
+ });
+
const createWrapper = ({ propsData = {} } = {}) =>
mountExtended(Deployment, {
propsData: {
- deployment: resolvedEnvironment.lastDeployment,
+ deployment,
...propsData,
},
+ stubs: { transition: stubTransition() },
});
afterEach(() => {
@@ -21,9 +36,229 @@ describe('~/environments/components/deployment.vue', () => {
describe('status', () => {
it('should pass the deployable status to the badge', () => {
wrapper = createWrapper();
- expect(wrapper.findComponent(DeploymentStatusBadge).props('status')).toBe(
- resolvedEnvironment.lastDeployment.status,
- );
+ expect(wrapper.findComponent(DeploymentStatusBadge).props('status')).toBe(deployment.status);
+ });
+ });
+
+ describe('latest', () => {
+ it('should show a badge if the deployment is latest', () => {
+ wrapper = createWrapper({ propsData: { latest: true } });
+
+ const badge = wrapper.findByText(s__('Deployment|Latest Deployed'));
+
+ expect(badge.exists()).toBe(true);
+ });
+
+ it('should not show a badge if the deployment is not latest', () => {
+ wrapper = createWrapper();
+
+ const badge = wrapper.findByText(s__('Deployment|Latest Deployed'));
+
+ expect(badge.exists()).toBe(false);
+ });
+ });
+
+ describe('iid', () => {
+ const findIid = () => wrapper.findByTitle(s__('Deployment|Deployment ID'));
+ const findDeploymentIcon = () => wrapper.findComponent({ ref: 'deployment-iid-icon' });
+
+ describe('is present', () => {
+ beforeEach(() => {
+ wrapper = createWrapper();
+ });
+
+ it('should show the iid', () => {
+ const iid = findIid();
+ expect(iid.exists()).toBe(true);
+ });
+
+ it('should show an icon for the iid', () => {
+ const deploymentIcon = findDeploymentIcon();
+ expect(deploymentIcon.props('name')).toBe('deployments');
+ });
+ });
+
+ describe('is not present', () => {
+ beforeEach(() => {
+ wrapper = createWrapper({ propsData: { deployment: { ...deployment, iid: '' } } });
+ });
+
+ it('should not show the iid', () => {
+ const iid = findIid();
+ expect(iid.exists()).toBe(false);
+ });
+
+ it('should not show an icon for the iid', () => {
+ const deploymentIcon = findDeploymentIcon();
+ expect(deploymentIcon.exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('shortSha', () => {
+ describe('is present', () => {
+ beforeEach(() => {
+ wrapper = createWrapper();
+ });
+
+ it('shows the short SHA for the commit of the deployment', () => {
+ const sha = wrapper.findByTitle(__('Commit SHA'));
+
+ expect(sha.exists()).toBe(true);
+ expect(sha.text()).toBe(deployment.commit.shortId);
+ });
+
+ it('shows the commit icon', () => {
+ const icon = wrapper.findComponent({ ref: 'deployment-commit-icon' });
+ expect(icon.props('name')).toBe('commit');
+ });
+
+ it('shows a copy button for the sha', () => {
+ const button = wrapper.findComponent(ClipboardButton);
+ expect(button.props()).toMatchObject({
+ text: deployment.commit.shortId,
+ title: __('Copy commit SHA'),
+ });
+ });
+ });
+
+ describe('is not present', () => {
+ it('does not show the short SHA for the commit of the deployment', () => {
+ wrapper = createWrapper({
+ propsData: {
+ deployment: {
+ ...deployment,
+ commit: null,
+ },
+ },
+ });
+ const sha = wrapper.findByTestId('deployment-commit-sha');
+ expect(sha.exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('created at time', () => {
+ describe('is present', () => {
+ it('shows the timestamp the deployment was deployed at', () => {
+ wrapper = createWrapper();
+ const date = wrapper.findByTitle(formatDate(deployment.createdAt));
+
+ expect(date.text()).toBe('1 day ago');
+ });
+ });
+ describe('is not present', () => {
+ it('does not show the timestamp', () => {
+ wrapper = createWrapper({ propsData: { deployment: { ...deployment, createdAt: null } } });
+ const date = wrapper.findByTitle(formatDate(deployment.createdAt));
+
+ expect(date.exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('commit message', () => {
+ describe('with commit', () => {
+ beforeEach(() => {
+ wrapper = createWrapper();
+ });
+
+ it('shows the commit component', () => {
+ const commit = wrapper.findComponent(Commit);
+ expect(commit.props('commit')).toBe(deployment.commit);
+ });
+ });
+
+ describe('without a commit', () => {
+ it('displays nothing', () => {
+ const noCommit = {
+ ...deployment,
+ commit: null,
+ };
+ wrapper = createWrapper({ propsData: { deployment: noCommit } });
+
+ const commit = wrapper.findComponent(Commit);
+ expect(commit.exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('collapse', () => {
+ let collapse;
+ let button;
+
+ beforeEach(() => {
+ wrapper = createWrapper();
+ collapse = wrapper.findComponent(GlCollapse);
+ button = wrapper.findComponent({ ref: 'details-toggle' });
+ });
+
+ it('is collapsed by default', () => {
+ expect(collapse.attributes('visible')).toBeUndefined();
+ expect(button.props('icon')).toBe('expand-down');
+ expect(button.text()).toBe(__('Show details'));
+ });
+
+ it('opens on click', async () => {
+ await button.trigger('click');
+
+ expect(button.text()).toBe(__('Hide details'));
+ expect(button.props('icon')).toBe('expand-up');
+ expect(collapse.attributes('visible')).toBe('visible');
+
+ const username = wrapper.findByRole('link', { name: `@${deployment.user.username}` });
+
+ expect(username.attributes('href')).toBe(deployment.user.path);
+ const job = wrapper.findByRole('link', { name: deployment.deployable.name });
+ expect(job.attributes('href')).toBe(deployment.deployable.buildPath);
+ const apiBadge = wrapper.findByText(__('API'));
+ expect(apiBadge.exists()).toBe(false);
+
+ const branchLabel = wrapper.findByText(__('Branch'));
+ expect(branchLabel.exists()).toBe(true);
+ const tagLabel = wrapper.findByText(__('Tag'));
+ expect(tagLabel.exists()).toBe(false);
+ const ref = wrapper.findByRole('link', { name: deployment.ref.name });
+ expect(ref.attributes('href')).toBe(deployment.ref.refPath);
+ });
+ });
+
+ describe('with tagged deployment', () => {
+ beforeEach(async () => {
+ wrapper = createWrapper({ propsData: { deployment: { ...deployment, tag: true } } });
+ await wrapper.findComponent({ ref: 'details-toggle' }).trigger('click');
+ });
+
+ it('shows tag instead of branch', () => {
+ const refLabel = wrapper.findByText(__('Tag'));
+ expect(refLabel.exists()).toBe(true);
+ });
+ });
+
+ describe('with API deployment', () => {
+ beforeEach(async () => {
+ wrapper = createWrapper({ propsData: { deployment: { ...deployment, deployable: null } } });
+ await wrapper.findComponent({ ref: 'details-toggle' }).trigger('click');
+ });
+
+ it('shows API instead of a job name', () => {
+ const apiBadge = wrapper.findByText(__('API'));
+ expect(apiBadge.exists()).toBe(true);
+ });
+ });
+ describe('without a job path', () => {
+ beforeEach(async () => {
+ wrapper = createWrapper({
+ propsData: {
+ deployment: { ...deployment, deployable: { name: deployment.deployable.name } },
+ },
+ });
+ await wrapper.findComponent({ ref: 'details-toggle' }).trigger('click');
+ });
+
+ it('shows a span instead of a link', () => {
+ const job = wrapper.findByTitle(deployment.deployable.name);
+ expect(job.attributes('href')).toBeUndefined();
});
});
});
diff --git a/spec/frontend/environments/environment_actions_spec.js b/spec/frontend/environments/environment_actions_spec.js
index 1b68a692db8..336c207428e 100644
--- a/spec/frontend/environments/environment_actions_spec.js
+++ b/spec/frontend/environments/environment_actions_spec.js
@@ -1,6 +1,6 @@
import { GlDropdown, GlDropdownItem, GlLoadingIcon, GlIcon } 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 { TEST_HOST } from 'helpers/test_constants';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
@@ -108,7 +108,7 @@ describe('EnvironmentActions Component', () => {
jest.spyOn(window, 'confirm').mockImplementation(() => confirm);
findDropdownItem(scheduledJobAction).vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
};
beforeEach(() => {
diff --git a/spec/frontend/environments/environment_pin_spec.js b/spec/frontend/environments/environment_pin_spec.js
index a9a58071e12..669c974ea4f 100644
--- a/spec/frontend/environments/environment_pin_spec.js
+++ b/spec/frontend/environments/environment_pin_spec.js
@@ -1,5 +1,9 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
import { GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import cancelAutoStopMutation from '~/environments/graphql/mutations/cancel_auto_stop.mutation.graphql';
+import createMockApollo from 'helpers/mock_apollo_helper';
import PinComponent from '~/environments/components/environment_pin.vue';
import eventHub from '~/environments/event_hub';
@@ -18,28 +22,66 @@ describe('Pin Component', () => {
const autoStopUrl = '/root/auto-stop-env-test/-/environments/38/cancel_auto_stop';
- beforeEach(() => {
- factory({
- propsData: {
- autoStopUrl,
- },
+ describe('without graphql', () => {
+ beforeEach(() => {
+ factory({
+ propsData: {
+ autoStopUrl,
+ },
+ });
});
- });
- afterEach(() => {
- wrapper.destroy();
- });
+ afterEach(() => {
+ wrapper.destroy();
+ });
- it('should render the component with descriptive text', () => {
- expect(wrapper.text()).toBe('Prevent auto-stopping');
+ it('should render the component with descriptive text', () => {
+ expect(wrapper.text()).toBe('Prevent auto-stopping');
+ });
+
+ it('should emit onPinClick when clicked', () => {
+ const eventHubSpy = jest.spyOn(eventHub, '$emit');
+ const item = wrapper.find(GlDropdownItem);
+
+ item.vm.$emit('click');
+
+ expect(eventHubSpy).toHaveBeenCalledWith('cancelAutoStop', autoStopUrl);
+ });
});
- it('should emit onPinClick when clicked', () => {
- const eventHubSpy = jest.spyOn(eventHub, '$emit');
- const item = wrapper.find(GlDropdownItem);
+ describe('with graphql', () => {
+ Vue.use(VueApollo);
+ let mockApollo;
- item.vm.$emit('click');
+ beforeEach(() => {
+ mockApollo = createMockApollo();
+ factory({
+ propsData: {
+ autoStopUrl,
+ graphql: true,
+ },
+ apolloProvider: mockApollo,
+ });
+ });
- expect(eventHubSpy).toHaveBeenCalledWith('cancelAutoStop', autoStopUrl);
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should render the component with descriptive text', () => {
+ expect(wrapper.text()).toBe('Prevent auto-stopping');
+ });
+
+ it('should emit onPinClick when clicked', () => {
+ jest.spyOn(mockApollo.defaultClient, 'mutate');
+ const item = wrapper.find(GlDropdownItem);
+
+ item.vm.$emit('click');
+
+ expect(mockApollo.defaultClient.mutate).toHaveBeenCalledWith({
+ mutation: cancelAutoStopMutation,
+ variables: { autoStopUrl },
+ });
+ });
});
});
diff --git a/spec/frontend/environments/environment_table_spec.js b/spec/frontend/environments/environment_table_spec.js
index 1851163ac68..c7582e4b06d 100644
--- a/spec/frontend/environments/environment_table_spec.js
+++ b/spec/frontend/environments/environment_table_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import CanaryUpdateModal from '~/environments/components/canary_update_modal.vue';
import DeployBoard from '~/environments/components/deploy_board.vue';
import EnvironmentTable from '~/environments/components/environments_table.vue';
@@ -181,7 +182,7 @@ describe('Environment table', () => {
});
wrapper.find(DeployBoard).vm.$emit('changeCanaryWeight', 40);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(CanaryUpdateModal).props()).toMatchObject({
weight: 40,
diff --git a/spec/frontend/environments/environments_app_spec.js b/spec/frontend/environments/environments_app_spec.js
index cd05ecbfb53..92d1820681c 100644
--- a/spec/frontend/environments/environments_app_spec.js
+++ b/spec/frontend/environments/environments_app_spec.js
@@ -1,6 +1,7 @@
import { GlTabs } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import Container from '~/environments/components/container.vue';
import DeployBoard from '~/environments/components/deploy_board.vue';
@@ -186,14 +187,13 @@ describe('Environment', () => {
expect(wrapper.find('.folder-icon[data-testid="chevron-right-icon"]').exists()).toBe(false);
});
- it('should close an opened folder', () => {
+ it('should close an opened folder', async () => {
expect(wrapper.find('.folder-icon[data-testid="chevron-down-icon"]').exists()).toBe(true);
// close folder
wrapper.find('.folder-name').trigger('click');
- wrapper.vm.$nextTick(() => {
- expect(wrapper.find('.folder-icon[data-testid="chevron-down-icon"]').exists()).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.find('.folder-icon[data-testid="chevron-down-icon"]').exists()).toBe(false);
});
it('should show children environments', () => {
diff --git a/spec/frontend/environments/graphql/mock_data.js b/spec/frontend/environments/graphql/mock_data.js
index fce30973547..1b7b35702de 100644
--- a/spec/frontend/environments/graphql/mock_data.js
+++ b/spec/frontend/environments/graphql/mock_data.js
@@ -1,3 +1,69 @@
+export const rolloutStatus = {
+ instances: [
+ {
+ status: 'succeeded',
+ tooltip: 'tanuki-2334 Finished',
+ podName: 'production-tanuki-1',
+ stable: false,
+ },
+ {
+ status: 'succeeded',
+ tooltip: 'tanuki-2335 Finished',
+ podName: 'production-tanuki-1',
+ stable: false,
+ },
+ {
+ status: 'succeeded',
+ tooltip: 'tanuki-2336 Finished',
+ podName: 'production-tanuki-1',
+ stable: false,
+ },
+ {
+ status: 'succeeded',
+ tooltip: 'tanuki-2337 Finished',
+ podName: 'production-tanuki-1',
+ stable: false,
+ },
+ {
+ status: 'succeeded',
+ tooltip: 'tanuki-2338 Finished',
+ podName: 'production-tanuki-1',
+ stable: false,
+ },
+ {
+ status: 'succeeded',
+ tooltip: 'tanuki-2339 Finished',
+ podName: 'production-tanuki-1',
+ stable: false,
+ },
+ { status: 'succeeded', tooltip: 'tanuki-2340 Finished', podName: 'production-tanuki-1' },
+ { status: 'succeeded', tooltip: 'tanuki-2334 Finished', podName: 'production-tanuki-1' },
+ { status: 'succeeded', tooltip: 'tanuki-2335 Finished', podName: 'production-tanuki-1' },
+ { status: 'succeeded', tooltip: 'tanuki-2336 Finished', podName: 'production-tanuki-1' },
+ { status: 'succeeded', tooltip: 'tanuki-2337 Finished', podName: 'production-tanuki-1' },
+ { status: 'succeeded', tooltip: 'tanuki-2338 Finished', podName: 'production-tanuki-1' },
+ { status: 'succeeded', tooltip: 'tanuki-2339 Finished', podName: 'production-tanuki-1' },
+ { status: 'succeeded', tooltip: 'tanuki-2340 Finished', podName: 'production-tanuki-1' },
+ { status: 'running', tooltip: 'tanuki-2341 Deploying', podName: 'production-tanuki-1' },
+ { status: 'running', tooltip: 'tanuki-2342 Deploying', podName: 'production-tanuki-1' },
+ { status: 'running', tooltip: 'tanuki-2343 Deploying', podName: 'production-tanuki-1' },
+ { status: 'failed', tooltip: 'tanuki-2344 Failed', podName: 'production-tanuki-1' },
+ { status: 'unknown', tooltip: 'tanuki-2345 Ready', podName: 'production-tanuki-1' },
+ { status: 'unknown', tooltip: 'tanuki-2346 Ready', podName: 'production-tanuki-1' },
+ { status: 'pending', tooltip: 'tanuki-2348 Preparing', podName: 'production-tanuki-1' },
+ { status: 'pending', tooltip: 'tanuki-2349 Preparing', podName: 'production-tanuki-1' },
+ { status: 'pending', tooltip: 'tanuki-2350 Preparing', podName: 'production-tanuki-1' },
+ { status: 'pending', tooltip: 'tanuki-2353 Preparing', podName: 'production-tanuki-1' },
+ { status: 'pending', tooltip: 'tanuki-2354 waiting', podName: 'production-tanuki-1' },
+ { status: 'pending', tooltip: 'tanuki-2355 waiting', podName: 'production-tanuki-1' },
+ { status: 'pending', tooltip: 'tanuki-2356 waiting', podName: 'production-tanuki-1' },
+ ],
+ abortUrl: 'url',
+ rollbackUrl: 'url',
+ completion: 100,
+ status: 'found',
+ canaryIngress: { canaryWeight: 50 },
+};
export const environmentsApp = {
environments: [
{
diff --git a/spec/frontend/environments/graphql/resolvers_spec.js b/spec/frontend/environments/graphql/resolvers_spec.js
index 6b53dc24f0f..21d7e09bad5 100644
--- a/spec/frontend/environments/graphql/resolvers_spec.js
+++ b/spec/frontend/environments/graphql/resolvers_spec.js
@@ -173,9 +173,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
it('should post to the auto stop path', async () => {
mock.onPost(ENDPOINT).reply(200);
- await mockResolvers.Mutation.cancelAutoStop(null, {
- environment: { autoStopPath: ENDPOINT },
- });
+ await mockResolvers.Mutation.cancelAutoStop(null, { autoStopUrl: ENDPOINT });
expect(mock.history.post).toContainEqual(
expect.objectContaining({ url: ENDPOINT, method: 'post' }),
diff --git a/spec/frontend/environments/new_environment_folder_spec.js b/spec/frontend/environments/new_environment_folder_spec.js
index 6823c88a5a1..460263587be 100644
--- a/spec/frontend/environments/new_environment_folder_spec.js
+++ b/spec/frontend/environments/new_environment_folder_spec.js
@@ -32,6 +32,7 @@ describe('~/environments/components/new_environments_folder.vue', () => {
apolloProvider,
propsData,
stubs: { transition: stubTransition() },
+ provide: { helpPagePath: '/help' },
});
beforeEach(async () => {
diff --git a/spec/frontend/environments/new_environment_item_spec.js b/spec/frontend/environments/new_environment_item_spec.js
index 244aef5c43b..db596688dad 100644
--- a/spec/frontend/environments/new_environment_item_spec.js
+++ b/spec/frontend/environments/new_environment_item_spec.js
@@ -2,12 +2,14 @@ import VueApollo from 'vue-apollo';
import Vue from 'vue';
import { GlCollapse, GlIcon } from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
-import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
import { stubTransition } from 'helpers/stub_transition';
-import { __, s__ } from '~/locale';
+import { formatDate, getTimeago } from '~/lib/utils/datetime_utility';
+import { __, s__, sprintf } from '~/locale';
import EnvironmentItem from '~/environments/components/new_environment_item.vue';
import Deployment from '~/environments/components/deployment.vue';
-import { resolvedEnvironment } from './graphql/mock_data';
+import DeployBoardWrapper from '~/environments/components/deploy_board_wrapper.vue';
+import { resolvedEnvironment, rolloutStatus } from './graphql/mock_data';
Vue.use(VueApollo);
@@ -22,11 +24,19 @@ describe('~/environments/components/new_environment_item.vue', () => {
mountExtended(EnvironmentItem, {
apolloProvider,
propsData: { environment: resolvedEnvironment, ...propsData },
+ provide: { helpPagePath: '/help' },
stubs: { transition: stubTransition() },
});
const findDeployment = () => wrapper.findComponent(Deployment);
+ const expandCollapsedSection = async () => {
+ const button = wrapper.findByRole('button', { name: __('Expand') });
+ await button.trigger('click');
+
+ return button;
+ };
+
afterEach(() => {
wrapper?.destroy();
});
@@ -165,25 +175,92 @@ describe('~/environments/components/new_environment_item.vue', () => {
});
describe('pin', () => {
- it('shows the option to pin the environment if there is an autostop date', () => {
- wrapper = createWrapper({
- propsData: {
- environment: { ...resolvedEnvironment, autoStopAt: new Date(Date.now() + 100000) },
- },
- apolloProvider: createApolloProvider(),
+ describe('with autostop', () => {
+ let environment;
+
+ beforeEach(() => {
+ environment = {
+ ...resolvedEnvironment,
+ autoStopAt: new Date(Date.now() + 100000).toString(),
+ };
+ wrapper = createWrapper({
+ propsData: {
+ environment,
+ },
+ apolloProvider: createApolloProvider(),
+ });
});
- const rollback = wrapper.findByRole('menuitem', { name: __('Prevent auto-stopping') });
+ it('shows the option to pin the environment if there is an autostop date', () => {
+ const pin = wrapper.findByRole('menuitem', { name: __('Prevent auto-stopping') });
- expect(rollback.exists()).toBe(true);
+ expect(pin.exists()).toBe(true);
+ });
+
+ it('shows when the environment auto stops', () => {
+ const autoStop = wrapper.findByTitle(formatDate(environment.autoStopAt));
+
+ expect(autoStop.text()).toBe('in 1 minute');
+ });
});
- it('does not show the option to pin the environment if there is no autostop date', () => {
- wrapper = createWrapper({ apolloProvider: createApolloProvider() });
+ describe('without autostop', () => {
+ beforeEach(() => {
+ wrapper = createWrapper({ apolloProvider: createApolloProvider() });
+ });
- const rollback = wrapper.findByRole('menuitem', { name: __('Prevent auto-stopping') });
+ it('does not show the option to pin the environment if there is no autostop date', () => {
+ wrapper = createWrapper({ apolloProvider: createApolloProvider() });
- expect(rollback.exists()).toBe(false);
+ const pin = wrapper.findByRole('menuitem', { name: __('Prevent auto-stopping') });
+
+ expect(pin.exists()).toBe(false);
+ });
+
+ it('does not show when the environment auto stops', () => {
+ const autoStop = wrapper.findByText(
+ sprintf(s__('Environment|Auto stop %{time}'), {
+ time: getTimeago().format(resolvedEnvironment.autoStopAt),
+ }),
+ );
+
+ expect(autoStop.exists()).toBe(false);
+ });
+ });
+
+ describe('with past autostop', () => {
+ let environment;
+
+ beforeEach(() => {
+ environment = {
+ ...resolvedEnvironment,
+ autoStopAt: new Date(Date.now() - 100000).toString(),
+ };
+ wrapper = createWrapper({
+ propsData: {
+ environment,
+ },
+ apolloProvider: createApolloProvider(),
+ });
+ });
+
+ it('does not show the option to pin the environment if there is no autostop date', () => {
+ wrapper = createWrapper({ apolloProvider: createApolloProvider() });
+
+ const pin = wrapper.findByRole('menuitem', { name: __('Prevent auto-stopping') });
+
+ expect(pin.exists()).toBe(false);
+ });
+
+ it('does not show when the environment auto stops', () => {
+ const autoStop = wrapper.findByText(
+ sprintf(s__('Environment|Auto stop %{time}'), {
+ time: getTimeago().format(environment.autoStopAt),
+ }),
+ );
+
+ expect(autoStop.exists()).toBe(false);
+ });
});
});
@@ -258,14 +335,12 @@ describe('~/environments/components/new_environment_item.vue', () => {
describe('collapse', () => {
let icon;
let collapse;
- let button;
let environmentName;
beforeEach(() => {
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
collapse = wrapper.findComponent(GlCollapse);
icon = wrapper.findComponent(GlIcon);
- button = wrapper.findByRole('button', { name: __('Expand') });
environmentName = wrapper.findByText(resolvedEnvironment.name);
});
@@ -278,7 +353,7 @@ describe('~/environments/components/new_environment_item.vue', () => {
it('opens on click', async () => {
expect(findDeployment().isVisible()).toBe(false);
- await button.trigger('click');
+ const button = await expandCollapsedSection();
expect(button.attributes('aria-label')).toBe(__('Collapse'));
expect(collapse.attributes('visible')).toBe('visible');
@@ -338,4 +413,78 @@ describe('~/environments/components/new_environment_item.vue', () => {
expect(deployment.exists()).toBe(false);
});
});
+
+ describe('empty state', () => {
+ it('should link to documentation', async () => {
+ const environment = {
+ ...resolvedEnvironment,
+ lastDeployment: null,
+ upcomingDeployment: null,
+ };
+
+ wrapper = createWrapper({
+ propsData: { environment },
+ apolloProvider: createApolloProvider(),
+ });
+
+ await expandCollapsedSection();
+
+ const text = s__(
+ 'Environments|There are no deployments for this environment yet. Learn more about setting up deployments.',
+ );
+
+ const emptyState = wrapper.findByText((_content, element) => element.textContent === text);
+
+ const link = extendedWrapper(emptyState).findByRole('link');
+
+ expect(link.attributes('href')).toBe('/help');
+ });
+
+ it('should not link to the documentation when there are deployments', async () => {
+ wrapper = createWrapper({
+ apolloProvider: createApolloProvider(),
+ });
+
+ await expandCollapsedSection();
+
+ const text = s__(
+ 'Environments|There are no deployments for this environment yet. Learn more about setting up deployments.',
+ );
+
+ const emptyState = wrapper.findByText((_content, element) => element.textContent === text);
+
+ expect(emptyState.exists()).toBe(false);
+ });
+ });
+
+ describe('deploy boards', () => {
+ it('should show a deploy board if the environment has a rollout status', async () => {
+ const environment = {
+ ...resolvedEnvironment,
+ rolloutStatus,
+ };
+
+ wrapper = createWrapper({
+ propsData: { environment },
+ apolloProvider: createApolloProvider(),
+ });
+
+ await expandCollapsedSection();
+
+ const deployBoard = wrapper.findComponent(DeployBoardWrapper);
+ expect(deployBoard.exists()).toBe(true);
+ expect(deployBoard.props('rolloutStatus')).toBe(rolloutStatus);
+ });
+
+ it('should not show a deploy board if the environment has no rollout status', async () => {
+ wrapper = createWrapper({
+ apolloProvider: createApolloProvider(),
+ });
+
+ await expandCollapsedSection();
+
+ const deployBoard = wrapper.findComponent(DeployBoardWrapper);
+ expect(deployBoard.exists()).toBe(false);
+ });
+ });
});
diff --git a/spec/frontend/environments/new_environments_app_spec.js b/spec/frontend/environments/new_environments_app_spec.js
index c9eccc26694..42e3608109b 100644
--- a/spec/frontend/environments/new_environments_app_spec.js
+++ b/spec/frontend/environments/new_environments_app_spec.js
@@ -10,6 +10,7 @@ import EnvironmentsApp from '~/environments/components/new_environments_app.vue'
import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue';
import EnvironmentsItem from '~/environments/components/new_environment_item.vue';
import StopEnvironmentModal from '~/environments/components/stop_environment_modal.vue';
+import CanaryUpdateModal from '~/environments/components/canary_update_modal.vue';
import { resolvedEnvironmentsApp, resolvedFolder, resolvedEnvironment } from './graphql/mock_data';
Vue.use(VueApollo);
@@ -20,6 +21,8 @@ describe('~/environments/components/new_environments_app.vue', () => {
let environmentFolderMock;
let paginationMock;
let environmentToStopMock;
+ let environmentToChangeCanaryMock;
+ let weightMock;
const createApolloProvider = () => {
const mockResolvers = {
@@ -28,6 +31,10 @@ describe('~/environments/components/new_environments_app.vue', () => {
folder: environmentFolderMock,
pageInfo: paginationMock,
environmentToStop: environmentToStopMock,
+ environmentToDelete: jest.fn().mockResolvedValue(resolvedEnvironment),
+ environmentToRollback: jest.fn().mockResolvedValue(resolvedEnvironment),
+ environmentToChangeCanary: environmentToChangeCanaryMock,
+ weight: weightMock,
},
};
@@ -40,6 +47,7 @@ describe('~/environments/components/new_environments_app.vue', () => {
newEnvironmentPath: '/environments/new',
canCreateEnvironment: true,
defaultBranchName: 'main',
+ helpPagePath: '/help',
...provide,
},
apolloProvider,
@@ -50,6 +58,8 @@ describe('~/environments/components/new_environments_app.vue', () => {
environmentsApp,
folder,
environmentToStop = {},
+ environmentToChangeCanary = {},
+ weight = 0,
pageInfo = {
total: 20,
perPage: 5,
@@ -64,6 +74,8 @@ describe('~/environments/components/new_environments_app.vue', () => {
environmentFolderMock.mockReturnValue(folder);
paginationMock.mockReturnValue(pageInfo);
environmentToStopMock.mockReturnValue(environmentToStop);
+ environmentToChangeCanaryMock.mockReturnValue(environmentToChangeCanary);
+ weightMock.mockReturnValue(weight);
const apolloProvider = createApolloProvider();
wrapper = createWrapper({ apolloProvider, provide });
@@ -75,11 +87,13 @@ describe('~/environments/components/new_environments_app.vue', () => {
environmentAppMock = jest.fn();
environmentFolderMock = jest.fn();
environmentToStopMock = jest.fn();
+ environmentToChangeCanaryMock = jest.fn();
+ weightMock = jest.fn();
paginationMock = jest.fn();
});
afterEach(() => {
- wrapper?.destroy();
+ wrapper.destroy();
});
it('should show all the folders that are fetched', async () => {
@@ -149,8 +163,6 @@ describe('~/environments/components/new_environments_app.vue', () => {
},
folder: resolvedFolder,
});
- await waitForPromises();
- await nextTick();
const button = wrapper.findByRole('button', { name: s__('Environments|Enable review app') });
expect(button.exists()).toBe(false);
@@ -206,6 +218,19 @@ describe('~/environments/components/new_environments_app.vue', () => {
expect(modal.props('environment')).toMatchObject(resolvedEnvironment);
});
+
+ it('should pass the environment to change canary to the canary update modal', async () => {
+ await createWrapperWithMocked({
+ environmentsApp: resolvedEnvironmentsApp,
+ folder: resolvedFolder,
+ environmentToChangeCanary: resolvedEnvironment,
+ weight: 10,
+ });
+
+ const modal = wrapper.findComponent(CanaryUpdateModal);
+
+ expect(modal.props('environment')).toMatchObject(resolvedEnvironment);
+ });
});
describe('pagination', () => {
diff --git a/spec/frontend/error_tracking/components/error_details_spec.js b/spec/frontend/error_tracking/components/error_details_spec.js
index 77f51193258..03ae437a89e 100644
--- a/spec/frontend/error_tracking/components/error_details_spec.js
+++ b/spec/frontend/error_tracking/components/error_details_spec.js
@@ -7,7 +7,8 @@ import {
GlAlert,
GlSprintf,
} from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import {
severityLevel,
@@ -27,8 +28,7 @@ import Tracking from '~/tracking';
jest.mock('~/flash');
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('ErrorDetails', () => {
let store;
@@ -53,7 +53,6 @@ describe('ErrorDetails', () => {
function mountComponent() {
wrapper = shallowMount(ErrorDetails, {
stubs: { GlButton, GlSprintf },
- localVue,
store,
mocks,
propsData: {
@@ -140,32 +139,30 @@ describe('ErrorDetails', () => {
mountComponent();
});
- it('when before timeout, still shows loading', () => {
+ it('when before timeout, still shows loading', async () => {
Date.now.mockReturnValue(endTime - 1);
wrapper.vm.onNoApolloResult();
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
- expect(createFlash).not.toHaveBeenCalled();
- expect(mocks.$apollo.queries.error.stopPolling).not.toHaveBeenCalled();
- });
+ await nextTick();
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
+ expect(createFlash).not.toHaveBeenCalled();
+ expect(mocks.$apollo.queries.error.stopPolling).not.toHaveBeenCalled();
});
- it('when timeout is hit and no apollo result, stops loading and shows flash', () => {
+ it('when timeout is hit and no apollo result, stops loading and shows flash', async () => {
Date.now.mockReturnValue(endTime + 1);
wrapper.vm.onNoApolloResult();
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
- expect(wrapper.find(GlLink).exists()).toBe(false);
- expect(createFlash).toHaveBeenCalledWith({
- message: 'Could not connect to Sentry. Refresh the page to try again.',
- type: 'warning',
- });
- expect(mocks.$apollo.queries.error.stopPolling).toHaveBeenCalled();
+ await nextTick();
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.find(GlLink).exists()).toBe(false);
+ expect(createFlash).toHaveBeenCalledWith({
+ message: 'Could not connect to Sentry. Refresh the page to try again.',
+ type: 'warning',
});
+ expect(mocks.$apollo.queries.error.stopPolling).toHaveBeenCalled();
});
});
@@ -225,7 +222,7 @@ describe('ErrorDetails', () => {
});
describe('Badges', () => {
- it('should show language and error level badges', () => {
+ it('should show language and error level badges', 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({
@@ -233,12 +230,11 @@ describe('ErrorDetails', () => {
tags: { level: 'error', logger: 'ruby' },
},
});
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.findAll(GlBadge).length).toBe(2);
- });
+ await nextTick();
+ expect(wrapper.findAll(GlBadge).length).toBe(2);
});
- it('should NOT show the badge if the tag is not present', () => {
+ it('should NOT show the badge if the tag is not present', 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({
@@ -246,14 +242,13 @@ describe('ErrorDetails', () => {
tags: { level: 'error' },
},
});
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.findAll(GlBadge).length).toBe(1);
- });
+ await nextTick();
+ expect(wrapper.findAll(GlBadge).length).toBe(1);
});
it.each(Object.keys(severityLevel))(
'should set correct severity level variant for %s badge',
- (level) => {
+ async (level) => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
@@ -261,15 +256,14 @@ describe('ErrorDetails', () => {
tags: { level: severityLevel[level] },
},
});
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlBadge).props('variant')).toEqual(
- severityLevelVariant[severityLevel[level]],
- );
- });
+ await nextTick();
+ expect(wrapper.find(GlBadge).props('variant')).toEqual(
+ severityLevelVariant[severityLevel[level]],
+ );
},
);
- it('should fallback for ERROR severityLevelVariant when severityLevel is unknown', () => {
+ it('should fallback for ERROR severityLevelVariant when severityLevel is unknown', 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({
@@ -277,32 +271,29 @@ describe('ErrorDetails', () => {
tags: { level: 'someNewErrorLevel' },
},
});
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlBadge).props('variant')).toEqual(
- severityLevelVariant[severityLevel.ERROR],
- );
- });
+ await nextTick();
+ expect(wrapper.find(GlBadge).props('variant')).toEqual(
+ severityLevelVariant[severityLevel.ERROR],
+ );
});
});
describe('Stacktrace', () => {
- it('should show stacktrace', () => {
+ it('should show stacktrace', async () => {
store.state.details.loadingStacktrace = false;
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
- expect(wrapper.find(Stacktrace).exists()).toBe(true);
- expect(findAlert().exists()).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.find(Stacktrace).exists()).toBe(true);
+ expect(findAlert().exists()).toBe(false);
});
- it('should NOT show stacktrace if no entries and show Alert message', () => {
+ it('should NOT show stacktrace if no entries and show Alert message', async () => {
store.state.details.loadingStacktrace = false;
store.getters = { 'details/sentryUrl': () => 'sentry.io', 'details/stacktrace': () => [] };
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
- expect(wrapper.find(Stacktrace).exists()).toBe(false);
- expect(findAlert().text()).toBe('No stack trace for this error');
- });
+ await nextTick();
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.find(Stacktrace).exists()).toBe(false);
+ expect(findAlert().text()).toBe('No stack trace for this error');
});
});
@@ -339,10 +330,10 @@ describe('ErrorDetails', () => {
});
describe('when error is unresolved', () => {
- beforeEach(() => {
+ beforeEach(async () => {
store.state.details.errorStatus = errorStatus.UNRESOLVED;
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays Ignore and Resolve buttons', () => {
@@ -366,10 +357,10 @@ describe('ErrorDetails', () => {
});
describe('when error is ignored', () => {
- beforeEach(() => {
+ beforeEach(async () => {
store.state.details.errorStatus = errorStatus.IGNORED;
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays Undo Ignore and Resolve buttons', () => {
@@ -393,10 +384,10 @@ describe('ErrorDetails', () => {
});
describe('when error is resolved', () => {
- beforeEach(() => {
+ beforeEach(async () => {
store.state.details.errorStatus = errorStatus.RESOLVED;
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays Ignore and Unresolve buttons', () => {
@@ -418,7 +409,7 @@ describe('ErrorDetails', () => {
);
});
- it('should show alert with closed issueId', () => {
+ it('should show alert with closed issueId', async () => {
const closedIssueId = 123;
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -427,10 +418,9 @@ describe('ErrorDetails', () => {
closedIssueId,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findAlert().exists()).toBe(true);
- expect(findAlert().text()).toContain(`#${closedIssueId}`);
- });
+ await nextTick();
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toContain(`#${closedIssueId}`);
});
});
});
@@ -496,7 +486,7 @@ describe('ErrorDetails', () => {
'/gitlab-org/gitlab-test/commit/7975be0116940bf2ad4321f79d02a55c5f7779aa';
const findGitLabCommitLink = () => wrapper.find(`[href$="${gitlabCommitPath}"]`);
- it('should display a link', () => {
+ it('should display a link', async () => {
mocks.$apollo.queries.error.loading = false;
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -506,12 +496,11 @@ describe('ErrorDetails', () => {
gitlabCommitPath,
},
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findGitLabCommitLink().exists()).toBe(true);
- });
+ await nextTick();
+ expect(findGitLabCommitLink().exists()).toBe(true);
});
- it('should not display a link', () => {
+ it('should not display a link', async () => {
mocks.$apollo.queries.error.loading = false;
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
@@ -520,9 +509,8 @@ describe('ErrorDetails', () => {
gitlabCommit: null,
},
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findGitLabCommitLink().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findGitLabCommitLink().exists()).toBe(false);
});
});
@@ -595,33 +583,25 @@ describe('ErrorDetails', () => {
expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
- it('should track IGNORE status update', () => {
+ it('should track IGNORE status update', async () => {
Tracking.event.mockClear();
- findUpdateIgnoreStatusButton().vm.$emit('click');
- setImmediate(() => {
- const { category, action } = trackErrorStatusUpdateOptions('ignored');
- expect(Tracking.event).toHaveBeenCalledWith(category, action);
- });
+ await findUpdateIgnoreStatusButton().trigger('click');
+ const { category, action } = trackErrorStatusUpdateOptions('ignored');
+ expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
- it('should track RESOLVE status update', () => {
+ it('should track RESOLVE status update', async () => {
Tracking.event.mockClear();
- findUpdateResolveStatusButton().vm.$emit('click');
- setImmediate(() => {
- const { category, action } = trackErrorStatusUpdateOptions('resolved');
- expect(Tracking.event).toHaveBeenCalledWith(category, action);
- });
+ await findUpdateResolveStatusButton().trigger('click');
+ const { category, action } = trackErrorStatusUpdateOptions('resolved');
+ expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
- it('should track external Sentry link views', () => {
+ it('should track external Sentry link views', async () => {
Tracking.event.mockClear();
- findExternalUrl().trigger('click');
- setImmediate(() => {
- const { category, action, label, property } = trackClickErrorLinkToSentryOptions(
- externalUrl,
- );
- expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property });
- });
+ await findExternalUrl().trigger('click');
+ const { category, action, label, property } = trackClickErrorLinkToSentryOptions(externalUrl);
+ expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property });
});
});
});
diff --git a/spec/frontend/error_tracking/components/error_tracking_actions_spec.js b/spec/frontend/error_tracking/components/error_tracking_actions_spec.js
index e21c40423c3..7ed4e5f6b05 100644
--- a/spec/frontend/error_tracking/components/error_tracking_actions_spec.js
+++ b/spec/frontend/error_tracking/components/error_tracking_actions_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import ErrorTrackingActions from '~/error_tracking/components/error_tracking_actions.vue';
describe('Error Tracking Actions', () => {
@@ -37,13 +38,12 @@ describe('Error Tracking Actions', () => {
const findButtons = () => wrapper.findAll(GlButton);
describe('when error status is unresolved', () => {
- it('renders the correct actions buttons to allow ignore and resolve', () => {
+ it('renders the correct actions buttons to allow ignore and resolve', async () => {
expect(findButtons().exists()).toBe(true);
- return wrapper.vm.$nextTick().then(() => {
- expect(findButtons().at(0).attributes('title')).toBe('Ignore');
- expect(findButtons().at(1).attributes('title')).toBe('Resolve');
- });
+ await nextTick();
+ expect(findButtons().at(0).attributes('title')).toBe('Ignore');
+ expect(findButtons().at(1).attributes('title')).toBe('Resolve');
});
});
@@ -52,12 +52,11 @@ describe('Error Tracking Actions', () => {
mountComponent({ error: { status: 'ignored' } });
});
- it('renders the correct action button to undo ignore', () => {
+ it('renders the correct action button to undo ignore', async () => {
expect(findButtons().exists()).toBe(true);
- return wrapper.vm.$nextTick().then(() => {
- expect(findButtons().at(0).attributes('title')).toBe('Undo Ignore');
- });
+ await nextTick();
+ expect(findButtons().at(0).attributes('title')).toBe('Undo Ignore');
});
});
@@ -66,12 +65,11 @@ describe('Error Tracking Actions', () => {
mountComponent({ error: { status: 'resolved' } });
});
- it('renders the correct action button to undo unresolve', () => {
+ it('renders the correct action button to undo unresolve', async () => {
expect(findButtons().exists()).toBe(true);
- return wrapper.vm.$nextTick().then(() => {
- expect(findButtons().at(1).attributes('title')).toBe('Unresolve');
- });
+ await nextTick();
+ expect(findButtons().at(1).attributes('title')).toBe('Unresolve');
});
});
});
diff --git a/spec/frontend/error_tracking/components/error_tracking_list_spec.js b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
index 74d5731bbea..59671c175e7 100644
--- a/spec/frontend/error_tracking/components/error_tracking_list_spec.js
+++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
@@ -1,5 +1,6 @@
import { GlEmptyState, GlLoadingIcon, GlFormInput, GlPagination, GlDropdown } from '@gitlab/ui';
-import { createLocalVue, mount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import stubChildren from 'helpers/stub_children';
import ErrorTrackingActions from '~/error_tracking/components/error_tracking_actions.vue';
@@ -8,8 +9,7 @@ import { trackErrorListViewsOptions, trackErrorStatusUpdateOptions } from '~/err
import Tracking from '~/tracking';
import errorsList from './list_mock.json';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('ErrorTrackingList', () => {
let store;
@@ -32,7 +32,6 @@ describe('ErrorTrackingList', () => {
stubs = {},
} = {}) {
wrapper = mount(ErrorTrackingList, {
- localVue,
store,
propsData: {
indexPath: '/path',
@@ -317,15 +316,14 @@ describe('ErrorTrackingList', () => {
expect(findRecentSearchesDropdown().text()).toContain("You don't have any recent searches");
});
- it('shows items', () => {
+ it('shows items', async () => {
store.state.list.recentSearches = ['great', 'search'];
- return wrapper.vm.$nextTick().then(() => {
- const dropdownItems = wrapper.findAll('.filtered-search-box li');
- expect(dropdownItems.length).toBe(3);
- expect(dropdownItems.at(0).text()).toBe('great');
- expect(dropdownItems.at(1).text()).toBe('search');
- });
+ await nextTick();
+ const dropdownItems = wrapper.findAll('.filtered-search-box li');
+ expect(dropdownItems.length).toBe(3);
+ expect(dropdownItems.at(0).text()).toBe('great');
+ expect(dropdownItems.at(1).text()).toBe('search');
});
describe('clear', () => {
@@ -337,23 +335,21 @@ describe('ErrorTrackingList', () => {
expect(clearRecentButton().exists()).toBe(false);
});
- it('is visible when list has items', () => {
+ it('is visible when list has items', async () => {
store.state.list.recentSearches = ['some', 'searches'];
- return wrapper.vm.$nextTick().then(() => {
- expect(clearRecentButton().exists()).toBe(true);
- expect(clearRecentButton().text()).toBe('Clear recent searches');
- });
+ await nextTick();
+ expect(clearRecentButton().exists()).toBe(true);
+ expect(clearRecentButton().text()).toBe('Clear recent searches');
});
- it('clears items on click', () => {
+ it('clears items on click', async () => {
store.state.list.recentSearches = ['some', 'searches'];
- return wrapper.vm.$nextTick().then(() => {
- clearRecentButton().vm.$emit('click');
+ await nextTick();
+ clearRecentButton().vm.$emit('click');
- expect(actions.clearRecentSearches).toHaveBeenCalledTimes(1);
- });
+ expect(actions.clearRecentSearches).toHaveBeenCalledTimes(1);
});
});
});
@@ -388,7 +384,7 @@ describe('ErrorTrackingList', () => {
describe('and the user is not on the first page', () => {
describe('and the previous button is clicked', () => {
- beforeEach(() => {
+ beforeEach(async () => {
store.state.list.loading = false;
mountComponent({
stubs: {
@@ -399,7 +395,7 @@ describe('ErrorTrackingList', () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ pageValue: 2 });
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('fetches the previous page of results', () => {
@@ -451,7 +447,7 @@ describe('ErrorTrackingList', () => {
expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
- it('should track status updates', () => {
+ it('should track status updates', async () => {
Tracking.event.mockClear();
const status = 'ignored';
findErrorActions().vm.$emit('update-issue-status', {
@@ -459,10 +455,10 @@ describe('ErrorTrackingList', () => {
status,
});
- setImmediate(() => {
- const { category, action } = trackErrorStatusUpdateOptions(status);
- expect(Tracking.event).toHaveBeenCalledWith(category, action);
- });
+ await nextTick();
+
+ const { category, action } = trackErrorStatusUpdateOptions(status);
+ expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
});
});
diff --git a/spec/frontend/error_tracking_settings/components/app_spec.js b/spec/frontend/error_tracking_settings/components/app_spec.js
index 844faff64a1..4d19ec047ef 100644
--- a/spec/frontend/error_tracking_settings/components/app_spec.js
+++ b/spec/frontend/error_tracking_settings/components/app_spec.js
@@ -1,6 +1,6 @@
import { GlFormRadioGroup, GlFormRadio, GlFormInputGroup } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { TEST_HOST } from 'helpers/test_constants';
@@ -10,8 +10,7 @@ import ErrorTrackingForm from '~/error_tracking_settings/components/error_tracki
import ProjectDropdown from '~/error_tracking_settings/components/project_dropdown.vue';
import createStore from '~/error_tracking_settings/store';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
const TEST_GITLAB_DSN = 'https://gitlab.example.com/123456';
@@ -22,7 +21,6 @@ describe('error tracking settings app', () => {
function mountComponent() {
wrapper = extendedWrapper(
shallowMount(ErrorTrackingSettings, {
- localVue,
store, // Override the imported store
propsData: {
initialEnabled: 'true',
@@ -81,12 +79,11 @@ describe('error tracking settings app', () => {
expect(wrapper.find('.js-error-tracking-button').attributes('disabled')).toBeFalsy();
});
- it('disables the button when saving', () => {
+ it('disables the button when saving', async () => {
store.state.settingsLoading = true;
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find('.js-error-tracking-button').attributes('disabled')).toBeTruthy();
- });
+ await nextTick();
+ expect(wrapper.find('.js-error-tracking-button').attributes('disabled')).toBeTruthy();
});
});
diff --git a/spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js b/spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js
index 2e8a42dbfe6..69d684faec2 100644
--- a/spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js
+++ b/spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js
@@ -1,12 +1,12 @@
import { GlFormInput, GlButton } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import ErrorTrackingForm from '~/error_tracking_settings/components/error_tracking_form.vue';
import createStore from '~/error_tracking_settings/store';
import { defaultProps } from '../mock';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('error tracking settings form', () => {
let wrapper;
@@ -14,7 +14,6 @@ describe('error tracking settings form', () => {
function mountComponent() {
wrapper = shallowMount(ErrorTrackingForm, {
- localVue,
store,
propsData: defaultProps,
});
diff --git a/spec/frontend/error_tracking_settings/components/project_dropdown_spec.js b/spec/frontend/error_tracking_settings/components/project_dropdown_spec.js
index 79518a487d4..1ba5a505f57 100644
--- a/spec/frontend/error_tracking_settings/components/project_dropdown_spec.js
+++ b/spec/frontend/error_tracking_settings/components/project_dropdown_spec.js
@@ -1,19 +1,18 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import { pick, clone } from 'lodash';
import Vuex from 'vuex';
import ProjectDropdown from '~/error_tracking_settings/components/project_dropdown.vue';
import { defaultProps, projectList, staleProject } from '../mock';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('error tracking settings project dropdown', () => {
let wrapper;
function mountComponent() {
wrapper = shallowMount(ProjectDropdown, {
- localVue,
propsData: {
...pick(
defaultProps,
@@ -64,10 +63,10 @@ describe('error tracking settings project dropdown', () => {
});
describe('populated project list', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.setProps({ projects: clone(projectList), hasProjects: true });
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders the dropdown', () => {
@@ -84,9 +83,9 @@ describe('error tracking settings project dropdown', () => {
describe('selected project', () => {
const selectedProject = clone(projectList[0]);
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.setProps({ projects: clone(projectList), selectedProject, hasProjects: true });
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('does not show helper text', () => {
@@ -96,13 +95,13 @@ describe('error tracking settings project dropdown', () => {
});
describe('invalid project selected', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.setProps({
projects: clone(projectList),
selectedProject: staleProject,
isProjectInvalid: true,
});
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays a error', () => {
diff --git a/spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js b/spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js
index f244da228b3..4a0242b4a46 100644
--- a/spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js
+++ b/spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js
@@ -1,5 +1,6 @@
import { GlModal, GlSprintf, GlAlert } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import Component from '~/feature_flags/components/configure_feature_flags_modal.vue';
@@ -56,7 +57,7 @@ describe('Configure Feature Flags Modal', () => {
it('should emit a `token` event when clicking on the Primary action', async () => {
findGlModal().vm.$emit('secondary', mockEvent);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('token')).toEqual([[]]);
expect(mockEvent.preventDefault).toHaveBeenCalled();
});
@@ -64,7 +65,7 @@ describe('Configure Feature Flags Modal', () => {
it('should clear the project name input after generating the token', async () => {
findProjectNameInput().vm.$emit('input', provide.projectName);
findGlModal().vm.$emit('primary', mockEvent);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findProjectNameInput().attributes('value')).toBe('');
});
@@ -116,7 +117,7 @@ describe('Configure Feature Flags Modal', () => {
it('should enable the secondary action', async () => {
findProjectNameInput().vm.$emit('input', provide.projectName);
- await wrapper.vm.$nextTick();
+ await nextTick();
const [{ disabled }] = findSecondaryAction().attributes;
expect(disabled).toBe(false);
});
diff --git a/spec/frontend/feature_flags/components/edit_feature_flag_spec.js b/spec/frontend/feature_flags/components/edit_feature_flag_spec.js
index 721b7249abc..05709cd05e6 100644
--- a/spec/frontend/feature_flags/components/edit_feature_flag_spec.js
+++ b/spec/frontend/feature_flags/components/edit_feature_flag_spec.js
@@ -1,7 +1,7 @@
import { GlToggle, GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { mockTracking } from 'helpers/tracking_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -12,6 +12,7 @@ import createStore from '~/feature_flags/store/edit';
import axios from '~/lib/utils/axios_utils';
Vue.use(Vuex);
+
describe('Edit feature flag form', () => {
let wrapper;
let mock;
@@ -66,13 +67,12 @@ describe('Edit feature flag form', () => {
});
describe('with error', () => {
- it('should render the error', () => {
+ it('should render the error', async () => {
store.dispatch('receiveUpdateFeatureFlagError', { message: ['The name is required'] });
- return wrapper.vm.$nextTick(() => {
- const warningGlAlert = findWarningGlAlert();
- expect(warningGlAlert.exists()).toEqual(true);
- expect(warningGlAlert.text()).toContain('The name is required');
- });
+ await nextTick();
+ const warningGlAlert = findWarningGlAlert();
+ expect(warningGlAlert.exists()).toEqual(true);
+ expect(warningGlAlert.text()).toContain('The name is required');
});
});
diff --git a/spec/frontend/feature_flags/components/empty_state_spec.js b/spec/frontend/feature_flags/components/empty_state_spec.js
index 86d0c1a05fd..4ac82ae44a6 100644
--- a/spec/frontend/feature_flags/components/empty_state_spec.js
+++ b/spec/frontend/feature_flags/components/empty_state_spec.js
@@ -1,5 +1,6 @@
import { GlAlert, GlEmptyState, GlLink, GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import EmptyState from '~/feature_flags/components/empty_state.vue';
const DEFAULT_PROPS = {
@@ -123,7 +124,7 @@ describe('feature_flags/components/feature_flags_tab.vue', () => {
beforeEach(async () => {
wrapper = factory();
- await wrapper.vm.$nextTick();
+ await nextTick();
slot = wrapper.find('[data-testid="test-slot"]');
});
diff --git a/spec/frontend/feature_flags/components/environments_dropdown_spec.js b/spec/frontend/feature_flags/components/environments_dropdown_spec.js
index 9194db3a182..cca472012e9 100644
--- a/spec/frontend/feature_flags/components/environments_dropdown_spec.js
+++ b/spec/frontend/feature_flags/components/environments_dropdown_spec.js
@@ -1,6 +1,7 @@
import { GlLoadingIcon, GlButton, GlSearchBoxByType } 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 { TEST_HOST } from 'spec/test_constants';
import EnvironmentsDropdown from '~/feature_flags/components/environments_dropdown.vue';
@@ -54,7 +55,7 @@ describe('Feature flags > Environments dropdown ', () => {
factory();
findEnvironmentSearchInput().vm.$emit('focus');
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('.dropdown-content > ul').exists()).toBe(true);
expect(wrapper.findAll('.dropdown-content > ul > li').exists()).toBe(true);
});
@@ -66,7 +67,7 @@ describe('Feature flags > Environments dropdown ', () => {
factory();
findEnvironmentSearchInput().vm.$emit('keyup');
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('.dropdown-content > ul').exists()).toBe(true);
expect(wrapper.findAll('.dropdown-content > ul > li').exists()).toBe(true);
});
@@ -80,7 +81,7 @@ describe('Feature flags > Environments dropdown ', () => {
findEnvironmentSearchInput().vm.$emit('focus');
findEnvironmentSearchInput().vm.$emit('input', 'production');
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('sets filter value', () => {
@@ -103,7 +104,7 @@ describe('Feature flags > Environments dropdown ', () => {
.filter((b) => b.text() === 'production')
.at(0);
button.vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('selectEnvironment')).toEqual([['production']]);
});
});
@@ -111,7 +112,7 @@ describe('Feature flags > Environments dropdown ', () => {
describe('on click clear button', () => {
beforeEach(async () => {
wrapper.find(GlButton).vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('resets filter value', () => {
@@ -132,12 +133,12 @@ describe('Feature flags > Environments dropdown ', () => {
findEnvironmentSearchInput().vm.$emit('focus');
findEnvironmentSearchInput().vm.$emit('input', 'production');
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('emits create event', async () => {
wrapper.findAll(GlButton).at(0).vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('createClicked')).toEqual([['production']]);
});
});
diff --git a/spec/frontend/feature_flags/components/feature_flags_spec.js b/spec/frontend/feature_flags/components/feature_flags_spec.js
index db4bdc736de..d27b23c5cd1 100644
--- a/spec/frontend/feature_flags/components/feature_flags_spec.js
+++ b/spec/frontend/feature_flags/components/feature_flags_spec.js
@@ -1,5 +1,6 @@
import { GlAlert, GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import MockAdapter from 'axios-mock-adapter';
import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
@@ -13,8 +14,7 @@ import axios from '~/lib/utils/axios_utils';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import { getRequestData } from '../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Feature flags', () => {
const mockData = {
@@ -45,7 +45,6 @@ describe('Feature flags', () => {
const factory = (provide = mockData, fn = mount) => {
store = createStore(mockState);
wrapper = fn(FeatureFlagsComponent, {
- localVue,
store,
provide,
stubs: {
@@ -72,12 +71,12 @@ describe('Feature flags', () => {
describe('when limit exceeded', () => {
const provideData = { ...mockData, featureFlagsLimitExceeded: true };
- beforeEach((done) => {
+ beforeEach(() => {
mock
.onGet(`${TEST_HOST}/endpoint.json`, { params: { page: '1' } })
.reply(200, getRequestData, {});
factory(provideData);
- setImmediate(done);
+ return waitForPromises();
});
it('makes the new feature flag button do nothing if clicked', () => {
@@ -117,12 +116,12 @@ describe('Feature flags', () => {
userListPath: null,
};
- beforeEach((done) => {
+ beforeEach(() => {
mock
.onGet(`${TEST_HOST}/endpoint.json`, { params: { page: '1' } })
.reply(200, getRequestData, {});
factory(provideData);
- setImmediate(done);
+ return waitForPromises();
});
it('does not render configure button', () => {
@@ -173,7 +172,7 @@ describe('Feature flags', () => {
factory();
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
emptyState = wrapper.findComponent(GlEmptyState);
});
@@ -203,7 +202,7 @@ describe('Feature flags', () => {
});
describe('with paginated feature flags', () => {
- beforeEach((done) => {
+ beforeEach(() => {
mock.onGet(mockState.endpoint, { params: { page: '1' } }).replyOnce(200, getRequestData, {
'x-next-page': '2',
'x-page': '1',
@@ -215,7 +214,7 @@ describe('Feature flags', () => {
factory();
jest.spyOn(store, 'dispatch');
- setImmediate(done);
+ return waitForPromises();
});
it('should render a table with feature flags', () => {
@@ -271,11 +270,11 @@ describe('Feature flags', () => {
});
describe('unsuccessful request', () => {
- beforeEach((done) => {
+ beforeEach(() => {
mock.onGet(mockState.endpoint, { params: { page: '1' } }).replyOnce(500, {});
factory();
- setImmediate(done);
+ return waitForPromises();
});
it('should render error state', () => {
@@ -301,12 +300,12 @@ describe('Feature flags', () => {
});
describe('rotate instance id', () => {
- beforeEach((done) => {
+ beforeEach(() => {
mock
.onGet(`${TEST_HOST}/endpoint.json`, { params: { page: '1' } })
.reply(200, getRequestData, {});
factory();
- setImmediate(done);
+ return waitForPromises();
});
it('should fire the rotate action when a `token` event is received', () => {
diff --git a/spec/frontend/feature_flags/components/feature_flags_table_spec.js b/spec/frontend/feature_flags/components/feature_flags_table_spec.js
index d06d60ae310..99864a95f59 100644
--- a/spec/frontend/feature_flags/components/feature_flags_table_spec.js
+++ b/spec/frontend/feature_flags/components/feature_flags_table_spec.js
@@ -1,5 +1,6 @@
import { GlToggle, GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { trimText } from 'helpers/text_helper';
import { mockTracking } from 'helpers/tracking_helper';
import FeatureFlagsTable from '~/feature_flags/components/feature_flags_table.vue';
@@ -148,13 +149,12 @@ describe('Feature flag table', () => {
});
});
- it('should trigger a toggle event', () => {
+ it('should trigger a toggle event', async () => {
toggle.vm.$emit('change');
const flag = { ...props.featureFlags[0], active: !props.featureFlags[0].active };
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('toggle-flag')).toEqual([[flag]]);
- });
+ await nextTick();
+ expect(wrapper.emitted('toggle-flag')).toEqual([[flag]]);
});
it('tracks a click', () => {
diff --git a/spec/frontend/feature_flags/components/form_spec.js b/spec/frontend/feature_flags/components/form_spec.js
index c0f9638390a..3ad1225906b 100644
--- a/spec/frontend/feature_flags/components/form_spec.js
+++ b/spec/frontend/feature_flags/components/form_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import Api from '~/api';
import Form from '~/feature_flags/components/form.vue';
@@ -126,28 +127,26 @@ describe('feature flag form', () => {
expect(wrapper.findAll(Strategy)).toHaveLength(2);
});
- it('adds an all users strategy when clicking the Add button', () => {
+ it('adds an all users strategy when clicking the Add button', async () => {
wrapper.find(GlButton).vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- const strategies = wrapper.findAll(Strategy);
+ await nextTick();
+ const strategies = wrapper.findAll(Strategy);
- expect(strategies).toHaveLength(3);
- expect(strategies.at(2).props('strategy')).toEqual(allUsersStrategy);
- });
+ expect(strategies).toHaveLength(3);
+ expect(strategies.at(2).props('strategy')).toEqual(allUsersStrategy);
});
- it('should remove a strategy on delete', () => {
+ it('should remove a strategy on delete', async () => {
const strategy = {
type: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
parameters: { percentage: '30' },
scopes: [],
};
wrapper.find(Strategy).vm.$emit('delete');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.findAll(Strategy)).toHaveLength(1);
- expect(wrapper.find(Strategy).props('strategy')).not.toEqual(strategy);
- });
+ await nextTick();
+ expect(wrapper.findAll(Strategy)).toHaveLength(1);
+ expect(wrapper.find(Strategy).props('strategy')).not.toEqual(strategy);
});
});
});
diff --git a/spec/frontend/feature_flags/components/new_environments_dropdown_spec.js b/spec/frontend/feature_flags/components/new_environments_dropdown_spec.js
index 6342ac0bda7..63fa5d19982 100644
--- a/spec/frontend/feature_flags/components/new_environments_dropdown_spec.js
+++ b/spec/frontend/feature_flags/components/new_environments_dropdown_spec.js
@@ -1,6 +1,7 @@
import { GlLoadingIcon, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import NewEnvironmentsDropdown from '~/feature_flags/components/new_environments_dropdown.vue';
import axios from '~/lib/utils/axios_utils';
import httpStatusCodes from '~/lib/utils/http_status';
@@ -47,16 +48,13 @@ describe('New Environments Dropdown', () => {
describe('with empty results', () => {
let item;
- beforeEach(() => {
+ beforeEach(async () => {
axiosMock.onGet(TEST_HOST).reply(200, []);
wrapper.find(GlSearchBoxByType).vm.$emit('focus');
wrapper.find(GlSearchBoxByType).vm.$emit('input', TEST_SEARCH);
- return axios
- .waitForAll()
- .then(() => wrapper.vm.$nextTick())
- .then(() => {
- item = wrapper.find(GlDropdownItem);
- });
+ await axios.waitForAll();
+ await nextTick();
+ item = wrapper.find(GlDropdownItem);
});
it('should display a Create item label', () => {
diff --git a/spec/frontend/feature_flags/components/new_feature_flag_spec.js b/spec/frontend/feature_flags/components/new_feature_flag_spec.js
index fe98b6421d4..9c1657bc0d2 100644
--- a/spec/frontend/feature_flags/components/new_feature_flag_spec.js
+++ b/spec/frontend/feature_flags/components/new_feature_flag_spec.js
@@ -1,5 +1,6 @@
import { GlAlert } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { TEST_HOST } from 'spec/test_constants';
import Form from '~/feature_flags/components/form.vue';
@@ -10,8 +11,7 @@ import { allUsersStrategy } from '../mock_data';
const userCalloutId = 'feature_flags_new_version';
const userCalloutsPath = `${TEST_HOST}/user_callouts`;
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('New feature flag form', () => {
let wrapper;
@@ -27,7 +27,6 @@ describe('New feature flag form', () => {
wrapper = null;
}
wrapper = shallowMount(NewFeatureFlag, {
- localVue,
store,
provide: {
showUserCallout: true,
@@ -52,13 +51,12 @@ describe('New feature flag form', () => {
});
describe('with error', () => {
- it('should render the error', () => {
+ it('should render the error', async () => {
store.dispatch('receiveCreateFeatureFlagError', { message: ['The name is required'] });
- return wrapper.vm.$nextTick(() => {
- const warningGlAlert = findWarningGlAlert();
- expect(warningGlAlert.at(0).exists()).toBe(true);
- expect(warningGlAlert.at(0).text()).toContain('The name is required');
- });
+ await nextTick();
+ const warningGlAlert = findWarningGlAlert();
+ expect(warningGlAlert.at(0).exists()).toBe(true);
+ expect(warningGlAlert.at(0).text()).toContain('The name is required');
});
});
diff --git a/spec/frontend/feature_flags/components/strategies/flexible_rollout_spec.js b/spec/frontend/feature_flags/components/strategies/flexible_rollout_spec.js
index 07aa456e69e..56b14d80ab3 100644
--- a/spec/frontend/feature_flags/components/strategies/flexible_rollout_spec.js
+++ b/spec/frontend/feature_flags/components/strategies/flexible_rollout_spec.js
@@ -1,5 +1,6 @@
import { GlFormInput, GlFormSelect } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import FlexibleRollout from '~/feature_flags/components/strategies/flexible_rollout.vue';
import ParameterFormGroup from '~/feature_flags/components/strategies/parameter_form_group.vue';
import { PERCENT_ROLLOUT_GROUP_ID } from '~/feature_flags/constants';
@@ -51,7 +52,7 @@ describe('feature_flags/components/strategies/flexible_rollout.vue', () => {
it('emits a change when the percentage value changes', async () => {
percentageInput.setValue('75');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('change')).toEqual([
[
{
diff --git a/spec/frontend/feature_flags/components/strategies/gitlab_user_list_spec.js b/spec/frontend/feature_flags/components/strategies/gitlab_user_list_spec.js
index 6188672b23b..3b69194494f 100644
--- a/spec/frontend/feature_flags/components/strategies/gitlab_user_list_spec.js
+++ b/spec/frontend/feature_flags/components/strategies/gitlab_user_list_spec.js
@@ -1,5 +1,6 @@
import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlLoadingIcon } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import Api from '~/api';
import GitlabUserList from '~/feature_flags/components/strategies/gitlab_user_list.vue';
@@ -12,15 +13,13 @@ const DEFAULT_PROPS = {
strategy: userListStrategy,
};
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('~/feature_flags/components/strategies/gitlab_user_list.vue', () => {
let wrapper;
const factory = (props = {}) =>
mount(GitlabUserList, {
- localVue,
store: createStore({ projectId: '1' }),
propsData: { ...DEFAULT_PROPS, ...props },
});
@@ -72,7 +71,7 @@ describe('~/feature_flags/components/strategies/gitlab_user_list.vue', () => {
);
const searchWrapper = wrapper.find(GlSearchBoxByType);
searchWrapper.vm.$emit('input', 'new');
- await wrapper.vm.$nextTick();
+ await nextTick();
const loadingIcon = wrapper.find(GlLoadingIcon);
expect(loadingIcon.exists()).toBe(true);
@@ -80,7 +79,7 @@ describe('~/feature_flags/components/strategies/gitlab_user_list.vue', () => {
r({ data: [userList] });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(loadingIcon.exists()).toBe(false);
});
diff --git a/spec/frontend/feature_flags/components/strategies/percent_rollout_spec.js b/spec/frontend/feature_flags/components/strategies/percent_rollout_spec.js
index 442f7faf161..180697e93e4 100644
--- a/spec/frontend/feature_flags/components/strategies/percent_rollout_spec.js
+++ b/spec/frontend/feature_flags/components/strategies/percent_rollout_spec.js
@@ -1,5 +1,6 @@
import { GlFormInput } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import ParameterFormGroup from '~/feature_flags/components/strategies/parameter_form_group.vue';
import PercentRollout from '~/feature_flags/components/strategies/percent_rollout.vue';
import { PERCENT_ROLLOUT_GROUP_ID } from '~/feature_flags/constants';
@@ -39,7 +40,7 @@ describe('~/feature_flags/components/strategies/percent_rollout.vue', () => {
it('emits a change when the value changes', async () => {
input.setValue('75');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('change')).toEqual([
[{ parameters: { percentage: '75', groupId: PERCENT_ROLLOUT_GROUP_ID } }],
]);
diff --git a/spec/frontend/feature_flags/components/strategy_spec.js b/spec/frontend/feature_flags/components/strategy_spec.js
index 4fdf436bfc4..aee3873721c 100644
--- a/spec/frontend/feature_flags/components/strategy_spec.js
+++ b/spec/frontend/feature_flags/components/strategy_spec.js
@@ -1,5 +1,6 @@
import { GlAlert, GlFormSelect, GlLink, GlToken, GlButton } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import { last } from 'lodash';
import Vuex from 'vuex';
import Api from '~/api';
@@ -26,8 +27,7 @@ const provide = {
environmentsEndpoint: '',
};
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Feature flags strategy', () => {
let wrapper;
@@ -48,7 +48,7 @@ describe('Feature flags strategy', () => {
wrapper.destroy();
wrapper = null;
}
- wrapper = mount(Strategy, { localVue, store: createStore({ projectId: '1' }), ...opts });
+ wrapper = mount(Strategy, { store: createStore({ projectId: '1' }), ...opts });
};
beforeEach(() => {
@@ -85,11 +85,11 @@ describe('Feature flags strategy', () => {
let propsData;
let strategy;
- beforeEach(() => {
+ beforeEach(async () => {
strategy = { name, parameters: {}, scopes: [] };
propsData = { strategy, index: 0 };
factory({ propsData, provide });
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('should set the select to match the strategy name', () => {
@@ -138,19 +138,18 @@ describe('Feature flags strategy', () => {
factory({ propsData, provide });
});
- it('should revert to all-environments scope when last scope is removed', () => {
+ it('should revert to all-environments scope when last scope is removed', async () => {
const token = wrapper.find(GlToken);
token.vm.$emit('close');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.findAll(GlToken)).toHaveLength(0);
- expect(last(wrapper.emitted('change'))).toEqual([
- {
- name: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
- parameters: { percentage: '50', groupId: PERCENT_ROLLOUT_GROUP_ID },
- scopes: [{ environmentScope: '*' }],
- },
- ]);
- });
+ await nextTick();
+ expect(wrapper.findAll(GlToken)).toHaveLength(0);
+ expect(last(wrapper.emitted('change'))).toEqual([
+ {
+ name: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
+ parameters: { percentage: '50', groupId: PERCENT_ROLLOUT_GROUP_ID },
+ scopes: [{ environmentScope: '*' }],
+ },
+ ]);
});
});
@@ -167,56 +166,52 @@ describe('Feature flags strategy', () => {
factory({ propsData, provide });
});
- it('should change the parameters if a different strategy is chosen', () => {
+ it('should change the parameters if a different strategy is chosen', async () => {
const select = wrapper.find(GlFormSelect);
select.setValue(ROLLOUT_STRATEGY_ALL_USERS);
- return wrapper.vm.$nextTick().then(() => {
- expect(last(wrapper.emitted('change'))).toEqual([
- {
- name: ROLLOUT_STRATEGY_ALL_USERS,
- parameters: {},
- scopes: [{ environmentScope: '*' }],
- },
- ]);
- });
+ await nextTick();
+ expect(last(wrapper.emitted('change'))).toEqual([
+ {
+ name: ROLLOUT_STRATEGY_ALL_USERS,
+ parameters: {},
+ scopes: [{ environmentScope: '*' }],
+ },
+ ]);
});
- it('should display selected scopes', () => {
+ it('should display selected scopes', async () => {
const dropdown = wrapper.find(NewEnvironmentsDropdown);
dropdown.vm.$emit('add', 'production');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.findAll(GlToken)).toHaveLength(1);
- expect(wrapper.find(GlToken).text()).toBe('production');
- });
+ await nextTick();
+ expect(wrapper.findAll(GlToken)).toHaveLength(1);
+ expect(wrapper.find(GlToken).text()).toBe('production');
});
- it('should display all selected scopes', () => {
+ it('should display all selected scopes', async () => {
const dropdown = wrapper.find(NewEnvironmentsDropdown);
dropdown.vm.$emit('add', 'production');
dropdown.vm.$emit('add', 'staging');
- return wrapper.vm.$nextTick().then(() => {
- const tokens = wrapper.findAll(GlToken);
- expect(tokens).toHaveLength(2);
- expect(tokens.at(0).text()).toBe('production');
- expect(tokens.at(1).text()).toBe('staging');
- });
+ await nextTick();
+ const tokens = wrapper.findAll(GlToken);
+ expect(tokens).toHaveLength(2);
+ expect(tokens.at(0).text()).toBe('production');
+ expect(tokens.at(1).text()).toBe('staging');
});
- it('should emit selected scopes', () => {
+ it('should emit selected scopes', async () => {
const dropdown = wrapper.find(NewEnvironmentsDropdown);
dropdown.vm.$emit('add', 'production');
- return wrapper.vm.$nextTick().then(() => {
- expect(last(wrapper.emitted('change'))).toEqual([
- {
- name: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
- parameters: { percentage: '50', groupId: PERCENT_ROLLOUT_GROUP_ID },
- scopes: [
- { environmentScope: '*', shouldBeDestroyed: true },
- { environmentScope: 'production' },
- ],
- },
- ]);
- });
+ await nextTick();
+ expect(last(wrapper.emitted('change'))).toEqual([
+ {
+ name: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
+ parameters: { percentage: '50', groupId: PERCENT_ROLLOUT_GROUP_ID },
+ scopes: [
+ { environmentScope: '*', shouldBeDestroyed: true },
+ { environmentScope: 'production' },
+ ],
+ },
+ ]);
});
it('should emit a delete if the delete button is clicked', () => {
@@ -236,39 +231,36 @@ describe('Feature flags strategy', () => {
factory({ propsData, provide });
});
- it('should display selected scopes', () => {
+ it('should display selected scopes', async () => {
const dropdown = wrapper.find(NewEnvironmentsDropdown);
dropdown.vm.$emit('add', 'production');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.findAll(GlToken)).toHaveLength(1);
- expect(wrapper.find(GlToken).text()).toBe('production');
- });
+ await nextTick();
+ expect(wrapper.findAll(GlToken)).toHaveLength(1);
+ expect(wrapper.find(GlToken).text()).toBe('production');
});
- it('should display all selected scopes', () => {
+ it('should display all selected scopes', async () => {
const dropdown = wrapper.find(NewEnvironmentsDropdown);
dropdown.vm.$emit('add', 'production');
dropdown.vm.$emit('add', 'staging');
- return wrapper.vm.$nextTick().then(() => {
- const tokens = wrapper.findAll(GlToken);
- expect(tokens).toHaveLength(2);
- expect(tokens.at(0).text()).toBe('production');
- expect(tokens.at(1).text()).toBe('staging');
- });
+ await nextTick();
+ const tokens = wrapper.findAll(GlToken);
+ expect(tokens).toHaveLength(2);
+ expect(tokens.at(0).text()).toBe('production');
+ expect(tokens.at(1).text()).toBe('staging');
});
- it('should emit selected scopes', () => {
+ it('should emit selected scopes', async () => {
const dropdown = wrapper.find(NewEnvironmentsDropdown);
dropdown.vm.$emit('add', 'production');
- return wrapper.vm.$nextTick().then(() => {
- expect(last(wrapper.emitted('change'))).toEqual([
- {
- name: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
- parameters: { percentage: '50', groupId: PERCENT_ROLLOUT_GROUP_ID },
- scopes: [{ environmentScope: 'production' }],
- },
- ]);
- });
+ await nextTick();
+ expect(last(wrapper.emitted('change'))).toEqual([
+ {
+ name: ROLLOUT_STRATEGY_PERCENT_ROLLOUT,
+ parameters: { percentage: '50', groupId: PERCENT_ROLLOUT_GROUP_ID },
+ scopes: [{ environmentScope: 'production' }],
+ },
+ ]);
});
});
});
diff --git a/spec/frontend/feature_highlight/feature_highlight_popover_spec.js b/spec/frontend/feature_highlight/feature_highlight_popover_spec.js
index e5e3974e103..650f9eb1bbc 100644
--- a/spec/frontend/feature_highlight/feature_highlight_popover_spec.js
+++ b/spec/frontend/feature_highlight/feature_highlight_popover_spec.js
@@ -1,5 +1,6 @@
import { GlPopover, GlLink, GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { POPOVER_TARGET_ID } from '~/feature_highlight/constants';
import { dismiss } from '~/feature_highlight/feature_highlight_helper';
import FeatureHighlightPopover from '~/feature_highlight/feature_highlight_popover.vue';
@@ -71,7 +72,7 @@ describe('feature_highlight/feature_highlight_popover', () => {
it('hides the popover target', async () => {
await findDismissButton().trigger('click');
findPopover().vm.$emit('hidden');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findPopoverTarget().exists()).toBe(false);
});
diff --git a/spec/frontend/filtered_search/dropdown_user_spec.js b/spec/frontend/filtered_search/dropdown_user_spec.js
index 9a20fb1bae6..ee0eef6a1b6 100644
--- a/spec/frontend/filtered_search/dropdown_user_spec.js
+++ b/spec/frontend/filtered_search/dropdown_user_spec.js
@@ -74,7 +74,7 @@ describe('Dropdown User', () => {
});
describe('hideCurrentUser', () => {
- const fixtureTemplate = 'issues/issue_list.html';
+ const fixtureTemplate = 'merge_requests/merge_request_list.html';
let dropdown;
let authorFilterDropdownElement;
diff --git a/spec/frontend/filtered_search/dropdown_utils_spec.js b/spec/frontend/filtered_search/dropdown_utils_spec.js
index 49e14f58630..4c1e79eba42 100644
--- a/spec/frontend/filtered_search/dropdown_utils_spec.js
+++ b/spec/frontend/filtered_search/dropdown_utils_spec.js
@@ -4,7 +4,7 @@ import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dro
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
describe('Dropdown Utils', () => {
- const issueListFixture = 'issues/issue_list.html';
+ const issuableListFixture = 'merge_requests/merge_request_list.html';
describe('getEscapedText', () => {
it('should return same word when it has no space', () => {
@@ -350,7 +350,7 @@ describe('Dropdown Utils', () => {
let authorToken;
beforeEach(() => {
- loadFixtures(issueListFixture);
+ loadFixtures(issuableListFixture);
authorToken = FilteredSearchSpecHelper.createFilterVisualToken('author', '=', '@user');
const searchTermToken = FilteredSearchSpecHelper.createSearchVisualToken('search term');
diff --git a/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js b/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js
index 44f67f269a2..c4e125e96da 100644
--- a/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/frontend/filtered_search/filtered_search_visual_tokens_spec.js
@@ -1,6 +1,7 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import FilteredSearchSpecHelper from 'helpers/filtered_search_spec_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import FilteredSearchVisualTokens from '~/filtered_search/filtered_search_visual_tokens';
describe('Filtered Search Visual Tokens', () => {
@@ -715,18 +716,16 @@ describe('Filtered Search Visual Tokens', () => {
`);
});
- it('renders a author token value element', () => {
+ it('renders a author token value element', async () => {
const { tokenNameElement, tokenValueElement } = findElements(authorToken);
const tokenName = tokenNameElement.textContent;
const tokenValue = 'new value';
subject.renderVisualTokenValue(authorToken, tokenName, tokenValue);
- jest.runOnlyPendingTimers();
+ await waitForPromises();
- setImmediate(() => {
- expect(tokenValueElement.textContent).toBe(tokenValue);
- });
+ expect(tokenValueElement.textContent).toBe(tokenValue);
});
});
});
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/fixtures/application_settings.rb b/spec/frontend/fixtures/application_settings.rb
index 9fa8d68e695..a7a989f31ec 100644
--- a/spec/frontend/fixtures/application_settings.rb
+++ b/spec/frontend/fixtures/application_settings.rb
@@ -13,6 +13,7 @@ RSpec.describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', ty
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+ allow(Gitlab::Metrics).to receive(:metrics_folder_present?).and_return(true)
sign_in(admin)
enable_admin_mode!(admin)
end
diff --git a/spec/frontend/fixtures/blob.rb b/spec/frontend/fixtures/blob.rb
index 35a7ff4eb07..af548823886 100644
--- a/spec/frontend/fixtures/blob.rb
+++ b/spec/frontend/fixtures/blob.rb
@@ -7,7 +7,7 @@ RSpec.describe Projects::BlobController, '(JavaScript fixtures)', type: :control
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
render_views
diff --git a/spec/frontend/fixtures/branches.rb b/spec/frontend/fixtures/branches.rb
index 828564977e0..b3bb4b8873a 100644
--- a/spec/frontend/fixtures/branches.rb
+++ b/spec/frontend/fixtures/branches.rb
@@ -7,7 +7,7 @@ RSpec.describe 'Branches (JavaScript fixtures)' do
let_it_be(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let_it_be(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
- let_it_be(:user) { project.owner }
+ let_it_be(:user) { project.first_owner }
after(:all) do
remove_repository(project)
diff --git a/spec/frontend/fixtures/clusters.rb b/spec/frontend/fixtures/clusters.rb
index ea883555255..49596d98774 100644
--- a/spec/frontend/fixtures/clusters.rb
+++ b/spec/frontend/fixtures/clusters.rb
@@ -8,7 +8,7 @@ RSpec.describe Projects::ClustersController, '(JavaScript fixtures)', type: :con
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace) }
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
render_views
diff --git a/spec/frontend/fixtures/commit.rb b/spec/frontend/fixtures/commit.rb
index f9e0f604b52..815c140950d 100644
--- a/spec/frontend/fixtures/commit.rb
+++ b/spec/frontend/fixtures/commit.rb
@@ -6,7 +6,7 @@ RSpec.describe 'Commit (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
let_it_be(:project) { create(:project, :repository) }
- let_it_be(:user) { project.owner }
+ let_it_be(:user) { project.first_owner }
let_it_be(:commit) { project.commit("master") }
before do
diff --git a/spec/frontend/fixtures/freeze_period.rb b/spec/frontend/fixtures/freeze_period.rb
index d9573c8000d..dd16bd81b51 100644
--- a/spec/frontend/fixtures/freeze_period.rb
+++ b/spec/frontend/fixtures/freeze_period.rb
@@ -7,7 +7,7 @@ RSpec.describe 'Freeze Periods (JavaScript fixtures)' do
include TimeZoneHelper
let_it_be(:project) { create(:project, :repository, path: 'freeze-periods-project') }
- let_it_be(:user) { project.owner }
+ let_it_be(:user) { project.first_owner }
after(:all) do
remove_repository(project)
diff --git a/spec/frontend/fixtures/issues.rb b/spec/frontend/fixtures/issues.rb
index 6519416cb9e..8bedb802242 100644
--- a/spec/frontend/fixtures/issues.rb
+++ b/spec/frontend/fixtures/issues.rb
@@ -37,17 +37,6 @@ RSpec.describe Projects::IssuesController, '(JavaScript fixtures)', type: :contr
render_issue(create(:closed_issue, project: project))
end
- it 'issues/issue_list.html' do
- create(:issue, project: project)
-
- get :index, params: {
- namespace_id: project.namespace.to_param,
- project_id: project
- }
-
- expect(response).to be_successful
- end
-
private
def render_issue(issue)
diff --git a/spec/frontend/fixtures/jobs.rb b/spec/frontend/fixtures/jobs.rb
index 12584f38629..3cc87432655 100644
--- a/spec/frontend/fixtures/jobs.rb
+++ b/spec/frontend/fixtures/jobs.rb
@@ -7,7 +7,7 @@ RSpec.describe Projects::JobsController, '(JavaScript fixtures)', type: :control
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'builds-project') }
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id) }
let!(:build_with_artifacts) { create(:ci_build, :success, :artifacts, :trace_artifact, pipeline: pipeline, stage: 'test', artifacts_expire_at: Time.now + 18.months) }
let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline, stage: 'build') }
diff --git a/spec/frontend/fixtures/listbox.rb b/spec/frontend/fixtures/listbox.rb
new file mode 100644
index 00000000000..8f8489a2827
--- /dev/null
+++ b/spec/frontend/fixtures/listbox.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'initRedirectListboxBehavior', '(JavaScript fixtures)', type: :helper do
+ include JavaScriptFixturesHelpers
+ include ListboxHelper
+
+ let(:response) { @tag }
+
+ it 'listbox/redirect_listbox.html' do
+ items = [{
+ value: 'foo',
+ text: 'Foo',
+ href: '/foo',
+ arbitrary_key: 'foo xyz'
+ }, {
+ value: 'bar',
+ text: 'Bar',
+ href: '/bar',
+ arbitrary_key: 'bar xyz'
+ }, {
+ value: 'qux',
+ text: 'Qux',
+ href: '/qux',
+ arbitrary_key: 'qux xyz'
+ }]
+
+ @tag = helper.gl_redirect_listbox_tag(items, 'bar', class: %w[test-class-1 test-class-2], data: { right: true })
+ end
+end
diff --git a/spec/frontend/fixtures/merge_requests.rb b/spec/frontend/fixtures/merge_requests.rb
index 68ed2ca2359..1eb48c0ce2c 100644
--- a/spec/frontend/fixtures/merge_requests.rb
+++ b/spec/frontend/fixtures/merge_requests.rb
@@ -7,7 +7,7 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type:
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') }
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
# rubocop: disable Layout/TrailingWhitespace
let(:description) do
@@ -119,6 +119,17 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type:
end
end
+ it 'merge_requests/merge_request_list.html' do
+ create(:merge_request, source_project: project, target_project: project)
+
+ get :index, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project
+ }
+
+ expect(response).to be_successful
+ end
+
private
def render_discussions_json(merge_request)
diff --git a/spec/frontend/fixtures/merge_requests_diffs.rb b/spec/frontend/fixtures/merge_requests_diffs.rb
index e733764f248..7f0d650b710 100644
--- a/spec/frontend/fixtures/merge_requests_diffs.rb
+++ b/spec/frontend/fixtures/merge_requests_diffs.rb
@@ -7,7 +7,7 @@ RSpec.describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)'
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') }
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project, description: '- [ ] Task List Item') }
let(:path) { "files/ruby/popen.rb" }
let(:position) do
diff --git a/spec/frontend/fixtures/pipeline_schedules.rb b/spec/frontend/fixtures/pipeline_schedules.rb
index 6389f59aa0a..e155d27920d 100644
--- a/spec/frontend/fixtures/pipeline_schedules.rb
+++ b/spec/frontend/fixtures/pipeline_schedules.rb
@@ -7,7 +7,7 @@ RSpec.describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', t
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :public, :repository) }
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: user) }
let!(:pipeline_schedule_populated) { create(:ci_pipeline_schedule, project: project, owner: user) }
let!(:pipeline_schedule_variable1) { create(:ci_pipeline_schedule_variable, key: 'foo', value: 'foovalue', pipeline_schedule: pipeline_schedule_populated) }
diff --git a/spec/frontend/fixtures/projects.rb b/spec/frontend/fixtures/projects.rb
index 3c8964d398a..fa7d61df3e8 100644
--- a/spec/frontend/fixtures/projects.rb
+++ b/spec/frontend/fixtures/projects.rb
@@ -12,7 +12,7 @@ RSpec.describe 'Projects (JavaScript fixtures)', type: :controller do
let(:project) { create(:project, namespace: namespace, path: 'builds-project', runners_token: runners_token, avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png')) }
let(:project_with_repo) { create(:project, :repository, description: 'Code and stuff', avatar: fixture_file_upload('spec/fixtures/dk.png', 'image/png')) }
let(:project_variable_populated) { create(:project, namespace: namespace, path: 'builds-project2', runners_token: runners_token) }
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
render_views
diff --git a/spec/frontend/fixtures/prometheus_service.rb b/spec/frontend/fixtures/prometheus_service.rb
index bbd938c66f6..aed73dc1096 100644
--- a/spec/frontend/fixtures/prometheus_service.rb
+++ b/spec/frontend/fixtures/prometheus_service.rb
@@ -8,7 +8,7 @@ RSpec.describe Projects::ServicesController, '(JavaScript fixtures)', type: :con
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
let!(:integration) { create(:prometheus_integration, project: project) }
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
render_views
diff --git a/spec/frontend/fixtures/runner.rb b/spec/frontend/fixtures/runner.rb
index 36e6cf72750..cdb4c3fd8ba 100644
--- a/spec/frontend/fixtures/runner.rb
+++ b/spec/frontend/fixtures/runner.rb
@@ -11,11 +11,13 @@ RSpec.describe 'Runner (JavaScript fixtures)' do
let_it_be(:admin) { create(:admin) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:project_2) { create(:project, :repository, :public) }
let_it_be(:instance_runner) { create(:ci_runner, :instance, version: '1.0.0', revision: '123', description: 'Instance runner', ip_address: '127.0.0.1') }
let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], active: false, version: '2.0.0', revision: '456', description: 'Group runner', ip_address: '127.0.0.1') }
let_it_be(:group_runner_2) { create(:ci_runner, :group, groups: [group], active: false, version: '2.0.0', revision: '456', description: 'Group runner 2', ip_address: '127.0.0.1') }
- let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project], active: false, version: '2.0.0', revision: '456', description: 'Project runner', ip_address: '127.0.0.1') }
+ let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project, project_2], active: false, version: '2.0.0', revision: '456', description: 'Project runner', ip_address: '127.0.0.1') }
+ let_it_be(:build) { create(:ci_build, runner: instance_runner) }
query_path = 'runner/graphql/'
fixtures_path = 'graphql/runner/'
@@ -78,6 +80,46 @@ RSpec.describe 'Runner (JavaScript fixtures)' do
expect_graphql_errors_to_be_empty
end
+
+ it "#{fixtures_path}#{get_runner_query_name}.with_group.json" do
+ post_graphql(query, current_user: admin, variables: {
+ id: group_runner.to_global_id.to_s
+ })
+
+ expect_graphql_errors_to_be_empty
+ end
+ end
+
+ describe GraphQL::Query, type: :request do
+ get_runner_projects_query_name = 'get_runner_projects.query.graphql'
+
+ let_it_be(:query) do
+ get_graphql_query_as_string("#{query_path}#{get_runner_projects_query_name}")
+ end
+
+ it "#{fixtures_path}#{get_runner_projects_query_name}.json" do
+ post_graphql(query, current_user: admin, variables: {
+ id: project_runner.to_global_id.to_s
+ })
+
+ expect_graphql_errors_to_be_empty
+ end
+ end
+
+ describe GraphQL::Query, type: :request do
+ get_runner_jobs_query_name = 'get_runner_jobs.query.graphql'
+
+ let_it_be(:query) do
+ get_graphql_query_as_string("#{query_path}#{get_runner_jobs_query_name}")
+ end
+
+ it "#{fixtures_path}#{get_runner_jobs_query_name}.json" do
+ post_graphql(query, current_user: admin, variables: {
+ id: instance_runner.to_global_id.to_s
+ })
+
+ expect_graphql_errors_to_be_empty
+ end
end
end
diff --git a/spec/frontend/fixtures/services.rb b/spec/frontend/fixtures/services.rb
index a8293a080a9..f0bb8fb962f 100644
--- a/spec/frontend/fixtures/services.rb
+++ b/spec/frontend/fixtures/services.rb
@@ -8,7 +8,7 @@ RSpec.describe Projects::ServicesController, '(JavaScript fixtures)', type: :con
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
let!(:service) { create(:custom_issue_tracker_integration, project: project) }
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
render_views
diff --git a/spec/frontend/fixtures/snippet.rb b/spec/frontend/fixtures/snippet.rb
index 397fb3e7124..f05ff3ee269 100644
--- a/spec/frontend/fixtures/snippet.rb
+++ b/spec/frontend/fixtures/snippet.rb
@@ -7,7 +7,7 @@ RSpec.describe SnippetsController, '(JavaScript fixtures)', type: :controller do
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
let(:snippet) { create(:personal_snippet, :public, title: 'snippet.md', content: '# snippet', file_name: 'snippet.md', author: user) }
render_views
diff --git a/spec/frontend/fixtures/tags.rb b/spec/frontend/fixtures/tags.rb
index 6cfa5f82efe..fdf85844ee2 100644
--- a/spec/frontend/fixtures/tags.rb
+++ b/spec/frontend/fixtures/tags.rb
@@ -6,7 +6,7 @@ RSpec.describe 'Tags (JavaScript fixtures)' do
include JavaScriptFixturesHelpers
let_it_be(:project) { create(:project, :repository, path: 'tags-project') }
- let_it_be(:user) { project.owner }
+ let_it_be(:user) { project.first_owner }
after(:all) do
remove_repository(project)
diff --git a/spec/frontend/fixtures/todos.rb b/spec/frontend/fixtures/todos.rb
index a0573b0b658..7dce09e8f49 100644
--- a/spec/frontend/fixtures/todos.rb
+++ b/spec/frontend/fixtures/todos.rb
@@ -7,7 +7,7 @@ RSpec.describe 'Todos (JavaScript fixtures)' do
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'todos-project') }
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
let(:issue_1) { create(:issue, title: 'issue_1', project: project) }
let!(:todo_1) { create(:todo, user: user, project: project, target: issue_1, created_at: 5.hours.ago) }
let(:issue_2) { create(:issue, title: 'issue_2', project: project) }
diff --git a/spec/frontend/flash_spec.js b/spec/frontend/flash_spec.js
index d5451ec2064..942e2c330fa 100644
--- a/spec/frontend/flash_spec.js
+++ b/spec/frontend/flash_spec.js
@@ -517,16 +517,12 @@ describe('Flash', () => {
`;
});
- it('removes global flash on click', (done) => {
+ it('removes global flash on click', () => {
addDismissFlashClickListener(el, false);
el.querySelector('.js-close-icon').click();
- setImmediate(() => {
- expect(document.querySelector('.flash')).toBeNull();
-
- done();
- });
+ expect(document.querySelector('.flash')).toBeNull();
});
});
diff --git a/spec/frontend/frequent_items/components/app_spec.js b/spec/frontend/frequent_items/components/app_spec.js
index a94cb3e2fcc..ba989bf53ab 100644
--- a/spec/frontend/frequent_items/components/app_spec.js
+++ b/spec/frontend/frequent_items/components/app_spec.js
@@ -1,5 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
@@ -97,7 +97,7 @@ describe('Frequent Items App Component', () => {
triggerDropdownOpen();
store.state[TEST_VUEX_MODULE].isLoadingItems = true;
- await wrapper.vm.$nextTick();
+ await nextTick();
const loading = findLoading();
@@ -119,7 +119,7 @@ describe('Frequent Items App Component', () => {
expect(findFrequentItems().length).toBe(1);
triggerDropdownOpen();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findFrequentItems().length).toBe(expectedResult.length);
expect(findFrequentItemsList().props()).toEqual({
@@ -135,7 +135,7 @@ describe('Frequent Items App Component', () => {
mock.onGet(/\/api\/v4\/projects.json(.*)$/).replyOnce(200, mockSearchedProjects.data);
setSearch('gitlab');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findLoading().exists()).toBe(true);
diff --git a/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js b/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js
index 5a05265afdc..8220ea16342 100644
--- a/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js
+++ b/spec/frontend/frequent_items/components/frequent_items_list_item_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import { trimText } from 'helpers/text_helper';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
@@ -8,8 +9,7 @@ import { createStore } from '~/frequent_items/store';
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
import { mockProject } from '../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('FrequentItemsListItemComponent', () => {
let wrapper;
@@ -40,7 +40,6 @@ describe('FrequentItemsListItemComponent', () => {
provide: {
vuexModule: 'frequentProjects',
},
- localVue,
});
};
diff --git a/spec/frontend/frequent_items/components/frequent_items_list_spec.js b/spec/frontend/frequent_items/components/frequent_items_list_spec.js
index c015914c991..beaab1913d0 100644
--- a/spec/frontend/frequent_items/components/frequent_items_list_spec.js
+++ b/spec/frontend/frequent_items/components/frequent_items_list_spec.js
@@ -1,12 +1,12 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import frequentItemsListComponent from '~/frequent_items/components/frequent_items_list.vue';
import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue';
import { createStore } from '~/frequent_items/store';
import { mockFrequentProjects } from '../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('FrequentItemsListComponent', () => {
let wrapper;
@@ -22,7 +22,6 @@ describe('FrequentItemsListComponent', () => {
matcher: 'lab',
...props,
},
- localVue,
provide: {
vuexModule: 'frequentProjects',
},
@@ -45,7 +44,7 @@ describe('FrequentItemsListComponent', () => {
wrapper.setProps({
items: mockFrequentProjects,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.isListEmpty).toBe(false);
});
@@ -64,7 +63,7 @@ describe('FrequentItemsListComponent', () => {
wrapper.setProps({
isFetchFailed: false,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.listEmptyMessage).toBe('Projects you visit often will appear here');
});
@@ -82,7 +81,7 @@ describe('FrequentItemsListComponent', () => {
wrapper.setProps({
isFetchFailed: false,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.listEmptyMessage).toBe('Sorry, no projects matched your search');
});
@@ -90,25 +89,23 @@ describe('FrequentItemsListComponent', () => {
});
describe('template', () => {
- it('should render component element with list of projects', () => {
+ it('should render component element with list of projects', async () => {
createComponent();
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.classes('frequent-items-list-container')).toBe(true);
- expect(wrapper.findAll({ ref: 'frequentItemsList' })).toHaveLength(1);
- expect(wrapper.findAll(frequentItemsListItemComponent)).toHaveLength(5);
- });
+ await nextTick();
+ expect(wrapper.classes('frequent-items-list-container')).toBe(true);
+ expect(wrapper.findAll({ ref: 'frequentItemsList' })).toHaveLength(1);
+ expect(wrapper.findAll(frequentItemsListItemComponent)).toHaveLength(5);
});
- it('should render component element with empty message', () => {
+ it('should render component element with empty message', async () => {
createComponent({
items: [],
});
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$el.querySelectorAll('li.section-empty')).toHaveLength(1);
- expect(wrapper.findAll(frequentItemsListItemComponent)).toHaveLength(0);
- });
+ await nextTick();
+ expect(wrapper.vm.$el.querySelectorAll('li.section-empty')).toHaveLength(1);
+ expect(wrapper.findAll(frequentItemsListItemComponent)).toHaveLength(0);
});
});
});
diff --git a/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js b/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js
index c9b7e0f3d13..d0a4cf70f5f 100644
--- a/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js
+++ b/spec/frontend/frequent_items/components/frequent_items_search_input_spec.js
@@ -1,12 +1,12 @@
import { GlSearchBoxByType } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import searchComponent from '~/frequent_items/components/frequent_items_search_input.vue';
import { createStore } from '~/frequent_items/store';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('FrequentItemsSearchInputComponent', () => {
let wrapper;
@@ -18,7 +18,6 @@ describe('FrequentItemsSearchInputComponent', () => {
shallowMount(searchComponent, {
store,
propsData: { namespace },
- localVue,
provide: {
vuexModule: 'frequentProjects',
},
@@ -62,7 +61,7 @@ describe('FrequentItemsSearchInputComponent', () => {
findSearchBoxByType().vm.$emit('input', value);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'type_search_query', {
label: 'projects_dropdown_frequent_items_search_input',
diff --git a/spec/frontend/gl_form_spec.js b/spec/frontend/gl_form_spec.js
index 07487fbb60e..ab5627ce216 100644
--- a/spec/frontend/gl_form_spec.js
+++ b/spec/frontend/gl_form_spec.js
@@ -8,7 +8,7 @@ describe('GLForm', () => {
const testContext = {};
describe('when instantiated', () => {
- beforeEach((done) => {
+ beforeEach(() => {
window.gl = window.gl || {};
testContext.form = $('<form class="gfm-form"><textarea class="js-gfm-input"></form>');
@@ -18,22 +18,11 @@ describe('GLForm', () => {
jest.spyOn($.prototype, 'css').mockImplementation(() => {});
testContext.glForm = new GLForm(testContext.form, false);
-
- setImmediate(() => {
- $.prototype.off.mockClear();
- $.prototype.on.mockClear();
- $.prototype.css.mockClear();
- done();
- });
});
describe('setupAutosize', () => {
- beforeEach((done) => {
+ beforeEach(() => {
testContext.glForm.setupAutosize();
-
- setImmediate(() => {
- done();
- });
});
it('should register an autosize event handler on the textarea', () => {
diff --git a/spec/frontend/google_cloud/components/app_spec.js b/spec/frontend/google_cloud/components/app_spec.js
index 92bc7596f7d..5ddc0ffa50f 100644
--- a/spec/frontend/google_cloud/components/app_spec.js
+++ b/spec/frontend/google_cloud/components/app_spec.js
@@ -24,8 +24,8 @@ const HOME_PROPS = {
serviceAccounts: [{}, {}],
createServiceAccountUrl: '#url-create-service-account',
emptyIllustrationUrl: '#url-empty-illustration',
- deploymentsCloudRunUrl: '#url-deployments-cloud-run',
- deploymentsCloudStorageUrl: '#deploymentsCloudStorageUrl',
+ enableCloudRunUrl: '#url-enable-cloud-run',
+ enableCloudStorageUrl: '#enableCloudStorageUrl',
};
describe('google_cloud App component', () => {
diff --git a/spec/frontend/google_cloud/components/deployments_service_table_spec.js b/spec/frontend/google_cloud/components/deployments_service_table_spec.js
index 76c3bfd00a8..882376547c4 100644
--- a/spec/frontend/google_cloud/components/deployments_service_table_spec.js
+++ b/spec/frontend/google_cloud/components/deployments_service_table_spec.js
@@ -12,8 +12,8 @@ describe('google_cloud DeploymentsServiceTable component', () => {
beforeEach(() => {
const propsData = {
- cloudRunUrl: '#url-deployments-cloud-run',
- cloudStorageUrl: '#url-deployments-cloud-storage',
+ cloudRunUrl: '#url-enable-cloud-run',
+ cloudStorageUrl: '#url-enable-cloud-storage',
};
wrapper = mount(DeploymentsServiceTable, { propsData });
});
@@ -29,12 +29,13 @@ describe('google_cloud DeploymentsServiceTable component', () => {
it('should contain configure cloud run button', () => {
const cloudRunButton = findCloudRunButton();
expect(cloudRunButton.exists()).toBe(true);
- expect(cloudRunButton.props().disabled).toBe(true);
+ expect(cloudRunButton.attributes('href')).toBe('#url-enable-cloud-run');
});
it('should contain configure cloud storage button', () => {
const cloudStorageButton = findCloudStorageButton();
expect(cloudStorageButton.exists()).toBe(true);
expect(cloudStorageButton.props().disabled).toBe(true);
+ expect(cloudStorageButton.attributes('href')).toBe('#url-enable-cloud-storage');
});
});
diff --git a/spec/frontend/google_cloud/components/home_spec.js b/spec/frontend/google_cloud/components/home_spec.js
index 3a009fc88ce..57cf831b19b 100644
--- a/spec/frontend/google_cloud/components/home_spec.js
+++ b/spec/frontend/google_cloud/components/home_spec.js
@@ -20,8 +20,8 @@ describe('google_cloud Home component', () => {
serviceAccounts: [{}, {}],
createServiceAccountUrl: '#url-create-service-account',
emptyIllustrationUrl: '#url-empty-illustration',
- deploymentsCloudRunUrl: '#url-deployments-cloud-run',
- deploymentsCloudStorageUrl: '#deploymentsCloudStorageUrl',
+ enableCloudRunUrl: '#url-enable-cloud-run',
+ enableCloudStorageUrl: '#enableCloudStorageUrl',
};
beforeEach(() => {
diff --git a/spec/frontend/google_cloud/components/service_accounts_form_spec.js b/spec/frontend/google_cloud/components/service_accounts_form_spec.js
index 5394d0cdaef..7262e12c84d 100644
--- a/spec/frontend/google_cloud/components/service_accounts_form_spec.js
+++ b/spec/frontend/google_cloud/components/service_accounts_form_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { GlButton, GlFormGroup, GlFormSelect } from '@gitlab/ui';
+import { GlButton, GlFormGroup, GlFormSelect, GlFormCheckbox } from '@gitlab/ui';
import ServiceAccountsForm from '~/google_cloud/components/service_accounts_form.vue';
describe('ServiceAccountsForm component', () => {
@@ -9,11 +9,12 @@ describe('ServiceAccountsForm component', () => {
const findAllFormGroups = () => wrapper.findAllComponents(GlFormGroup);
const findAllFormSelects = () => wrapper.findAllComponents(GlFormSelect);
const findAllButtons = () => wrapper.findAllComponents(GlButton);
+ const findCheckbox = () => wrapper.findComponent(GlFormCheckbox);
const propsData = { gcpProjects: [], environments: [], cancelPath: '#cancel-url' };
beforeEach(() => {
- wrapper = shallowMount(ServiceAccountsForm, { propsData });
+ wrapper = shallowMount(ServiceAccountsForm, { propsData, stubs: { GlFormCheckbox } });
});
afterEach(() => {
@@ -35,8 +36,8 @@ describe('ServiceAccountsForm component', () => {
});
it('contains Environments form group', () => {
- const formGorup = findAllFormGroups().at(1);
- expect(formGorup.exists()).toBe(true);
+ const formGroup = findAllFormGroups().at(1);
+ expect(formGroup.exists()).toBe(true);
});
it('contains Environments dropdown', () => {
@@ -56,4 +57,14 @@ describe('ServiceAccountsForm component', () => {
expect(button.text()).toBe(ServiceAccountsForm.i18n.cancelLabel);
expect(button.attributes('href')).toBe('#cancel-url');
});
+
+ it('contains Confirmation checkbox', () => {
+ const checkbox = findCheckbox();
+ expect(checkbox.text()).toBe(ServiceAccountsForm.i18n.checkboxLabel);
+ });
+
+ it('checkbox must be required', () => {
+ const checkbox = findCheckbox();
+ expect(checkbox.attributes('required')).toBe('true');
+ });
});
diff --git a/spec/frontend/google_cloud/components/service_accounts_list_spec.js b/spec/frontend/google_cloud/components/service_accounts_list_spec.js
index cdb3f74051c..f7051c8a53d 100644
--- a/spec/frontend/google_cloud/components/service_accounts_list_spec.js
+++ b/spec/frontend/google_cloud/components/service_accounts_list_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import { GlButton, GlEmptyState, GlTable } from '@gitlab/ui';
+import { GlAlert, GlButton, GlEmptyState, GlTable } from '@gitlab/ui';
import ServiceAccountsList from '~/google_cloud/components/service_accounts_list.vue';
describe('ServiceAccounts component', () => {
@@ -28,7 +28,7 @@ describe('ServiceAccounts component', () => {
it('shows the link to create new service accounts', () => {
const button = findButtonInEmptyState();
expect(button.exists()).toBe(true);
- expect(button.text()).toBe('Create service account');
+ expect(button.text()).toBe(ServiceAccountsList.i18n.createServiceAccount);
expect(button.attributes('href')).toBe('#create-url');
});
});
@@ -41,6 +41,7 @@ describe('ServiceAccounts component', () => {
const findTable = () => wrapper.findComponent(GlTable);
const findRows = () => findTable().findAll('tr');
const findButton = () => wrapper.findComponent(GlButton);
+ const findSecretManagerTip = () => wrapper.findComponent(GlAlert);
beforeEach(() => {
const propsData = {
@@ -52,13 +53,11 @@ describe('ServiceAccounts component', () => {
});
it('shows the title', () => {
- expect(findTitle().text()).toBe('Service Accounts');
+ expect(findTitle().text()).toBe(ServiceAccountsList.i18n.serviceAccountsTitle);
});
it('shows the description', () => {
- expect(findDescription().text()).toBe(
- 'Service Accounts keys authorize GitLab to deploy your Google Cloud project',
- );
+ expect(findDescription().text()).toBe(ServiceAccountsList.i18n.serviceAccountsDescription);
});
it('shows the table', () => {
@@ -72,8 +71,14 @@ describe('ServiceAccounts component', () => {
it('shows the link to create new service accounts', () => {
const button = findButton();
expect(button.exists()).toBe(true);
- expect(button.text()).toBe('Create service account');
+ expect(button.text()).toBe(ServiceAccountsList.i18n.createServiceAccount);
expect(button.attributes('href')).toBe('#create-url');
});
+
+ it('must contain secret managers tip', () => {
+ const tip = findSecretManagerTip();
+ const expectedText = ServiceAccountsList.i18n.secretManagersDescription.substr(0, 48);
+ expect(tip.text()).toContain(expectedText);
+ });
});
});
diff --git a/spec/frontend/google_tag_manager/index_spec.js b/spec/frontend/google_tag_manager/index_spec.js
index ff38de28da6..9112b0e17e7 100644
--- a/spec/frontend/google_tag_manager/index_spec.js
+++ b/spec/frontend/google_tag_manager/index_spec.js
@@ -1,4 +1,5 @@
import { merge } from 'lodash';
+import { v4 as uuidv4 } from 'uuid';
import {
trackFreeTrialAccountSubmissions,
trackNewRegistrations,
@@ -8,11 +9,14 @@ import {
trackSaasTrialProject,
trackSaasTrialProjectImport,
trackSaasTrialGetStarted,
+ trackCheckout,
+ trackTransaction,
} from '~/google_tag_manager';
import { setHTMLFixture } from 'helpers/fixtures';
import { logError } from '~/lib/logger';
jest.mock('~/lib/logger');
+jest.mock('uuid');
describe('~/google_tag_manager/index', () => {
let spy;
@@ -209,6 +213,180 @@ describe('~/google_tag_manager/index', () => {
expect(spy).toHaveBeenCalledWith({ event: 'saasTrialSubmit' });
expect(logError).not.toHaveBeenCalled();
});
+
+ describe('when trackCheckout is invoked', () => {
+ it('with selectedPlan: 2c92a00d76f0d5060176f2fb0a5029ff', () => {
+ expect(spy).not.toHaveBeenCalled();
+
+ trackCheckout('2c92a00d76f0d5060176f2fb0a5029ff', 1);
+
+ expect(spy.mock.calls.flatMap((x) => x)).toEqual([
+ { ecommerce: null },
+ {
+ event: 'EECCheckout',
+ ecommerce: {
+ currencyCode: 'USD',
+ checkout: {
+ actionField: { step: 1 },
+ products: [
+ {
+ brand: 'GitLab',
+ category: 'DevOps',
+ id: '0002',
+ name: 'Premium',
+ price: '228',
+ quantity: 1,
+ variant: 'SaaS',
+ },
+ ],
+ },
+ },
+ },
+ ]);
+ });
+
+ it('with selectedPlan: 2c92a0ff76f0d5250176f2f8c86f305a', () => {
+ expect(spy).not.toHaveBeenCalled();
+
+ trackCheckout('2c92a0ff76f0d5250176f2f8c86f305a', 1);
+
+ expect(spy).toHaveBeenCalledTimes(2);
+ expect(spy).toHaveBeenCalledWith({ ecommerce: null });
+ expect(spy).toHaveBeenCalledWith({
+ event: 'EECCheckout',
+ ecommerce: {
+ currencyCode: 'USD',
+ checkout: {
+ actionField: { step: 1 },
+ products: [
+ {
+ brand: 'GitLab',
+ category: 'DevOps',
+ id: '0001',
+ name: 'Ultimate',
+ price: '1188',
+ quantity: 1,
+ variant: 'SaaS',
+ },
+ ],
+ },
+ },
+ });
+ });
+
+ it('with selectedPlan: Something else', () => {
+ expect(spy).not.toHaveBeenCalled();
+
+ trackCheckout('Something else', 1);
+
+ expect(spy).not.toHaveBeenCalled();
+ });
+
+ it('with a different number of users', () => {
+ expect(spy).not.toHaveBeenCalled();
+
+ trackCheckout('2c92a0ff76f0d5250176f2f8c86f305a', 5);
+
+ expect(spy).toHaveBeenCalledTimes(2);
+ expect(spy).toHaveBeenCalledWith({ ecommerce: null });
+ expect(spy).toHaveBeenCalledWith({
+ event: 'EECCheckout',
+ ecommerce: {
+ currencyCode: 'USD',
+ checkout: {
+ actionField: { step: 1 },
+ products: [
+ {
+ brand: 'GitLab',
+ category: 'DevOps',
+ id: '0001',
+ name: 'Ultimate',
+ price: '1188',
+ quantity: 5,
+ variant: 'SaaS',
+ },
+ ],
+ },
+ },
+ });
+ });
+ });
+
+ describe('when trackTransactions is invoked', () => {
+ describe.each([
+ {
+ selectedPlan: '2c92a00d76f0d5060176f2fb0a5029ff',
+ revenue: 228,
+ name: 'Premium',
+ id: '0002',
+ },
+ {
+ selectedPlan: '2c92a0ff76f0d5250176f2f8c86f305a',
+ revenue: 1188,
+ name: 'Ultimate',
+ id: '0001',
+ },
+ ])('with %o', (planObject) => {
+ it('invokes pushes a new event that references the selected plan', () => {
+ const { selectedPlan, revenue, name, id } = planObject;
+
+ expect(spy).not.toHaveBeenCalled();
+ uuidv4.mockImplementationOnce(() => '123');
+
+ const transactionDetails = {
+ paymentOption: 'visa',
+ revenue,
+ tax: 10,
+ selectedPlan,
+ quantity: 1,
+ };
+
+ trackTransaction(transactionDetails);
+
+ expect(spy.mock.calls.flatMap((x) => x)).toEqual([
+ { ecommerce: null },
+ {
+ event: 'EECtransactionSuccess',
+ ecommerce: {
+ currencyCode: 'USD',
+ purchase: {
+ actionField: {
+ id: '123',
+ affiliation: 'GitLab',
+ option: 'visa',
+ revenue: revenue.toString(),
+ tax: '10',
+ },
+ products: [
+ {
+ brand: 'GitLab',
+ category: 'DevOps',
+ id,
+ name,
+ price: revenue.toString(),
+ quantity: 1,
+ variant: 'SaaS',
+ },
+ ],
+ },
+ },
+ },
+ ]);
+ });
+ });
+ });
+
+ describe('when trackTransaction is invoked', () => {
+ describe('with an invalid plan object', () => {
+ it('does not get called', () => {
+ expect(spy).not.toHaveBeenCalled();
+
+ trackTransaction({ selectedPlan: 'notAplan' });
+
+ expect(spy).not.toHaveBeenCalled();
+ });
+ });
+ });
});
describe.each([
diff --git a/spec/frontend/grafana_integration/components/grafana_integration_spec.js b/spec/frontend/grafana_integration/components/grafana_integration_spec.js
index d5338430054..d2111194097 100644
--- a/spec/frontend/grafana_integration/components/grafana_integration_spec.js
+++ b/spec/frontend/grafana_integration/components/grafana_integration_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 'helpers/test_constants';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import createFlash from '~/flash';
@@ -93,29 +94,28 @@ describe('grafana integration component', () => {
},
];
- it('submits form on click', () => {
+ it('submits form on click', async () => {
axios.patch.mockResolvedValue();
findSubmitButton(wrapper).trigger('click');
expect(axios.patch).toHaveBeenCalledWith(...endpointRequest);
- return wrapper.vm.$nextTick().then(() => expect(refreshCurrentPage).toHaveBeenCalled());
+ await nextTick();
+ expect(refreshCurrentPage).toHaveBeenCalled();
});
- it('creates flash banner on error', () => {
+ it('creates flash banner on error', async () => {
const message = 'mockErrorMessage';
axios.patch.mockRejectedValue({ response: { data: { message } } });
findSubmitButton().trigger('click');
expect(axios.patch).toHaveBeenCalledWith(...endpointRequest);
- return wrapper.vm
- .$nextTick()
- .then(jest.runAllTicks)
- .then(() =>
- expect(createFlash).toHaveBeenCalledWith({
- message: `There was an error saving your changes. ${message}`,
- }),
- );
+
+ await nextTick();
+ await jest.runAllTicks();
+ expect(createFlash).toHaveBeenCalledWith({
+ message: `There was an error saving your changes. ${message}`,
+ });
});
});
});
diff --git a/spec/frontend/group_settings/components/shared_runners_form_spec.js b/spec/frontend/group_settings/components/shared_runners_form_spec.js
index 617d91178e4..26e9cd39cfd 100644
--- a/spec/frontend/group_settings/components/shared_runners_form_spec.js
+++ b/spec/frontend/group_settings/components/shared_runners_form_spec.js
@@ -1,6 +1,7 @@
import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAxiosAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import SharedRunnersForm from '~/group_settings/components/shared_runners_form.vue';
import axios from '~/lib/utils/axios_utils';
@@ -76,7 +77,7 @@ describe('group_settings/components/shared_runners_form', () => {
findEnabledToggle().vm.$emit('change', true);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(isLoadingIconVisible()).toBe(true);
diff --git a/spec/frontend/groups/components/app_spec.js b/spec/frontend/groups/components/app_spec.js
index bc8c6460cf4..848e50c86ba 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', () => {
@@ -280,6 +280,7 @@ describe('AppComponent', () => {
expect(vm.targetParentGroup).toBe(null);
vm.showLeaveGroupModal(group, mockParentGroupItem);
+ expect(vm.isModalVisible).toBe(true);
expect(vm.targetGroup).not.toBe(null);
expect(vm.targetParentGroup).not.toBe(null);
});
@@ -290,6 +291,7 @@ describe('AppComponent', () => {
expect(vm.groupLeaveConfirmationMessage).toBe('');
vm.showLeaveGroupModal(group, mockParentGroupItem);
+ expect(vm.isModalVisible).toBe(true);
expect(vm.groupLeaveConfirmationMessage).toBe(
`Are you sure you want to leave the "${group.fullName}" group?`,
);
@@ -397,66 +399,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/group_folder_spec.js b/spec/frontend/groups/components/group_folder_spec.js
index 1d8e10479b6..98b7c2dd6c6 100644
--- a/spec/frontend/groups/components/group_folder_spec.js
+++ b/spec/frontend/groups/components/group_folder_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import groupFolderComponent from '~/groups/components/group_folder.vue';
import groupItemComponent from '~/groups/components/group_item.vue';
@@ -18,13 +18,13 @@ const createComponent = (groups = mockGroups, parentGroup = mockParentGroupItem)
describe('GroupFolderComponent', () => {
let vm;
- beforeEach(() => {
+ beforeEach(async () => {
Vue.component('GroupItem', groupItemComponent);
vm = createComponent();
vm.$mount();
- return Vue.nextTick();
+ await nextTick();
});
afterEach(() => {
diff --git a/spec/frontend/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/groups/components/invite_members_banner_spec.js b/spec/frontend/groups/components/invite_members_banner_spec.js
index c81edad499c..1924f400861 100644
--- a/spec/frontend/groups/components/invite_members_banner_spec.js
+++ b/spec/frontend/groups/components/invite_members_banner_spec.js
@@ -1,6 +1,7 @@
import { GlBanner } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import InviteMembersBanner from '~/groups/components/invite_members_banner.vue';
import eventHub from '~/invite_members/event_hub';
@@ -75,7 +76,6 @@ describe('InviteMembersBanner', () => {
it('calls openModal through the eventHub', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('openModal', {
- inviteeType: 'members',
source: 'invite_members_banner',
});
});
@@ -140,7 +140,7 @@ describe('InviteMembersBanner', () => {
expect(wrapper.find(GlBanner).exists()).toBe(true);
wrapper.find(GlBanner).vm.$emit('close');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlBanner).exists()).toBe(false);
});
});
diff --git a/spec/frontend/groups/components/item_actions_spec.js b/spec/frontend/groups/components/item_actions_spec.js
index ffbdf9b1aa6..3ceb038dd3c 100644
--- a/spec/frontend/groups/components/item_actions_spec.js
+++ b/spec/frontend/groups/components/item_actions_spec.js
@@ -1,4 +1,4 @@
-import { shallowMount } from '@vue/test-utils';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ItemActions from '~/groups/components/item_actions.vue';
import eventHub from '~/groups/event_hub';
import { mockParentGroupItem, mockChildren } from '../mock_data';
@@ -13,7 +13,7 @@ describe('ItemActions', () => {
};
const createComponent = (props = {}) => {
- wrapper = shallowMount(ItemActions, {
+ wrapper = shallowMountExtended(ItemActions, {
propsData: { ...defaultProps, ...props },
});
};
@@ -23,8 +23,10 @@ describe('ItemActions', () => {
wrapper = null;
});
- const findEditGroupBtn = () => wrapper.find('[data-testid="edit-group-btn"]');
- const findLeaveGroupBtn = () => wrapper.find('[data-testid="leave-group-btn"]');
+ const findEditGroupBtn = () => wrapper.findByTestId(`edit-group-${mockParentGroupItem.id}-btn`);
+ const findLeaveGroupBtn = () => wrapper.findByTestId(`leave-group-${mockParentGroupItem.id}-btn`);
+ const findRemoveGroupBtn = () =>
+ wrapper.findByTestId(`remove-group-${mockParentGroupItem.id}-btn`);
describe('template', () => {
let group;
@@ -34,6 +36,7 @@ describe('ItemActions', () => {
...mockParentGroupItem,
canEdit: true,
canLeave: true,
+ canRemove: true,
};
createComponent({ group });
});
@@ -41,21 +44,21 @@ describe('ItemActions', () => {
it('renders component template correctly', () => {
createComponent();
- expect(wrapper.classes()).toContain('controls');
+ expect(wrapper.classes()).toContain('gl-display-flex', 'gl-justify-content-end', 'gl-ml-5');
});
- it('renders "Edit group" button with correct attribute values', () => {
+ it('renders "Edit" group button with correct attribute values', () => {
const button = findEditGroupBtn();
expect(button.exists()).toBe(true);
- expect(button.props('icon')).toBe('pencil');
- expect(button.attributes('aria-label')).toBe('Edit group');
+ expect(button.attributes('href')).toBe(mockParentGroupItem.editPath);
});
- it('renders "Leave this group" button with correct attribute values', () => {
- const button = findLeaveGroupBtn();
+ it('renders "Delete" group button with correct attribute values', () => {
+ const button = findRemoveGroupBtn();
expect(button.exists()).toBe(true);
- expect(button.props('icon')).toBe('leave');
- expect(button.attributes('aria-label')).toBe('Leave this group');
+ expect(button.attributes('href')).toBe(
+ `${mockParentGroupItem.editPath}#js-remove-group-form`,
+ );
});
it('emits `showLeaveGroupModal` event in the event hub', () => {
@@ -103,4 +106,15 @@ describe('ItemActions', () => {
expect(findEditGroupBtn().exists()).toBe(false);
});
+
+ it('does not render delete button if group can not be edited', () => {
+ createComponent({
+ group: {
+ ...mockParentGroupItem,
+ canRemove: false,
+ },
+ });
+
+ expect(findRemoveGroupBtn().exists()).toBe(false);
+ });
});
diff --git a/spec/frontend/groups/components/transfer_group_form_spec.js b/spec/frontend/groups/components/transfer_group_form_spec.js
new file mode 100644
index 00000000000..6dc760f4f7c
--- /dev/null
+++ b/spec/frontend/groups/components/transfer_group_form_spec.js
@@ -0,0 +1,131 @@
+import { GlAlert, GlSprintf } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import Component from '~/groups/components/transfer_group_form.vue';
+import ConfirmDanger from '~/vue_shared/components/confirm_danger/confirm_danger.vue';
+import NamespaceSelect from '~/vue_shared/components/namespace_select/namespace_select.vue';
+
+describe('Transfer group form', () => {
+ let wrapper;
+
+ const confirmButtonText = 'confirm';
+ const confirmationPhrase = 'confirmation-phrase';
+ const paidGroupHelpLink = 'some/fake/link';
+ const groupNamespaces = [
+ {
+ id: 1,
+ humanName: 'Group 1',
+ },
+ {
+ id: 2,
+ humanName: 'Group 2',
+ },
+ ];
+
+ const defaultProps = {
+ groupNamespaces,
+ paidGroupHelpLink,
+ isPaidGroup: false,
+ confirmationPhrase,
+ confirmButtonText,
+ };
+
+ const createComponent = (propsData = {}) =>
+ shallowMountExtended(Component, {
+ propsData: {
+ ...defaultProps,
+ ...propsData,
+ },
+ stubs: { GlSprintf },
+ });
+
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findConfirmDanger = () => wrapper.findComponent(ConfirmDanger);
+ const findNamespaceSelect = () => wrapper.findComponent(NamespaceSelect);
+ const findHiddenInput = () => wrapper.find('[name="new_parent_group_id"]');
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('default', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ it('renders the namespace select component', () => {
+ expect(findNamespaceSelect().exists()).toBe(true);
+ });
+
+ it('sets the namespace select properties', () => {
+ expect(findNamespaceSelect().props()).toMatchObject({
+ defaultText: 'Select parent group',
+ fullWidth: false,
+ includeHeaders: false,
+ emptyNamespaceTitle: 'No parent group',
+ includeEmptyNamespace: true,
+ groupNamespaces,
+ });
+ });
+
+ it('renders the hidden input field', () => {
+ expect(findHiddenInput().exists()).toBe(true);
+ expect(findHiddenInput().attributes('value')).toBeUndefined();
+ });
+
+ it('does not render the alert message', () => {
+ expect(findAlert().exists()).toBe(false);
+ });
+
+ it('renders the confirm danger component', () => {
+ expect(findConfirmDanger().exists()).toBe(true);
+ });
+
+ it('sets the confirm danger properties', () => {
+ expect(findConfirmDanger().props()).toMatchObject({
+ buttonClass: 'qa-transfer-button',
+ disabled: true,
+ buttonText: confirmButtonText,
+ phrase: confirmationPhrase,
+ });
+ });
+ });
+
+ describe('with a selected project', () => {
+ const [firstGroup] = groupNamespaces;
+ beforeEach(() => {
+ wrapper = createComponent();
+ findNamespaceSelect().vm.$emit('select', firstGroup);
+ });
+
+ it('sets the confirm danger disabled property to false', () => {
+ expect(findConfirmDanger().props()).toMatchObject({ disabled: false });
+ });
+
+ it('sets the hidden input field', () => {
+ expect(findHiddenInput().exists()).toBe(true);
+ expect(parseInt(findHiddenInput().attributes('value'), 10)).toBe(firstGroup.id);
+ });
+
+ it('emits "confirm" event when the danger modal is confirmed', () => {
+ expect(wrapper.emitted('confirm')).toBeUndefined();
+
+ findConfirmDanger().vm.$emit('confirm');
+
+ expect(wrapper.emitted('confirm')).toHaveLength(1);
+ });
+ });
+
+ describe('isPaidGroup = true', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ isPaidGroup: true });
+ });
+
+ it('disables the transfer button', () => {
+ expect(findConfirmDanger().props()).toMatchObject({ disabled: true });
+ });
+
+ it('hides the namespace selector button', () => {
+ expect(findNamespaceSelect().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/groups/landing_spec.js b/spec/frontend/groups/landing_spec.js
index f90f541eb96..d60adea202b 100644
--- a/spec/frontend/groups/landing_spec.js
+++ b/spec/frontend/groups/landing_spec.js
@@ -159,7 +159,10 @@ describe('Landing', () => {
});
it('should call Cookies.set', () => {
- expect(Cookies.set).toHaveBeenCalledWith(test.cookieName, 'true', { expires: 365 });
+ expect(Cookies.set).toHaveBeenCalledWith(test.cookieName, 'true', {
+ expires: 365,
+ secure: false,
+ });
});
});
diff --git a/spec/frontend/groups/transfer_edit_spec.js b/spec/frontend/groups/transfer_edit_spec.js
deleted file mode 100644
index bc070920d02..00000000000
--- a/spec/frontend/groups/transfer_edit_spec.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import $ from 'jquery';
-
-import { loadHTMLFixture } from 'helpers/fixtures';
-import setupTransferEdit from '~/groups/transfer_edit';
-
-describe('setupTransferEdit', () => {
- const formSelector = '.js-group-transfer-form';
- const targetSelector = '#new_parent_group_id';
-
- beforeEach(() => {
- loadHTMLFixture('groups/edit.html');
- setupTransferEdit(formSelector, targetSelector);
- });
-
- it('disables submit button on load', () => {
- expect($(formSelector).find(':submit').prop('disabled')).toBe(true);
- });
-
- it('enables submit button when selection changes to non-empty value', () => {
- const lastValue = $(formSelector).find(targetSelector).find('.dropdown-content li').last();
- $(formSelector).find(targetSelector).val(lastValue).trigger('change');
-
- expect($(formSelector).find(':submit').prop('disabled')).toBeFalsy();
- });
-
- it('disables submit button when selection changes to empty value', () => {
- $(formSelector).find(targetSelector).val('').trigger('change');
-
- expect($(formSelector).find(':submit').prop('disabled')).toBe(true);
- });
-});
diff --git a/spec/frontend/header_search/components/app_spec.js b/spec/frontend/header_search/components/app_spec.js
index 3200c6614f1..dcbeeeffb2d 100644
--- a/spec/frontend/header_search/components/app_spec.js
+++ b/spec/frontend/header_search/components/app_spec.js
@@ -1,5 +1,5 @@
import { GlSearchBoxByType } from '@gitlab/ui';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import HeaderSearchApp from '~/header_search/components/app.vue';
@@ -202,7 +202,7 @@ describe('HeaderSearchApp', () => {
expect(findHeaderSearchDropdown().exists()).toBe(false);
findHeaderSearchInput().vm.$emit('focus');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findHeaderSearchDropdown().exists()).toBe(true);
});
@@ -211,7 +211,7 @@ describe('HeaderSearchApp', () => {
expect(findHeaderSearchDropdown().exists()).toBe(false);
findHeaderSearchInput().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findHeaderSearchDropdown().exists()).toBe(true);
});
@@ -265,7 +265,7 @@ describe('HeaderSearchApp', () => {
expect(findHeaderSearchDropdown().exists()).toBe(true);
findDropdownKeyboardNavigation().vm.$emit('tab');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findHeaderSearchDropdown().exists()).toBe(false);
});
@@ -284,7 +284,7 @@ describe('HeaderSearchApp', () => {
it(`when currentFocusIndex changes to ${MOCK_INDEX} updates the data to searchOptions[${MOCK_INDEX}]`, async () => {
findDropdownKeyboardNavigation().vm.$emit('change', MOCK_INDEX);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.currentFocusedOption).toBe(MOCK_DEFAULT_SEARCH_OPTIONS[MOCK_INDEX]);
});
});
@@ -299,7 +299,7 @@ describe('HeaderSearchApp', () => {
it('onKey-enter submits a search', async () => {
findHeaderSearchInput().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY }));
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(visitUrl).toHaveBeenCalledWith(MOCK_SEARCH_QUERY);
});
@@ -316,7 +316,7 @@ describe('HeaderSearchApp', () => {
it('onKey-enter clicks the selected dropdown item rather than submitting a search', async () => {
findDropdownKeyboardNavigation().vm.$emit('change', MOCK_INDEX);
- await wrapper.vm.$nextTick();
+ await nextTick();
findHeaderSearchInput().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY }));
expect(visitUrl).toHaveBeenCalledWith(MOCK_DEFAULT_SEARCH_OPTIONS[MOCK_INDEX].url);
});
diff --git a/spec/frontend/header_search/components/header_search_autocomplete_items_spec.js b/spec/frontend/header_search/components/header_search_autocomplete_items_spec.js
index bec0cbc8a5c..502f10ff771 100644
--- a/spec/frontend/header_search/components/header_search_autocomplete_items_spec.js
+++ b/spec/frontend/header_search/components/header_search_autocomplete_items_spec.js
@@ -1,6 +1,6 @@
import { GlDropdownItem, GlLoadingIcon, GlAvatar } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import HeaderSearchAutocompleteItems from '~/header_search/components/header_search_autocomplete_items.vue';
import {
@@ -143,7 +143,7 @@ describe('HeaderSearchAutocompleteItems', () => {
wrapper.setProps({ currentFocusedOption: MOCK_SORTED_AUTOCOMPLETE_OPTIONS[0] });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(scrollSpy).toHaveBeenCalledWith(false);
scrollSpy.mockRestore();
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/branches/search_list_spec.js b/spec/frontend/ide/components/branches/search_list_spec.js
index 0efa7af2c6c..b6e3274153a 100644
--- a/spec/frontend/ide/components/branches/search_list_spec.js
+++ b/spec/frontend/ide/components/branches/search_list_spec.js
@@ -1,13 +1,13 @@
import { GlLoadingIcon } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import Item from '~/ide/components/branches/item.vue';
import List from '~/ide/components/branches/search_list.vue';
import { __ } from '~/locale';
import { branches } from '../../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('IDE branches search list', () => {
let wrapper;
@@ -31,7 +31,6 @@ describe('IDE branches search list', () => {
});
wrapper = shallowMount(List, {
- localVue,
store: fakeStore,
});
};
@@ -51,13 +50,12 @@ describe('IDE branches search list', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
- it('renders branches not found when search is not empty and branches list is empty', () => {
+ it('renders branches not found when search is not empty and branches list is empty', async () => {
createComponent({ branches: [] });
wrapper.find('input[type="search"]').setValue('something');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.text()).toContain(__('No branches found'));
- });
+ await nextTick();
+ expect(wrapper.text()).toContain(__('No branches found'));
});
describe('with branches', () => {
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/editor_header_spec.js b/spec/frontend/ide/components/commit_sidebar/editor_header_spec.js
index 50635ffe894..6e4c66cb780 100644
--- a/spec/frontend/ide/components/commit_sidebar/editor_header_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/editor_header_spec.js
@@ -1,11 +1,11 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import EditorHeader from '~/ide/components/commit_sidebar/editor_header.vue';
import { createStore } from '~/ide/stores';
import { file } from '../../helpers';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
const TEST_FILE_PATH = 'test/file/path';
@@ -16,7 +16,6 @@ describe('IDE commit editor header', () => {
const createComponent = (fileProps = {}) => {
wrapper = mount(EditorHeader, {
store,
- localVue,
propsData: {
activeFile: {
...file(TEST_FILE_PATH),
diff --git a/spec/frontend/ide/components/commit_sidebar/form_spec.js b/spec/frontend/ide/components/commit_sidebar/form_spec.js
index 83d1bbb842e..d3b2923ac6c 100644
--- a/spec/frontend/ide/components/commit_sidebar/form_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/form_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 { stubComponent } from 'helpers/stub_component';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import waitForPromises from 'helpers/wait_for_promises';
@@ -56,7 +56,6 @@ describe('IDE commit form', () => {
disabled: findCommitButton().props('disabled'),
tooltip: getBinding(findCommitButtonTooltip().element, 'gl-tooltip').value.title,
});
- const clickCommitButton = () => findCommitButton().vm.$emit('click');
const findForm = () => wrapper.find('form');
const submitForm = () => findForm().trigger('submit');
const findCommitMessageInput = () => wrapper.find(CommitMessageField);
@@ -98,7 +97,7 @@ describe('IDE commit form', () => {
it(`at view=${viewFn.name}, ${buttonFn.name} has disabled=${disabled} tooltip=${tooltip}`, async () => {
viewFn();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(buttonFn()).toEqual({
disabled,
@@ -116,7 +115,7 @@ describe('IDE commit form', () => {
goToEditView();
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders commit button in compact mode', () => {
@@ -135,7 +134,7 @@ describe('IDE commit form', () => {
it('when begin commit button is clicked, shows form', async () => {
findBeginCommitButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findForm().exists()).toBe(true);
});
@@ -143,7 +142,7 @@ describe('IDE commit form', () => {
it('when begin commit button is clicked, sets activity view', async () => {
findBeginCommitButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(store.state.currentActivityView).toBe(leftSidebarViews.commit.name);
});
@@ -153,14 +152,14 @@ describe('IDE commit form', () => {
setLastCommitMessage('test');
goToEditView();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findForm().exists()).toBe(true);
// Now test that it collapses when lastCommitMsg is cleared
setLastCommitMessage('');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findForm().exists()).toBe(false);
});
@@ -177,7 +176,7 @@ describe('IDE commit form', () => {
goToCommitView();
- await wrapper.vm.$nextTick();
+ await nextTick();
});
afterEach(() => {
@@ -188,12 +187,12 @@ describe('IDE commit form', () => {
expect(findForm().exists()).toBe(false);
store.state.stagedFiles = [];
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findForm().exists()).toBe(false);
store.state.stagedFiles.push('test');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findForm().exists()).toBe(false);
});
@@ -208,7 +207,7 @@ describe('IDE commit form', () => {
goToCommitView();
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('shows form', () => {
@@ -222,7 +221,7 @@ describe('IDE commit form', () => {
describe('when no changed files', () => {
beforeEach(async () => {
store.state.stagedFiles = [];
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('hides form', () => {
@@ -231,7 +230,7 @@ describe('IDE commit form', () => {
it('expands again when staged files are added', async () => {
store.state.stagedFiles.push('test');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findForm().exists()).toBe(true);
});
@@ -240,7 +239,7 @@ describe('IDE commit form', () => {
it('updates commitMessage in store on input', async () => {
setCommitMessageInput('testing commit message');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(store.state.commit.commitMessage).toBe('testing commit message');
});
@@ -253,14 +252,14 @@ describe('IDE commit form', () => {
it('resets commitMessage when clicking discard button', async () => {
setCommitMessageInput('testing commit message');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findCommitMessageInput().props('text')).toBe('testing commit message');
// Test that commitMessage is cleared on click
findDiscardDraftButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findCommitMessageInput().props('text')).toBe('');
});
@@ -274,24 +273,24 @@ describe('IDE commit form', () => {
goToCommitView();
- await wrapper.vm.$nextTick();
+ await nextTick();
setCommitMessageInput('testing commit message');
- await wrapper.vm.$nextTick();
+ await nextTick();
jest.spyOn(store, 'dispatch').mockResolvedValue();
});
- it.each([clickCommitButton, submitForm])('when %p, commits changes', (fn) => {
- fn();
+ it('when submitting form, commits changes', () => {
+ submitForm();
expect(store.dispatch).toHaveBeenCalledWith('commit/commitChanges', undefined);
});
it('when cannot push code, submitting does nothing', async () => {
store.state.projects.abcproject.userPermissions.pushCode = false;
- await wrapper.vm.$nextTick();
+ await nextTick();
submitForm();
@@ -309,7 +308,7 @@ describe('IDE commit form', () => {
const error = createError();
store.state.commit.commitError = error;
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(modal.vm.show).toHaveBeenCalled();
expect(modal.props()).toMatchObject({
@@ -342,7 +341,7 @@ describe('IDE commit form', () => {
async ({ commitError, expectedActions }) => {
store.state.commit.commitError = commitError('test message');
- await wrapper.vm.$nextTick();
+ await nextTick();
wrapper.find(GlModal).vm.$emit('ok');
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..dea920ecb5e 100644
--- a/spec/frontend/ide/components/commit_sidebar/list_item_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/list_item_spec.js
@@ -1,5 +1,6 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { trimText } from 'helpers/text_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import listItem from '~/ide/components/commit_sidebar/list_item.vue';
import { createRouter } from '~/ide/ide_router';
@@ -41,54 +42,42 @@ 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) => {
+ it('opens a closed file in the editor when clicking the file path', async () => {
jest.spyOn(vm, 'openPendingTab');
jest.spyOn(router, 'push').mockImplementation(() => {});
findPathEl.click();
- setImmediate(() => {
- expect(vm.openPendingTab).toHaveBeenCalled();
- expect(router.push).toHaveBeenCalled();
+ await nextTick();
- done();
- });
+ expect(vm.openPendingTab).toHaveBeenCalled();
+ expect(router.push).toHaveBeenCalled();
});
- it('calls updateViewer with diff when clicking file', (done) => {
+ it('calls updateViewer with diff when clicking file', async () => {
jest.spyOn(vm, 'openFileInEditor');
jest.spyOn(vm, 'updateViewer');
jest.spyOn(router, 'push').mockImplementation(() => {});
findPathEl.click();
- setImmediate(() => {
- expect(vm.updateViewer).toHaveBeenCalledWith('diff');
+ await waitForPromises();
- done();
- });
+ expect(vm.updateViewer).toHaveBeenCalledWith('diff');
});
describe('computed', () => {
@@ -134,14 +123,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/list_spec.js b/spec/frontend/ide/components/commit_sidebar/list_spec.js
index eb12fc994a5..1d42512c9ee 100644
--- a/spec/frontend/ide/components/commit_sidebar/list_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/list_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import commitSidebarList from '~/ide/components/commit_sidebar/list.vue';
import { createStore } from '~/ide/stores';
@@ -31,12 +31,11 @@ describe('Multi-file editor commit sidebar list', () => {
});
describe('with a list of files', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
const f = file('file name');
f.changed = true;
vm.fileList.push(f);
-
- Vue.nextTick(done);
+ await nextTick();
});
it('renders list', () => {
diff --git a/spec/frontend/ide/components/commit_sidebar/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/commit_sidebar/radio_group_spec.js b/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js
index a6f3253321b..d899bc4f7d8 100644
--- a/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import radioGroup from '~/ide/components/commit_sidebar/radio_group.vue';
import { createStore } from '~/ide/stores';
@@ -7,7 +7,7 @@ describe('IDE commit sidebar radio group', () => {
let vm;
let store;
- beforeEach((done) => {
+ beforeEach(async () => {
store = createStore();
const Component = Vue.extend(radioGroup);
@@ -22,7 +22,7 @@ describe('IDE commit sidebar radio group', () => {
vm.$mount();
- Vue.nextTick(done);
+ await nextTick();
});
afterEach(() => {
@@ -33,7 +33,7 @@ describe('IDE commit sidebar radio group', () => {
expect(vm.$el.textContent).toContain('test');
});
- it('uses slot if label is not present', (done) => {
+ it('uses slot if label is not present', async () => {
vm.$destroy();
vm = new Vue({
@@ -47,25 +47,19 @@ describe('IDE commit sidebar radio group', () => {
vm.$mount();
- Vue.nextTick(() => {
- expect(vm.$el.textContent).toContain('Testing slot');
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.textContent).toContain('Testing slot');
});
- it('updates store when changing radio button', (done) => {
+ it('updates store when changing radio button', async () => {
vm.$el.querySelector('input').dispatchEvent(new Event('change'));
- Vue.nextTick(() => {
- expect(store.state.commit.commitAction).toBe('1');
-
- done();
- });
+ await nextTick();
+ expect(store.state.commit.commitAction).toBe('1');
});
describe('with input', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
vm.$destroy();
const Component = Vue.extend(radioGroup);
@@ -82,32 +76,27 @@ describe('IDE commit sidebar radio group', () => {
vm.$mount();
- Vue.nextTick(done);
+ await nextTick();
});
it('renders input box when commitAction matches value', () => {
expect(vm.$el.querySelector('.form-control')).not.toBeNull();
});
- it('hides input when commitAction doesnt match value', (done) => {
+ it('hides input when commitAction doesnt match value', async () => {
store.state.commit.commitAction = '2';
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.form-control')).toBeNull();
- done();
- });
+ await nextTick();
+ expect(vm.$el.querySelector('.form-control')).toBeNull();
});
- it('updates branch name in store on input', (done) => {
+ it('updates branch name in store on input', async () => {
const input = vm.$el.querySelector('.form-control');
input.value = 'testing-123';
input.dispatchEvent(new Event('input'));
- Vue.nextTick(() => {
- expect(store.state.commit.newBranchName).toBe('testing-123');
-
- done();
- });
+ await nextTick();
+ expect(store.state.commit.newBranchName).toBe('testing-123');
});
it('renders newBranchName if present', () => {
diff --git a/spec/frontend/ide/components/commit_sidebar/success_message_spec.js b/spec/frontend/ide/components/commit_sidebar/success_message_spec.js
index 7bbe47d37af..52e35bdbb73 100644
--- a/spec/frontend/ide/components/commit_sidebar/success_message_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/success_message_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import successMessage from '~/ide/components/commit_sidebar/success_message.vue';
import { createStore } from '~/ide/stores';
@@ -23,13 +23,10 @@ describe('IDE commit panel successful commit state', () => {
vm.$destroy();
});
- it('renders last commit message when it exists', (done) => {
+ it('renders last commit message when it exists', async () => {
vm.$store.state.lastCommitMsg = 'testing commit message';
- Vue.nextTick(() => {
- expect(vm.$el.textContent).toContain('testing commit message');
-
- done();
- });
+ await nextTick();
+ expect(vm.$el.textContent).toContain('testing commit message');
});
});
diff --git a/spec/frontend/ide/components/error_message_spec.js b/spec/frontend/ide/components/error_message_spec.js
index 2de3fa863a8..17568158131 100644
--- a/spec/frontend/ide/components/error_message_spec.js
+++ b/spec/frontend/ide/components/error_message_spec.js
@@ -1,10 +1,10 @@
import { GlLoadingIcon } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import ErrorMessage from '~/ide/components/error_message.vue';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('IDE error message component', () => {
let wrapper;
@@ -25,7 +25,6 @@ describe('IDE error message component', () => {
},
},
store: fakeStore,
- localVue,
});
};
@@ -87,19 +86,15 @@ describe('IDE error message component', () => {
expect(actionMock).toHaveBeenCalledWith(message.actionPayload);
});
- it('does not dispatch action when already loading', () => {
+ it('does not dispatch action when already loading', async () => {
findActionButton().trigger('click');
actionMock.mockReset();
- return wrapper.vm.$nextTick(() => {
- findActionButton().trigger('click');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(actionMock).not.toHaveBeenCalled();
- });
- });
+ findActionButton().trigger('click');
+ await nextTick();
+ expect(actionMock).not.toHaveBeenCalled();
});
- it('shows loading icon when loading', () => {
+ it('shows loading icon when loading', async () => {
let resolveAction;
actionMock.mockImplementation(
() =>
@@ -109,19 +104,16 @@ describe('IDE error message component', () => {
);
findActionButton().trigger('click');
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(true);
- resolveAction();
- });
+ await nextTick();
+ expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(true);
+ resolveAction();
});
- it('hides loading icon when operation finishes', () => {
+ it('hides loading icon when operation finishes', async () => {
findActionButton().trigger('click');
- return actionMock()
- .then(() => wrapper.vm.$nextTick())
- .then(() => {
- expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(false);
- });
+ await actionMock();
+ await nextTick();
+ expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(false);
});
});
});
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/file_templates/dropdown_spec.js b/spec/frontend/ide/components/file_templates/dropdown_spec.js
index 44ac9aa954d..e54b322b9db 100644
--- a/spec/frontend/ide/components/file_templates/dropdown_spec.js
+++ b/spec/frontend/ide/components/file_templates/dropdown_spec.js
@@ -1,11 +1,11 @@
import { GlLoadingIcon } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import $ from 'jquery';
import Vuex from 'vuex';
import Dropdown from '~/ide/components/file_templates/dropdown.vue';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('IDE file templates dropdown component', () => {
let wrapper;
@@ -44,7 +44,6 @@ describe('IDE file templates dropdown component', () => {
...props,
},
store: fakeStore,
- localVue,
});
({ element } = wrapper);
@@ -55,15 +54,14 @@ describe('IDE file templates dropdown component', () => {
wrapper = null;
});
- it('calls clickItem on click', () => {
+ it('calls clickItem on click', async () => {
const itemData = { name: 'test.yml ' };
createComponent({ props: { data: [itemData] } });
const item = findItemButtons().at(0);
item.trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().click[0][0]).toBe(itemData);
- });
+ await nextTick();
+ expect(wrapper.emitted().click[0][0]).toBe(itemData);
});
it('renders dropdown title', () => {
@@ -112,7 +110,7 @@ describe('IDE file templates dropdown component', () => {
expect(items.wrappers.map((x) => x.text())).toEqual(templates.map((x) => x.name));
});
- it('searches template data', () => {
+ it('searches template data', async () => {
const templates = [{ name: 'match 1' }, { name: 'other' }, { name: 'match 2' }];
const matches = ['match 1', 'match 2'];
createComponent({
@@ -120,12 +118,11 @@ describe('IDE file templates dropdown component', () => {
state: { templates },
});
findSearch().setValue('match');
- return wrapper.vm.$nextTick().then(() => {
- const items = findItemButtons();
+ await nextTick();
+ const items = findItemButtons();
- expect(items.length).toBe(matches.length);
- expect(items.wrappers.map((x) => x.text())).toEqual(matches);
- });
+ expect(items.length).toBe(matches.length);
+ expect(items.wrappers.map((x) => x.text())).toEqual(matches);
});
it('does not render input when `searchable` is true & `showLoading` is true', () => {
@@ -160,17 +157,16 @@ describe('IDE file templates dropdown component', () => {
expect(findSearch().exists()).toBe(true);
});
- it('searches data', () => {
+ it('searches data', async () => {
const data = [{ name: 'match 1' }, { name: 'other' }, { name: 'match 2' }];
const matches = ['match 1', 'match 2'];
createComponent({ props: { searchable: true, data } });
findSearch().setValue('match');
- return wrapper.vm.$nextTick().then(() => {
- const items = findItemButtons();
+ await nextTick();
+ const items = findItemButtons();
- expect(items.length).toBe(matches.length);
- expect(items.wrappers.map((x) => x.text())).toEqual(matches);
- });
+ expect(items.length).toBe(matches.length);
+ expect(items.wrappers.map((x) => x.text())).toEqual(matches);
});
});
});
diff --git a/spec/frontend/ide/components/ide_file_row_spec.js b/spec/frontend/ide/components/ide_file_row_spec.js
index 20c105460f2..baf3d7cca9d 100644
--- a/spec/frontend/ide/components/ide_file_row_spec.js
+++ b/spec/frontend/ide/components/ide_file_row_spec.js
@@ -1,12 +1,12 @@
-import { createLocalVue, mount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import FileRowExtra from '~/ide/components/file_row_extra.vue';
import IdeFileRow from '~/ide/components/ide_file_row.vue';
import { createStore } from '~/ide/stores';
import FileRow from '~/vue_shared/components/file_row.vue';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
const TEST_EXTRA_PROPS = {
testattribute: 'abc',
@@ -30,7 +30,6 @@ describe('Ide File Row component', () => {
...props,
},
store: createStore(),
- localVue,
...options,
});
};
@@ -44,7 +43,7 @@ describe('Ide File Row component', () => {
const findFileRow = () => wrapper.find(FileRow);
const hasDropdownOpen = () => findFileRowExtra().props('dropdownOpen');
- it('fileRow component has listeners', () => {
+ it('fileRow component has listeners', async () => {
const toggleTreeOpen = jest.fn();
createComponent(
{},
@@ -57,9 +56,8 @@ describe('Ide File Row component', () => {
findFileRow().vm.$emit('toggleTreeOpen');
- return wrapper.vm.$nextTick().then(() => {
- expect(toggleTreeOpen).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(toggleTreeOpen).toHaveBeenCalled();
});
describe('default', () => {
@@ -86,32 +84,30 @@ describe('Ide File Row component', () => {
});
describe('with open dropdown', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createComponent();
findFileRowExtra().vm.$emit('toggle', true);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('shows open dropdown', () => {
expect(hasDropdownOpen()).toBe(true);
});
- it('hides dropdown when mouseleave', () => {
+ it('hides dropdown when mouseleave', async () => {
findFileRow().vm.$emit('mouseleave');
- return wrapper.vm.$nextTick().then(() => {
- expect(hasDropdownOpen()).toEqual(false);
- });
+ await nextTick();
+ expect(hasDropdownOpen()).toEqual(false);
});
- it('hides dropdown on toggle', () => {
+ it('hides dropdown on toggle', async () => {
findFileRowExtra().vm.$emit('toggle', false);
- return wrapper.vm.$nextTick().then(() => {
- expect(hasDropdownOpen()).toEqual(false);
- });
+ await nextTick();
+ expect(hasDropdownOpen()).toEqual(false);
});
});
});
diff --git a/spec/frontend/ide/components/ide_review_spec.js b/spec/frontend/ide/components/ide_review_spec.js
index 7a92f59641f..13d20761263 100644
--- a/spec/frontend/ide/components/ide_review_spec.js
+++ b/spec/frontend/ide/components/ide_review_spec.js
@@ -1,5 +1,5 @@
-import { createLocalVue, mount } from '@vue/test-utils';
-import Vue from 'vue';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { keepAlive } from 'helpers/keep_alive_component_helper';
import { trimText } from 'helpers/text_helper';
@@ -9,8 +9,7 @@ import { createStore } from '~/ide/stores';
import { file } from '../helpers';
import { projectData } from '../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('IDE review mode', () => {
let wrapper;
@@ -28,7 +27,6 @@ describe('IDE review mode', () => {
wrapper = mount(keepAlive(IdeReview), {
store,
- localVue,
});
});
@@ -76,14 +74,14 @@ describe('IDE review mode', () => {
});
describe('merge request', () => {
- beforeEach(() => {
+ beforeEach(async () => {
store.state.currentMergeRequestId = '1';
store.state.projects.abcproject.mergeRequests['1'] = {
iid: 123,
web_url: 'testing123',
};
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders edit dropdown', () => {
@@ -93,7 +91,7 @@ describe('IDE review mode', () => {
it('renders merge request link & IID', async () => {
store.state.viewer = 'mrdiff';
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(trimText(wrapper.text())).toContain('Merge request (!123)');
});
@@ -101,7 +99,7 @@ describe('IDE review mode', () => {
it('changes text to latest changes when viewer is not mrdiff', async () => {
store.state.viewer = 'diff';
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.text()).toContain('Latest changes');
});
diff --git a/spec/frontend/ide/components/ide_side_bar_spec.js b/spec/frontend/ide/components/ide_side_bar_spec.js
index c683612b142..34f14ef23a4 100644
--- a/spec/frontend/ide/components/ide_side_bar_spec.js
+++ b/spec/frontend/ide/components/ide_side_bar_spec.js
@@ -1,5 +1,6 @@
import { GlSkeletonLoading } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
import IdeReview from '~/ide/components/ide_review.vue';
@@ -10,8 +11,7 @@ import { leftSidebarViews } from '~/ide/constants';
import { createStore } from '~/ide/stores';
import { projectData } from '../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('IdeSidebar', () => {
let wrapper;
@@ -26,7 +26,6 @@ describe('IdeSidebar', () => {
return mount(IdeSidebar, {
store,
- localVue,
});
}
@@ -46,7 +45,7 @@ describe('IdeSidebar', () => {
store.state.loading = true;
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.findAll(GlSkeletonLoading)).toHaveLength(3);
});
@@ -61,7 +60,7 @@ describe('IdeSidebar', () => {
store.state.currentActivityView = leftSidebarViews.review.name;
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(IdeTree).exists()).toBe(false);
expect(wrapper.find(IdeReview).exists()).toBe(true);
@@ -69,7 +68,7 @@ describe('IdeSidebar', () => {
store.state.currentActivityView = leftSidebarViews.commit.name;
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(IdeTree).exists()).toBe(false);
expect(wrapper.find(IdeReview).exists()).toBe(false);
@@ -85,7 +84,7 @@ describe('IdeSidebar', () => {
view,
});
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(IdeTree).exists()).toBe(tree);
expect(wrapper.find(IdeReview).exists()).toBe(review);
@@ -100,7 +99,7 @@ describe('IdeSidebar', () => {
store.state.currentActivityView = leftSidebarViews.commit.name;
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(IdeTree).exists()).toBe(false);
expect(wrapper.find(RepoCommitSection).exists()).toBe(true);
@@ -108,7 +107,7 @@ describe('IdeSidebar', () => {
store.state.currentActivityView = leftSidebarViews.edit.name;
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
// reference to the elements remains the same, meaning the components were kept alive
expect(wrapper.find(IdeTree).element).toEqual(ideTreeComponent);
diff --git a/spec/frontend/ide/components/ide_spec.js b/spec/frontend/ide/components/ide_spec.js
index f8d29fc7b47..37b42001a80 100644
--- a/spec/frontend/ide/components/ide_spec.js
+++ b/spec/frontend/ide/components/ide_spec.js
@@ -1,4 +1,5 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
import CannotPushCodeAlert from '~/ide/components/cannot_push_code_alert.vue';
@@ -9,8 +10,7 @@ import { createStore } from '~/ide/stores';
import { file } from '../helpers';
import { projectData } from '../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
const TEST_FORK_IDE_PATH = '/test/ide/path';
@@ -34,7 +34,6 @@ describe('WebIDE', () => {
wrapper = shallowMount(Ide, {
store,
- localVue,
});
};
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_status_list_spec.js b/spec/frontend/ide/components/ide_status_list_spec.js
index 036edfb3ec1..371fbc6becd 100644
--- a/spec/frontend/ide/components/ide_status_list_spec.js
+++ b/spec/frontend/ide/components/ide_status_list_spec.js
@@ -1,5 +1,6 @@
import { GlLink } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import IdeStatusList from '~/ide/components/ide_status_list.vue';
import TerminalSyncStatusSafe from '~/ide/components/terminal_sync/terminal_sync_status_safe.vue';
@@ -16,8 +17,7 @@ const TEST_FILE_EDITOR = {
};
const TEST_EDITOR_POSITION = `${TEST_FILE_EDITOR.editorRow}:${TEST_FILE_EDITOR.editorColumn}`;
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('ide/components/ide_status_list', () => {
let activeFileEditor;
@@ -42,7 +42,6 @@ describe('ide/components/ide_status_list', () => {
});
wrapper = shallowMount(IdeStatusList, {
- localVue,
store,
...options,
});
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/ide_tree_spec.js b/spec/frontend/ide/components/ide_tree_spec.js
index 0792b88aeb6..8465ef9f5f3 100644
--- a/spec/frontend/ide/components/ide_tree_spec.js
+++ b/spec/frontend/ide/components/ide_tree_spec.js
@@ -1,4 +1,4 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import { keepAlive } from 'helpers/keep_alive_component_helper';
@@ -7,8 +7,7 @@ import { createStore } from '~/ide/stores';
import { file } from '../helpers';
import { projectData } from '../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('IdeTree', () => {
let store;
@@ -27,7 +26,6 @@ describe('IdeTree', () => {
wrapper = mount(keepAlive(IdeTree), {
store,
- localVue,
});
});
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/jobs/list_spec.js b/spec/frontend/ide/components/jobs/list_spec.js
index 8797e07aef1..cb2c9f8f04f 100644
--- a/spec/frontend/ide/components/jobs/list_spec.js
+++ b/spec/frontend/ide/components/jobs/list_spec.js
@@ -1,11 +1,11 @@
import { GlLoadingIcon } from '@gitlab/ui';
-import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
+import { shallowMount, mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import StageList from '~/ide/components/jobs/list.vue';
import Stage from '~/ide/components/jobs/stage.vue';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
const storeActions = {
fetchJobs: jest.fn(),
toggleStageCollapsed: jest.fn(),
@@ -42,7 +42,6 @@ describe('IDE stages list', () => {
...defaultProps,
...props,
},
- localVue,
store,
});
};
@@ -92,7 +91,6 @@ describe('IDE stages list', () => {
wrapper = mount(StageList, {
propsData: { ...defaultProps, stages },
store,
- localVue,
});
});
diff --git a/spec/frontend/ide/components/jobs/stage_spec.js b/spec/frontend/ide/components/jobs/stage_spec.js
index 9accd81a2ba..f158c59cd32 100644
--- a/spec/frontend/ide/components/jobs/stage_spec.js
+++ b/spec/frontend/ide/components/jobs/stage_spec.js
@@ -1,5 +1,6 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import Item from '~/ide/components/jobs/item.vue';
import Stage from '~/ide/components/jobs/stage.vue';
import { stages, jobs } from '../../mock_data';
@@ -47,23 +48,21 @@ describe('IDE pipeline stage', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
- it('emits toggleCollaped event with stage id when clicking header', () => {
+ it('emits toggleCollaped event with stage id when clicking header', async () => {
const id = 5;
createComponent({ stage: { ...defaultProps.stage, id } });
findHeader().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().toggleCollapsed[0][0]).toBe(id);
- });
+ await nextTick();
+ expect(wrapper.emitted().toggleCollapsed[0][0]).toBe(id);
});
- it('emits clickViewLog entity with job', () => {
+ it('emits clickViewLog entity with job', async () => {
const [job] = defaultProps.stage.jobs;
createComponent();
wrapper.findAll(Item).at(0).vm.$emit('clickViewLog', job);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().clickViewLog[0][0]).toBe(job);
- });
+ await nextTick();
+ expect(wrapper.emitted().clickViewLog[0][0]).toBe(job);
});
it('renders stage details & icon', () => {
diff --git a/spec/frontend/ide/components/merge_requests/item_spec.js b/spec/frontend/ide/components/merge_requests/item_spec.js
index f0a97a0b10a..d6cf8127b53 100644
--- a/spec/frontend/ide/components/merge_requests/item_spec.js
+++ b/spec/frontend/ide/components/merge_requests/item_spec.js
@@ -1,4 +1,5 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import Item from '~/ide/components/merge_requests/item.vue';
import { createRouter } from '~/ide/ide_router';
@@ -11,8 +12,7 @@ const TEST_ITEM = {
};
describe('IDE merge request item', () => {
- const localVue = createLocalVue();
- localVue.use(Vuex);
+ Vue.use(Vuex);
let wrapper;
let store;
@@ -28,7 +28,6 @@ describe('IDE merge request item', () => {
currentProjectId: TEST_ITEM.projectPathWithNamespace,
...props,
},
- localVue,
router,
store,
});
diff --git a/spec/frontend/ide/components/merge_requests/list_spec.js b/spec/frontend/ide/components/merge_requests/list_spec.js
index 610e20d5868..583671a0af6 100644
--- a/spec/frontend/ide/components/merge_requests/list_spec.js
+++ b/spec/frontend/ide/components/merge_requests/list_spec.js
@@ -1,13 +1,13 @@
import { GlLoadingIcon } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import Item from '~/ide/components/merge_requests/item.vue';
import List from '~/ide/components/merge_requests/list.vue';
import TokenedInput from '~/ide/components/shared/tokened_input.vue';
import { mergeRequests as mergeRequestsMock } from '../../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('IDE merge requests list', () => {
let wrapper;
@@ -41,7 +41,6 @@ describe('IDE merge requests list', () => {
wrapper = shallowMount(List, {
store: fakeStore,
- localVue,
});
};
@@ -67,33 +66,28 @@ describe('IDE merge requests list', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
- it('renders no search results text when search is not empty', () => {
+ it('renders no search results text when search is not empty', async () => {
createComponent();
findTokenedInput().vm.$emit('input', 'something');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.text()).toContain('No merge requests found');
- });
+ await nextTick();
+ expect(wrapper.text()).toContain('No merge requests found');
});
- it('clicking on search type, sets currentSearchType and loads merge requests', () => {
+ it('clicking on search type, sets currentSearchType and loads merge requests', async () => {
createComponent();
findTokenedInput().vm.$emit('focus');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findSearchTypeButtons().at(0).trigger('click');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- const searchType = wrapper.vm.$options.searchTypes[0];
+ await nextTick();
+ findSearchTypeButtons().at(0).trigger('click');
- expect(findTokenedInput().props('tokens')).toEqual([searchType]);
- expect(fetchMergeRequestsMock).toHaveBeenCalledWith(expect.any(Object), {
- type: searchType.type,
- search: '',
- });
- });
+ await nextTick();
+ const searchType = wrapper.vm.$options.searchTypes[0];
+
+ expect(findTokenedInput().props('tokens')).toEqual([searchType]);
+ expect(fetchMergeRequestsMock).toHaveBeenCalledWith(expect.any(Object), {
+ type: searchType.type,
+ search: '',
+ });
});
describe('with merge requests', () => {
@@ -120,16 +114,15 @@ describe('IDE merge requests list', () => {
});
describe('when searching merge requests', () => {
- it('calls `loadMergeRequests` on input in search field', () => {
+ it('calls `loadMergeRequests` on input in search field', async () => {
createComponent(defaultStateWithMergeRequests);
const input = findTokenedInput();
input.vm.$emit('input', 'something');
- return wrapper.vm.$nextTick().then(() => {
- expect(fetchMergeRequestsMock).toHaveBeenCalledWith(expect.any(Object), {
- search: 'something',
- type: '',
- });
+ await nextTick();
+ expect(fetchMergeRequestsMock).toHaveBeenCalledWith(expect.any(Object), {
+ search: 'something',
+ type: '',
});
});
});
@@ -144,9 +137,9 @@ describe('IDE merge requests list', () => {
});
describe('without search value', () => {
- beforeEach(() => {
+ beforeEach(async () => {
input.vm.$emit('focus');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('shows search types', () => {
@@ -156,22 +149,20 @@ describe('IDE merge requests list', () => {
);
});
- it('hides search types when search changes', () => {
+ it('hides search types when search changes', async () => {
input.vm.$emit('input', 'something');
- return wrapper.vm.$nextTick().then(() => {
- expect(findSearchTypeButtons().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findSearchTypeButtons().exists()).toBe(false);
});
describe('with search type', () => {
- beforeEach(() => {
+ beforeEach(async () => {
findSearchTypeButtons().at(0).trigger('click');
- return wrapper.vm
- .$nextTick()
- .then(() => input.vm.$emit('focus'))
- .then(() => wrapper.vm.$nextTick());
+ await nextTick();
+ await input.vm.$emit('focus');
+ await nextTick();
});
it('does not show search types', () => {
@@ -181,10 +172,10 @@ describe('IDE merge requests list', () => {
});
describe('with search value', () => {
- beforeEach(() => {
+ beforeEach(async () => {
input.vm.$emit('input', 'something');
input.vm.$emit('focus');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('does not show search types', () => {
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/index_spec.js b/spec/frontend/ide/components/new_dropdown/index_spec.js
index fa34d1b257f..19dcd9569b3 100644
--- a/spec/frontend/ide/components/new_dropdown/index_spec.js
+++ b/spec/frontend/ide/components/new_dropdown/index_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import newDropdown from '~/ide/components/new_dropdown/index.vue';
import { createStore } from '~/ide/stores';
@@ -57,17 +57,15 @@ describe('new dropdown component', () => {
});
describe('isOpen', () => {
- it('scrolls dropdown into view', (done) => {
+ it('scrolls dropdown into view', async () => {
jest.spyOn(vm.$refs.dropdownMenu, 'scrollIntoView').mockImplementation(() => {});
vm.isOpen = true;
- setImmediate(() => {
- expect(vm.$refs.dropdownMenu.scrollIntoView).toHaveBeenCalledWith({
- block: 'nearest',
- });
+ await nextTick();
- done();
+ expect(vm.$refs.dropdownMenu.scrollIntoView).toHaveBeenCalledWith({
+ block: 'nearest',
});
});
});
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/panes/collapsible_sidebar_spec.js b/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js
index 7216f50b05c..7f2ee0fe7d9 100644
--- a/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js
+++ b/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js
@@ -1,12 +1,12 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import IdeSidebarNav from '~/ide/components/ide_sidebar_nav.vue';
import CollapsibleSidebar from '~/ide/components/panes/collapsible_sidebar.vue';
import { createStore } from '~/ide/stores';
import paneModule from '~/ide/stores/modules/pane';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('ide/components/panes/collapsible_sidebar.vue', () => {
let wrapper;
@@ -17,7 +17,6 @@ describe('ide/components/panes/collapsible_sidebar.vue', () => {
const createComponent = (props) => {
wrapper = shallowMount(CollapsibleSidebar, {
- localVue,
store,
propsData: {
extensionTabs: [],
@@ -46,7 +45,7 @@ describe('ide/components/panes/collapsible_sidebar.vue', () => {
let extensionTabs;
beforeEach(() => {
- const FakeComponent = localVue.component(fakeComponentName, {
+ const FakeComponent = Vue.component(fakeComponentName, {
render: () => null,
});
diff --git a/spec/frontend/ide/components/panes/right_spec.js b/spec/frontend/ide/components/panes/right_spec.js
index c6231d129ff..d12acd6dc4c 100644
--- a/spec/frontend/ide/components/panes/right_spec.js
+++ b/spec/frontend/ide/components/panes/right_spec.js
@@ -1,5 +1,5 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import CollapsibleSidebar from '~/ide/components/panes/collapsible_sidebar.vue';
import RightPane from '~/ide/components/panes/right.vue';
@@ -7,8 +7,7 @@ import { rightSidebarViews } from '~/ide/constants';
import { createStore } from '~/ide/stores';
import extendStore from '~/ide/stores/extend';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('ide/components/panes/right.vue', () => {
let wrapper;
@@ -18,7 +17,6 @@ describe('ide/components/panes/right.vue', () => {
extendStore(store, document.createElement('div'));
wrapper = shallowMount(RightPane, {
- localVue,
store,
propsData: {
...props,
@@ -88,19 +86,18 @@ describe('ide/components/panes/right.vue', () => {
createComponent();
});
- it('adds terminal tab', () => {
+ it('adds terminal tab', async () => {
store.state.terminal.isVisible = true;
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(CollapsibleSidebar).props('extensionTabs')).toEqual(
- expect.arrayContaining([
- expect.objectContaining({
- show: true,
- title: 'Terminal',
- }),
- ]),
- );
- });
+ await nextTick();
+ expect(wrapper.find(CollapsibleSidebar).props('extensionTabs')).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ show: true,
+ title: 'Terminal',
+ }),
+ ]),
+ );
});
it('hides terminal tab when not visible', () => {
diff --git a/spec/frontend/ide/components/preview/clientside_spec.js b/spec/frontend/ide/components/preview/clientside_spec.js
index b168eec0f16..426fbd5c04c 100644
--- a/spec/frontend/ide/components/preview/clientside_spec.js
+++ b/spec/frontend/ide/components/preview/clientside_spec.js
@@ -1,16 +1,19 @@
import { GlLoadingIcon } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+import { dispatch } from 'codesandbox-api';
import smooshpack from 'smooshpack';
import Vuex from 'vuex';
+import waitForPromises from 'helpers/wait_for_promises';
import Clientside from '~/ide/components/preview/clientside.vue';
+import { PING_USAGE_PREVIEW_KEY, PING_USAGE_PREVIEW_SUCCESS_KEY } from '~/ide/constants';
import eventHub from '~/ide/eventhub';
jest.mock('smooshpack', () => ({
Manager: jest.fn(),
}));
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
const dummyPackageJson = () => ({
raw: JSON.stringify({
@@ -39,8 +42,7 @@ describe('IDE clientside preview', () => {
const storeClientsideActions = {
pingUsage: jest.fn().mockReturnValue(Promise.resolve({})),
};
-
- const waitForCalls = () => new Promise(setImmediate);
+ const dispatchCodesandboxReady = () => dispatch({ type: 'done' });
const createComponent = ({ state, getters } = {}) => {
store = new Vuex.Store({
@@ -67,7 +69,6 @@ describe('IDE clientside preview', () => {
wrapper = shallowMount(Clientside, {
store,
- localVue,
});
};
@@ -98,7 +99,7 @@ describe('IDE clientside preview', () => {
beforeEach(() => {
createComponent({ getters: { packageJson: dummyPackageJson } });
- return waitForCalls();
+ return waitForPromises();
});
it('creates sandpack manager', () => {
@@ -111,6 +112,20 @@ describe('IDE clientside preview', () => {
it('pings usage', () => {
expect(storeClientsideActions.pingUsage).toHaveBeenCalledTimes(1);
+ expect(storeClientsideActions.pingUsage).toHaveBeenCalledWith(
+ expect.anything(),
+ PING_USAGE_PREVIEW_KEY,
+ );
+ });
+
+ it('pings usage success', async () => {
+ dispatchCodesandboxReady();
+ await nextTick();
+ expect(storeClientsideActions.pingUsage).toHaveBeenCalledTimes(2);
+ expect(storeClientsideActions.pingUsage).toHaveBeenCalledWith(
+ expect.anything(),
+ PING_USAGE_PREVIEW_SUCCESS_KEY,
+ );
});
});
@@ -123,7 +138,7 @@ describe('IDE clientside preview', () => {
state: { codesandboxBundlerUrl: TEST_BUNDLER_URL },
});
- return waitForCalls();
+ return waitForPromises();
});
it('creates sandpack manager with bundlerURL', () => {
@@ -138,7 +153,7 @@ describe('IDE clientside preview', () => {
beforeEach(() => {
createComponent({ getters: { packageJson: dummyPackageJson } });
- return waitForCalls();
+ return waitForPromises();
});
it('creates sandpack manager', () => {
@@ -324,7 +339,7 @@ describe('IDE clientside preview', () => {
wrapper.setData({ sandpackReady: true });
wrapper.vm.update();
- return waitForCalls().then(() => {
+ return waitForPromises().then(() => {
expect(smooshpack.Manager).toHaveBeenCalled();
});
});
@@ -352,39 +367,36 @@ describe('IDE clientside preview', () => {
});
describe('template', () => {
- it('renders ide-preview element when showPreview is true', () => {
+ it('renders ide-preview element when showPreview is true', async () => {
createComponent({ getters: { packageJson: dummyPackageJson } });
// 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: false });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find('#ide-preview').exists()).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find('#ide-preview').exists()).toBe(true);
});
- it('renders empty state', () => {
+ it('renders empty state', 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({ loading: false });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.text()).toContain(
- 'Preview your web application using Web IDE client-side evaluation.',
- );
- });
+ await nextTick();
+ expect(wrapper.text()).toContain(
+ 'Preview your web application using Web IDE client-side evaluation.',
+ );
});
- it('renders loading icon', () => {
+ it('renders loading 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({ loading: true });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
});
diff --git a/spec/frontend/ide/components/preview/navigator_spec.js b/spec/frontend/ide/components/preview/navigator_spec.js
index ee760364c7e..a199f4704f7 100644
--- a/spec/frontend/ide/components/preview/navigator_spec.js
+++ b/spec/frontend/ide/components/preview/navigator_spec.js
@@ -1,6 +1,7 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { listen } from 'codesandbox-api';
+import { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import ClientsideNavigator from '~/ide/components/preview/navigator.vue';
@@ -29,31 +30,28 @@ describe('IDE clientside preview navigator', () => {
wrapper.destroy();
});
- it('renders readonly URL bar', () => {
+ it('renders readonly URL bar', async () => {
listenHandler({ type: 'urlchange', url: manager.bundlerURL });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find('input[readonly]').element.value).toBe('/');
- });
+ await nextTick();
+ expect(wrapper.find('input[readonly]').element.value).toBe('/');
});
it('renders loading icon by default', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
- it('removes loading icon when done event is fired', () => {
+ it('removes loading icon when done event is fired', async () => {
listenHandler({ type: 'done' });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
});
- it('does not count visiting same url multiple times', () => {
+ it('does not count visiting same url multiple times', async () => {
listenHandler({ type: 'done' });
listenHandler({ type: 'done', url: `${TEST_HOST}/url1` });
listenHandler({ type: 'done', url: `${TEST_HOST}/url1` });
- return wrapper.vm.$nextTick().then(() => {
- expect(findBackButton().attributes('disabled')).toBe('disabled');
- });
+ await nextTick();
+ expect(findBackButton().attributes('disabled')).toBe('disabled');
});
it('unsubscribes from listen on destroy', () => {
@@ -64,107 +62,93 @@ describe('IDE clientside preview navigator', () => {
});
describe('back button', () => {
- beforeEach(() => {
+ beforeEach(async () => {
listenHandler({ type: 'done' });
listenHandler({ type: 'urlchange', url: TEST_HOST });
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('is disabled by default', () => {
expect(findBackButton().attributes('disabled')).toBe('disabled');
});
- it('is enabled when there is previous entry', () => {
+ it('is enabled when there is previous entry', async () => {
listenHandler({ type: 'urlchange', url: `${TEST_HOST}/url1` });
- return wrapper.vm.$nextTick().then(() => {
- findBackButton().trigger('click');
- expect(findBackButton().attributes('disabled')).toBeFalsy();
- });
+ await nextTick();
+ findBackButton().trigger('click');
+ expect(findBackButton().attributes('disabled')).toBeFalsy();
});
- it('is disabled when there is no previous entry', () => {
+ it('is disabled when there is no previous entry', async () => {
listenHandler({ type: 'urlchange', url: `${TEST_HOST}/url1` });
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findBackButton().trigger('click');
-
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findBackButton().attributes('disabled')).toBe('disabled');
- });
+
+ await nextTick();
+ findBackButton().trigger('click');
+
+ await nextTick();
+ expect(findBackButton().attributes('disabled')).toBe('disabled');
});
- it('updates manager iframe src', () => {
+ it('updates manager iframe src', async () => {
listenHandler({ type: 'urlchange', url: `${TEST_HOST}/url1` });
listenHandler({ type: 'urlchange', url: `${TEST_HOST}/url2` });
- return wrapper.vm.$nextTick().then(() => {
- findBackButton().trigger('click');
+ await nextTick();
+ findBackButton().trigger('click');
- expect(manager.iframe.src).toBe(`${TEST_HOST}/url1`);
- });
+ expect(manager.iframe.src).toBe(`${TEST_HOST}/url1`);
});
});
describe('forward button', () => {
- beforeEach(() => {
+ beforeEach(async () => {
listenHandler({ type: 'done' });
listenHandler({ type: 'urlchange', url: TEST_HOST });
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('is disabled by default', () => {
expect(findForwardButton().attributes('disabled')).toBe('disabled');
});
- it('is enabled when there is next entry', () => {
+ it('is enabled when there is next entry', async () => {
listenHandler({ type: 'urlchange', url: `${TEST_HOST}/url1` });
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findBackButton().trigger('click');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findForwardButton().attributes('disabled')).toBeFalsy();
- });
+
+ await nextTick();
+ findBackButton().trigger('click');
+
+ await nextTick();
+ expect(findForwardButton().attributes('disabled')).toBeFalsy();
});
- it('is disabled when there is no next entry', () => {
+ it('is disabled when there is no next entry', async () => {
listenHandler({ type: 'urlchange', url: `${TEST_HOST}/url1` });
- return wrapper.vm
- .$nextTick()
- .then(() => {
- findBackButton().trigger('click');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- findForwardButton().trigger('click');
- return wrapper.vm.$nextTick();
- })
- .then(() => {
- expect(findForwardButton().attributes('disabled')).toBe('disabled');
- });
+
+ await nextTick();
+ findBackButton().trigger('click');
+
+ await nextTick();
+ findForwardButton().trigger('click');
+
+ await nextTick();
+ expect(findForwardButton().attributes('disabled')).toBe('disabled');
});
- it('updates manager iframe src', () => {
+ it('updates manager iframe src', async () => {
listenHandler({ type: 'urlchange', url: `${TEST_HOST}/url1` });
listenHandler({ type: 'urlchange', url: `${TEST_HOST}/url2` });
- return wrapper.vm.$nextTick().then(() => {
- findBackButton().trigger('click');
+ await nextTick();
+ findBackButton().trigger('click');
- expect(manager.iframe.src).toBe(`${TEST_HOST}/url1`);
- });
+ expect(manager.iframe.src).toBe(`${TEST_HOST}/url1`);
});
});
describe('refresh button', () => {
const url = `${TEST_HOST}/some_url`;
- beforeEach(() => {
+ beforeEach(async () => {
listenHandler({ type: 'done' });
listenHandler({ type: 'urlchange', url });
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('calls refresh with current path', () => {
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/repo_tab_spec.js b/spec/frontend/ide/components/repo_tab_spec.js
index 95d52e8f7a9..b16fd8f80ba 100644
--- a/spec/frontend/ide/components/repo_tab_spec.js
+++ b/spec/frontend/ide/components/repo_tab_spec.js
@@ -1,5 +1,6 @@
import { GlTab } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import { stubComponent } from 'helpers/stub_component';
import RepoTab from '~/ide/components/repo_tab.vue';
@@ -7,8 +8,7 @@ import { createRouter } from '~/ide/ide_router';
import { createStore } from '~/ide/stores';
import { file } from '../helpers';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
const GlTabStub = stubComponent(GlTab, {
template: '<li><slot name="title" /></li>',
@@ -23,7 +23,6 @@ describe('RepoTab', () => {
function createComponent(propsData) {
wrapper = mount(RepoTab, {
- localVue,
store,
propsData,
stubs: {
diff --git a/spec/frontend/ide/components/repo_tabs_spec.js b/spec/frontend/ide/components/repo_tabs_spec.js
index 6ee73b0a437..1cfc1f12745 100644
--- a/spec/frontend/ide/components/repo_tabs_spec.js
+++ b/spec/frontend/ide/components/repo_tabs_spec.js
@@ -1,11 +1,11 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import RepoTabs from '~/ide/components/repo_tabs.vue';
import { createStore } from '~/ide/stores';
import { file } from '../helpers';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('RepoTabs', () => {
let wrapper;
@@ -22,7 +22,6 @@ describe('RepoTabs', () => {
activeFile: file('activeFile'),
},
store,
- localVue,
});
});
@@ -30,17 +29,14 @@ describe('RepoTabs', () => {
wrapper.destroy();
});
- it('renders a list of tabs', (done) => {
+ it('renders a list of tabs', async () => {
store.state.openFiles[0].active = true;
- wrapper.vm.$nextTick(() => {
- const tabs = [...wrapper.vm.$el.querySelectorAll('.multi-file-tab')];
+ await nextTick();
+ const tabs = [...wrapper.vm.$el.querySelectorAll('.multi-file-tab')];
- expect(tabs.length).toEqual(2);
- expect(tabs[0].parentNode.classList.contains('active')).toEqual(true);
- expect(tabs[1].parentNode.classList.contains('active')).toEqual(false);
-
- done();
- });
+ expect(tabs.length).toEqual(2);
+ expect(tabs[0].parentNode.classList.contains('active')).toEqual(true);
+ expect(tabs[1].parentNode.classList.contains('active')).toEqual(false);
});
});
diff --git a/spec/frontend/ide/components/resizable_panel_spec.js b/spec/frontend/ide/components/resizable_panel_spec.js
index 6a5af52ea35..55b9423aba8 100644
--- a/spec/frontend/ide/components/resizable_panel_spec.js
+++ b/spec/frontend/ide/components/resizable_panel_spec.js
@@ -1,4 +1,5 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import ResizablePanel from '~/ide/components/resizable_panel.vue';
import { SIDE_LEFT, SIDE_RIGHT } from '~/ide/constants';
@@ -8,8 +9,7 @@ const TEST_WIDTH = 500;
const TEST_MIN_WIDTH = 400;
describe('~/ide/components/resizable_panel', () => {
- const localVue = createLocalVue();
- localVue.use(Vuex);
+ Vue.use(Vuex);
let wrapper;
let store;
@@ -33,7 +33,6 @@ describe('~/ide/components/resizable_panel', () => {
...props,
},
store,
- localVue,
});
};
const findResizer = () => wrapper.find(PanelResizer);
@@ -100,15 +99,14 @@ describe('~/ide/components/resizable_panel', () => {
});
});
- it('when resizer emits update:size, changes inline width', () => {
+ it('when resizer emits update:size, changes inline width', async () => {
const newSize = TEST_WIDTH - 100;
const resizer = findResizer();
resizer.vm.$emit('update:size', newSize);
- return wrapper.vm.$nextTick().then(() => {
- expect(findInlineStyle()).toBe(createInlineStyle(newSize));
- });
+ await nextTick();
+ expect(findInlineStyle()).toBe(createInlineStyle(newSize));
});
});
});
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/ide/components/terminal/session_spec.js b/spec/frontend/ide/components/terminal/session_spec.js
index 5659a7d15da..6a70ddb46a8 100644
--- a/spec/frontend/ide/components/terminal/session_spec.js
+++ b/spec/frontend/ide/components/terminal/session_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import TerminalSession from '~/ide/components/terminal/session.vue';
import Terminal from '~/ide/components/terminal/terminal.vue';
@@ -13,8 +14,7 @@ import {
const TEST_TERMINAL_PATH = 'terminal/path';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('IDE TerminalSession', () => {
let wrapper;
@@ -33,7 +33,6 @@ describe('IDE TerminalSession', () => {
});
wrapper = shallowMount(TerminalSession, {
- localVue,
store,
...options,
});
@@ -68,32 +67,30 @@ describe('IDE TerminalSession', () => {
});
[STARTING, PENDING, RUNNING].forEach((status) => {
- it(`show stop button when status is ${status}`, () => {
+ it(`show stop button when status is ${status}`, async () => {
state.session = { status };
factory();
const button = findButton();
button.vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(button.text()).toEqual('Stop Terminal');
- expect(actions.stopSession).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(button.text()).toEqual('Stop Terminal');
+ expect(actions.stopSession).toHaveBeenCalled();
});
});
[STOPPING, STOPPED].forEach((status) => {
- it(`show stop button when status is ${status}`, () => {
+ it(`show stop button when status is ${status}`, async () => {
state.session = { status };
factory();
const button = findButton();
button.vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(button.text()).toEqual('Restart Terminal');
- expect(actions.restartSession).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(button.text()).toEqual('Restart Terminal');
+ expect(actions.restartSession).toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/ide/components/terminal/terminal_controls_spec.js b/spec/frontend/ide/components/terminal/terminal_controls_spec.js
index 416096083f0..71ec0dca89d 100644
--- a/spec/frontend/ide/components/terminal/terminal_controls_spec.js
+++ b/spec/frontend/ide/components/terminal/terminal_controls_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import ScrollButton from '~/ide/components/jobs/detail/scroll_button.vue';
import TerminalControls from '~/ide/components/terminal/terminal_controls.vue';
@@ -39,27 +40,25 @@ describe('IDE TerminalControls', () => {
);
});
- it('emits "scroll-up" when click up button', () => {
+ it('emits "scroll-up" when click up button', async () => {
factory({ propsData: { canScrollUp: true } });
expect(wrapper.emitted()).toEqual({});
buttons.at(0).vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('scroll-up')).toEqual([[]]);
- });
+ await nextTick();
+ expect(wrapper.emitted('scroll-up')).toEqual([[]]);
});
- it('emits "scroll-down" when click down button', () => {
+ it('emits "scroll-down" when click down button', async () => {
factory({ propsData: { canScrollDown: true } });
expect(wrapper.emitted()).toEqual({});
buttons.at(1).vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('scroll-down')).toEqual([[]]);
- });
+ await nextTick();
+ expect(wrapper.emitted('scroll-down')).toEqual([[]]);
});
});
diff --git a/spec/frontend/ide/components/terminal/view_spec.js b/spec/frontend/ide/components/terminal/view_spec.js
index e97d4d8a73b..49f9513d2ac 100644
--- a/spec/frontend/ide/components/terminal/view_spec.js
+++ b/spec/frontend/ide/components/terminal/view_spec.js
@@ -1,4 +1,5 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
import { TEST_HOST } from 'spec/test_constants';
@@ -9,8 +10,7 @@ import TerminalView from '~/ide/components/terminal/view.vue';
const TEST_HELP_PATH = `${TEST_HOST}/help`;
const TEST_SVG_PATH = `${TEST_HOST}/illustration.svg`;
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('IDE TerminalView', () => {
let state;
@@ -30,7 +30,7 @@ describe('IDE TerminalView', () => {
},
});
- wrapper = shallowMount(TerminalView, { localVue, store });
+ wrapper = shallowMount(TerminalView, { store });
// Uses deferred components, so wait for those to load...
await waitForPromises();
diff --git a/spec/frontend/ide/components/terminal_sync/terminal_sync_status_safe_spec.js b/spec/frontend/ide/components/terminal_sync/terminal_sync_status_safe_spec.js
index 69077ef2c68..f921037d744 100644
--- a/spec/frontend/ide/components/terminal_sync/terminal_sync_status_safe_spec.js
+++ b/spec/frontend/ide/components/terminal_sync/terminal_sync_status_safe_spec.js
@@ -1,10 +1,10 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import TerminalSyncStatus from '~/ide/components/terminal_sync/terminal_sync_status.vue';
import TerminalSyncStatusSafe from '~/ide/components/terminal_sync/terminal_sync_status_safe.vue';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('ide/components/terminal_sync/terminal_sync_status_safe', () => {
let store;
@@ -16,7 +16,6 @@ describe('ide/components/terminal_sync/terminal_sync_status_safe', () => {
});
wrapper = shallowMount(TerminalSyncStatusSafe, {
- localVue,
store,
});
};
diff --git a/spec/frontend/ide/components/terminal_sync/terminal_sync_status_spec.js b/spec/frontend/ide/components/terminal_sync/terminal_sync_status_spec.js
index c916c43d1e2..3a326b08fff 100644
--- a/spec/frontend/ide/components/terminal_sync/terminal_sync_status_spec.js
+++ b/spec/frontend/ide/components/terminal_sync/terminal_sync_status_spec.js
@@ -1,5 +1,6 @@
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import TerminalSyncStatus from '~/ide/components/terminal_sync/terminal_sync_status.vue';
import {
@@ -11,8 +12,7 @@ import {
const TEST_MESSAGE = 'lorem ipsum dolar sit';
const START_LOADING = 'START_LOADING';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('ide/components/terminal_sync/terminal_sync_status', () => {
let moduleState;
@@ -35,7 +35,6 @@ describe('ide/components/terminal_sync/terminal_sync_status', () => {
});
wrapper = shallowMount(TerminalSyncStatus, {
- localVue,
store,
});
};
diff --git a/spec/frontend/ide/lib/common/model_spec.js b/spec/frontend/ide/lib/common/model_spec.js
index 51df1e2e42f..5d1623429c0 100644
--- a/spec/frontend/ide/lib/common/model_spec.js
+++ b/spec/frontend/ide/lib/common/model_spec.js
@@ -81,16 +81,13 @@ describe('Multi-file editor library model', () => {
});
describe('onChange', () => {
- it('calls callback on change', (done) => {
+ it('calls callback on change', () => {
const spy = jest.fn();
model.onChange(spy);
model.getModel().setValue('123');
- setImmediate(() => {
- expect(spy).toHaveBeenCalledWith(model, expect.anything());
- done();
- });
+ expect(spy).toHaveBeenCalledWith(model, expect.anything());
});
});
diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js
index 6b94d7cf6f1..45d1beea3f8 100644
--- a/spec/frontend/ide/stores/actions/file_spec.js
+++ b/spec/frontend/ide/stores/actions/file_spec.js
@@ -1,5 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
-import Vue from 'vue';
+import { nextTick } from 'vue';
import eventHub from '~/ide/eventhub';
import { createRouter } from '~/ide/ide_router';
import service from '~/ide/services';
@@ -68,7 +68,7 @@ describe('IDE store file actions', () => {
return store
.dispatch('closeFile', localFile)
- .then(Vue.nextTick)
+ .then(nextTick)
.then(() => {
expect(store.state.openFiles.length).toBe(0);
expect(store.state.changedFiles.length).toBe(1);
@@ -83,7 +83,7 @@ describe('IDE store file actions', () => {
return store
.dispatch('closeFile', localFile)
- .then(Vue.nextTick)
+ .then(nextTick)
.then(() => {
expect(router.push).toHaveBeenCalledWith('/project/test/test/tree/main/-/newOpenFile/');
});
diff --git a/spec/frontend/ide/stores/actions_spec.js b/spec/frontend/ide/stores/actions_spec.js
index e575667b8c6..be43c618095 100644
--- a/spec/frontend/ide/stores/actions_spec.js
+++ b/spec/frontend/ide/stores/actions_spec.js
@@ -274,24 +274,17 @@ describe('Multi-file store actions', () => {
});
describe('scrollToTab', () => {
- it('focuses the current active element', (done) => {
+ it('focuses the current active element', () => {
document.body.innerHTML +=
'<div id="tabs"><div class="active"><div class="repo-tab"></div></div></div>';
const el = document.querySelector('.repo-tab');
jest.spyOn(el, 'focus').mockImplementation();
- store
- .dispatch('scrollToTab')
- .then(() => {
- setImmediate(() => {
- expect(el.focus).toHaveBeenCalled();
+ return store.dispatch('scrollToTab').then(() => {
+ expect(el.focus).toHaveBeenCalled();
- document.getElementById('tabs').remove();
-
- done();
- });
- })
- .catch(done.fail);
+ document.getElementById('tabs').remove();
+ });
});
});
diff --git a/spec/frontend/ide/stores/modules/clientside/actions_spec.js b/spec/frontend/ide/stores/modules/clientside/actions_spec.js
index 88d7a630a90..d2777623b0d 100644
--- a/spec/frontend/ide/stores/modules/clientside/actions_spec.js
+++ b/spec/frontend/ide/stores/modules/clientside/actions_spec.js
@@ -1,11 +1,12 @@
import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
import testAction from 'helpers/vuex_action_helper';
+import { PING_USAGE_PREVIEW_KEY } from '~/ide/constants';
import * as actions from '~/ide/stores/modules/clientside/actions';
import axios from '~/lib/utils/axios_utils';
const TEST_PROJECT_URL = `${TEST_HOST}/lorem/ipsum`;
-const TEST_USAGE_URL = `${TEST_PROJECT_URL}/service_ping/web_ide_clientside_preview`;
+const TEST_USAGE_URL = `${TEST_PROJECT_URL}/service_ping/${PING_USAGE_PREVIEW_KEY}`;
describe('IDE store module clientside actions', () => {
let rootGetters;
@@ -30,7 +31,7 @@ describe('IDE store module clientside actions', () => {
mock.onPost(TEST_USAGE_URL).reply(() => usageSpy());
- testAction(actions.pingUsage, null, rootGetters, [], [], () => {
+ testAction(actions.pingUsage, PING_USAGE_PREVIEW_KEY, rootGetters, [], [], () => {
expect(usageSpy).toHaveBeenCalled();
done();
});
diff --git a/spec/frontend/ide/stores/plugins/terminal_spec.js b/spec/frontend/ide/stores/plugins/terminal_spec.js
index d4cdad16ecb..912de88cb39 100644
--- a/spec/frontend/ide/stores/plugins/terminal_spec.js
+++ b/spec/frontend/ide/stores/plugins/terminal_spec.js
@@ -1,4 +1,4 @@
-import { createLocalVue } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import { TEST_HOST } from 'helpers/test_constants';
import terminalModule from '~/ide/stores/modules/terminal';
@@ -11,8 +11,7 @@ const TEST_DATASET = {
eeWebTerminalConfigHelpPath: `${TEST_HOST}/web/terminal/config/help`,
eeWebTerminalRunnersHelpPath: `${TEST_HOST}/web/terminal/runners/help`,
};
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('ide/stores/extend', () => {
let store;
diff --git a/spec/frontend/image_diff/helpers/badge_helper_spec.js b/spec/frontend/image_diff/helpers/badge_helper_spec.js
index c970ccc535d..9ac6ebf6fdb 100644
--- a/spec/frontend/image_diff/helpers/badge_helper_spec.js
+++ b/spec/frontend/image_diff/helpers/badge_helper_spec.js
@@ -62,7 +62,10 @@ describe('badge helper', () => {
});
it('should add badge classes', () => {
- expect(buttonEl.className).toContain('badge badge-pill');
+ const classes = buttonEl.className.split(' ');
+ expect(classes).toEqual(
+ expect.arrayContaining(['design-note-pin', 'on-image', 'gl-absolute']),
+ );
});
it('should set the badge text', () => {
@@ -105,7 +108,7 @@ describe('badge helper', () => {
beforeEach(() => {
containerEl.innerHTML = `
<div id="${noteId}">
- <div class="badge hidden">
+ <div class="design-note-pin hidden">
</div>
</div>
`;
@@ -116,7 +119,7 @@ describe('badge helper', () => {
badgeNumber,
},
});
- avatarBadgeEl = containerEl.querySelector(`#${noteId} .badge`);
+ avatarBadgeEl = containerEl.querySelector(`#${noteId} .design-note-pin`);
});
it('should update badge number', () => {
diff --git a/spec/frontend/image_diff/helpers/dom_helper_spec.js b/spec/frontend/image_diff/helpers/dom_helper_spec.js
index 9357d626bbe..1c5f1cbe3da 100644
--- a/spec/frontend/image_diff/helpers/dom_helper_spec.js
+++ b/spec/frontend/image_diff/helpers/dom_helper_spec.js
@@ -37,14 +37,16 @@ describe('domHelper', () => {
discussionEl = document.createElement('div');
discussionEl.innerHTML = `
<a href="#" class="image-diff-avatar-link">
- <div class="badge"></div>
+ <div class="design-note-pin"></div>
</a>
`;
domHelper.updateDiscussionAvatarBadgeNumber(discussionEl, badgeNumber);
});
it('should update avatar badge number', () => {
- expect(discussionEl.querySelector('.badge').textContent).toEqual(badgeNumber.toString());
+ expect(discussionEl.querySelector('.design-note-pin').textContent).toEqual(
+ badgeNumber.toString(),
+ );
});
});
@@ -54,13 +56,15 @@ describe('domHelper', () => {
beforeEach(() => {
discussionEl = document.createElement('div');
discussionEl.innerHTML = `
- <div class="badge"></div>
+ <div class="design-note-pin"></div>
`;
domHelper.updateDiscussionBadgeNumber(discussionEl, badgeNumber);
});
it('should update discussion badge number', () => {
- expect(discussionEl.querySelector('.badge').textContent).toEqual(badgeNumber.toString());
+ expect(discussionEl.querySelector('.design-note-pin').textContent).toEqual(
+ badgeNumber.toString(),
+ );
});
});
diff --git a/spec/frontend/image_diff/image_diff_spec.js b/spec/frontend/image_diff/image_diff_spec.js
index 16d19f45496..710aa7108a8 100644
--- a/spec/frontend/image_diff/image_diff_spec.js
+++ b/spec/frontend/image_diff/image_diff_spec.js
@@ -15,9 +15,9 @@ describe('ImageDiff', () => {
<div class="js-image-frame">
<img src="${TEST_HOST}/image.png">
<div class="comment-indicator"></div>
- <div id="badge-1" class="badge">1</div>
- <div id="badge-2" class="badge">2</div>
- <div id="badge-3" class="badge">3</div>
+ <div id="badge-1" class="design-note-pin">1</div>
+ <div id="badge-2" class="design-note-pin">2</div>
+ <div id="badge-3" class="design-note-pin">3</div>
</div>
<div class="note-container">
<div class="discussion-notes">
@@ -335,7 +335,7 @@ describe('ImageDiff', () => {
describe('cascade badge count', () => {
it('should update next imageBadgeEl value', () => {
- const imageBadgeEls = imageDiff.imageFrameEl.querySelectorAll('.badge');
+ const imageBadgeEls = imageDiff.imageFrameEl.querySelectorAll('.design-note-pin');
expect(imageBadgeEls[0].textContent).toEqual('1');
expect(imageBadgeEls[1].textContent).toEqual('2');
diff --git a/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js b/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js
index c6ddce17fe4..52c868e5356 100644
--- a/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js
+++ b/spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js
@@ -1,4 +1,4 @@
-import { InMemoryCache } from 'apollo-cache-inmemory';
+import { InMemoryCache } from '@apollo/client/core';
import MockAdapter from 'axios-mock-adapter';
import { createMockClient } from 'mock-apollo-client';
import waitForPromises from 'helpers/wait_for_promises';
diff --git a/spec/frontend/import_entities/import_groups/graphql/fixtures.js b/spec/frontend/import_entities/import_groups/graphql/fixtures.js
index ed4e343f331..938020e03f0 100644
--- a/spec/frontend/import_entities/import_groups/graphql/fixtures.js
+++ b/spec/frontend/import_entities/import_groups/graphql/fixtures.js
@@ -16,6 +16,7 @@ export const generateFakeEntry = ({ id, status, message, ...rest }) => ({
status === STATUSES.NONE || status === STATUSES.PENDING
? null
: {
+ __typename: clientTypenames.BulkImportProgress,
id,
status,
message: message || '',
diff --git a/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js b/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js
index 0e748baa313..0b12df83cd1 100644
--- a/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js
+++ b/spec/frontend/import_entities/import_projects/components/import_projects_table_spec.js
@@ -1,6 +1,6 @@
import { GlLoadingIcon, GlButton, GlIntersectionObserver, GlFormInput } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { STATUSES } from '~/import_entities/constants';
import ImportProjectsTable from '~/import_entities/import_projects/components/import_projects_table.vue';
@@ -46,8 +46,7 @@ describe('ImportProjectsTable', () => {
filterable,
paginatable,
} = {}) {
- const localVue = createLocalVue();
- localVue.use(Vuex);
+ Vue.use(Vuex);
const store = new Vuex.Store({
state: { ...state(), defaultTargetNamespace: USER_NAMESPACE, ...initialState },
@@ -67,7 +66,6 @@ describe('ImportProjectsTable', () => {
});
wrapper = shallowMount(ImportProjectsTable, {
- localVue,
store,
propsData: {
providerTitle,
diff --git a/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js b/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
index 72640f3d601..c8afa9ea57d 100644
--- a/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
+++ b/spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js
@@ -1,6 +1,6 @@
import { GlBadge, GlButton, GlDropdown } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { STATUSES } from '~/import_entities//constants';
import ImportGroupDropdown from '~/import_entities/components/group_dropdown.vue';
@@ -38,13 +38,11 @@ describe('ProviderRepoTableRow', () => {
};
function mountComponent(props) {
- const localVue = createLocalVue();
- localVue.use(Vuex);
+ Vue.use(Vuex);
const store = initStore();
wrapper = shallowMount(ProviderRepoTableRow, {
- localVue,
store,
propsData: { availableNamespaces, userNamespace, ...props },
});
diff --git a/spec/frontend/incidents/components/incidents_list_spec.js b/spec/frontend/incidents/components/incidents_list_spec.js
index 48545ffd2d6..1be6007d844 100644
--- a/spec/frontend/incidents/components/incidents_list_spec.js
+++ b/spec/frontend/incidents/components/incidents_list_spec.js
@@ -1,5 +1,6 @@
import { GlAlert, GlLoadingIcon, GlTable, GlAvatar, GlEmptyState } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import IncidentsList from '~/incidents/components/incidents_list.vue';
import {
I18N,
@@ -210,7 +211,7 @@ describe('Incidents List', () => {
it('sets button loading on click', async () => {
findCreateIncidentBtn().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findCreateIncidentBtn().attributes('loading')).toBe('true');
});
@@ -233,7 +234,7 @@ describe('Incidents List', () => {
it('should track create new incident button', async () => {
findCreateIncidentBtn().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(Tracking.event).toHaveBeenCalled();
});
});
@@ -263,10 +264,10 @@ describe('Incidents List', () => {
const columnHeader = () => wrapper.find(`[${attr}="${value}"]`);
expect(columnHeader().attributes('aria-sort')).toBe(initialSort);
columnHeader().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(columnHeader().attributes('aria-sort')).toBe(firstSort);
columnHeader().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(columnHeader().attributes('aria-sort')).toBe(nextSort);
},
);
@@ -287,7 +288,7 @@ describe('Incidents List', () => {
it('should track incident creation events', async () => {
findCreateIncidentBtn().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
const { category, action } = trackIncidentCreateNewOptions;
expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
diff --git a/spec/frontend/incidents/mocks/incidents.json b/spec/frontend/incidents/mocks/incidents.json
index 78783a0dce5..357b94e5b6c 100644
--- a/spec/frontend/incidents/mocks/incidents.json
+++ b/spec/frontend/incidents/mocks/incidents.json
@@ -1,5 +1,6 @@
[
{
+ "id": 1,
"iid": "15",
"title": "New: Alert",
"createdAt": "2020-06-03T15:46:08Z",
@@ -9,6 +10,7 @@
"slaDueAt": "2020-06-04T12:46:08Z"
},
{
+ "id": 2,
"iid": "14",
"title": "Create issue4",
"createdAt": "2020-05-19T09:26:07Z",
@@ -27,6 +29,7 @@
"slaDueAt": null
},
{
+ "id": 3,
"iid": "13",
"title": "Create issue3",
"createdAt": "2020-05-19T08:53:55Z",
@@ -35,6 +38,7 @@
"severity": "LOW"
},
{
+ "id": 4,
"iid": "12",
"title": "Create issue2",
"createdAt": "2020-05-18T17:13:35Z",
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap
index 1e3c344ce65..0f042dfaa4c 100644
--- a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap
+++ b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap
@@ -40,7 +40,6 @@ exports[`IncidentsSettingTabs should render the component 1`] = `
>
<gl-tabs-stub
queryparamname="tab"
- theme="indigo"
value="0"
>
<!---->
diff --git a/spec/frontend/integrations/edit/components/active_checkbox_spec.js b/spec/frontend/integrations/edit/components/active_checkbox_spec.js
index 0dc31616166..c335b593f7d 100644
--- a/spec/frontend/integrations/edit/components/active_checkbox_spec.js
+++ b/spec/frontend/integrations/edit/components/active_checkbox_spec.js
@@ -1,6 +1,7 @@
import { GlFormCheckbox } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import ActiveCheckbox from '~/integrations/edit/components/active_checkbox.vue';
import { createStore } from '~/integrations/edit/store';
@@ -68,7 +69,7 @@ describe('ActiveCheckbox', () => {
it('switches the form value', async () => {
findInputInCheckbox().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findGlFormCheckbox().vm.$attrs.checked).toBe(false);
});
});
diff --git a/spec/frontend/integrations/edit/components/confirmation_modal_spec.js b/spec/frontend/integrations/edit/components/confirmation_modal_spec.js
index 805d3971994..cbe3402727a 100644
--- a/spec/frontend/integrations/edit/components/confirmation_modal_spec.js
+++ b/spec/frontend/integrations/edit/components/confirmation_modal_spec.js
@@ -1,6 +1,7 @@
import { GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import ConfirmationModal from '~/integrations/edit/components/confirmation_modal.vue';
import { createStore } from '~/integrations/edit/store';
@@ -40,7 +41,7 @@ describe('ConfirmationModal', () => {
findGlModal().vm.$emit('primary');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted().submit).toHaveLength(1);
});
diff --git a/spec/frontend/integrations/edit/components/dynamic_field_spec.js b/spec/frontend/integrations/edit/components/dynamic_field_spec.js
index b0fb94d2b29..ee2f6541b03 100644
--- a/spec/frontend/integrations/edit/components/dynamic_field_spec.js
+++ b/spec/frontend/integrations/edit/components/dynamic_field_spec.js
@@ -2,22 +2,14 @@ import { GlFormGroup, GlFormCheckbox, GlFormInput, GlFormSelect, GlFormTextarea
import { mount } from '@vue/test-utils';
import DynamicField from '~/integrations/edit/components/dynamic_field.vue';
+import { mockField } from '../mock_data';
describe('DynamicField', () => {
let wrapper;
- const defaultProps = {
- help: 'The URL of the project',
- name: 'project_url',
- placeholder: 'https://jira.example.com',
- title: 'Project URL',
- type: 'text',
- value: '1',
- };
-
const createComponent = (props, isInheriting = false) => {
wrapper = mount(DynamicField, {
- propsData: { ...defaultProps, ...props },
+ propsData: { ...mockField, ...props },
computed: {
isInheriting: () => isInheriting,
},
@@ -61,7 +53,7 @@ describe('DynamicField', () => {
});
it(`renders GlFormCheckbox with correct text content when checkboxLabel is ${checkboxLabel}`, () => {
- expect(findGlFormCheckbox().text()).toContain(checkboxLabel ?? defaultProps.title);
+ expect(findGlFormCheckbox().text()).toContain(checkboxLabel ?? mockField.title);
});
it('does not render other types of input', () => {
@@ -160,7 +152,7 @@ describe('DynamicField', () => {
type: 'text',
id: 'service_project_url',
name: 'service[project_url]',
- placeholder: defaultProps.placeholder,
+ placeholder: mockField.placeholder,
required: 'required',
});
expect(findGlFormInput().attributes('readonly')).toBe(readonly);
@@ -179,7 +171,7 @@ describe('DynamicField', () => {
it('renders description with help text', () => {
createComponent();
- expect(findGlFormGroup().find('small').text()).toBe(defaultProps.help);
+ expect(findGlFormGroup().find('small').text()).toBe(mockField.help);
});
describe('when type is checkbox', () => {
@@ -189,7 +181,7 @@ describe('DynamicField', () => {
});
expect(findGlFormGroup().find('small').exists()).toBe(false);
- expect(findGlFormCheckbox().text()).toContain(defaultProps.help);
+ expect(findGlFormCheckbox().text()).toContain(mockField.help);
});
});
@@ -221,40 +213,36 @@ describe('DynamicField', () => {
it('renders label with title', () => {
createComponent();
- expect(findGlFormGroup().find('label').text()).toBe(defaultProps.title);
+ expect(findGlFormGroup().find('label').text()).toBe(mockField.title);
});
});
- describe('validations', () => {
- describe('password field', () => {
- beforeEach(() => {
+ describe('password field validations', () => {
+ describe('without value', () => {
+ it('requires validation', () => {
createComponent({
type: 'password',
required: true,
value: null,
+ isValidated: true,
});
- wrapper.vm.validated = true;
- });
-
- describe('without value', () => {
- it('requires validation', () => {
- expect(wrapper.vm.valid).toBe(false);
- expect(findGlFormGroup().classes('is-invalid')).toBe(true);
- expect(findGlFormInput().classes('is-invalid')).toBe(true);
- });
+ expect(findGlFormGroup().classes('is-invalid')).toBe(true);
+ expect(findGlFormInput().classes('is-invalid')).toBe(true);
});
+ });
- describe('with value', () => {
- beforeEach(() => {
- wrapper.setProps({ value: 'true' });
+ describe('with value', () => {
+ it('does not require validation', () => {
+ createComponent({
+ type: 'password',
+ required: true,
+ value: 'test value',
+ isValidated: true,
});
- it('does not require validation', () => {
- expect(wrapper.vm.valid).toBe(true);
- expect(findGlFormGroup().classes('is-valid')).toBe(true);
- expect(findGlFormInput().classes('is-valid')).toBe(true);
- });
+ expect(findGlFormGroup().classes('is-valid')).toBe(true);
+ expect(findGlFormInput().classes('is-valid')).toBe(true);
});
});
});
diff --git a/spec/frontend/integrations/edit/components/integration_form_spec.js b/spec/frontend/integrations/edit/components/integration_form_spec.js
index 8cf8a403e5d..7e01b79383a 100644
--- a/spec/frontend/integrations/edit/components/integration_form_spec.js
+++ b/spec/frontend/integrations/edit/components/integration_form_spec.js
@@ -17,16 +17,13 @@ import TriggerFields from '~/integrations/edit/components/trigger_fields.vue';
import {
integrationLevels,
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
- VALIDATE_INTEGRATION_FORM_EVENT,
I18N_DEFAULT_ERROR_MESSAGE,
} from '~/integrations/constants';
import { createStore } from '~/integrations/edit/store';
-import eventHub from '~/integrations/edit/event_hub';
import httpStatus from '~/lib/utils/http_status';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
-import { mockIntegrationProps } from '../mock_data';
+import { mockIntegrationProps, mockField } from '../mock_data';
-jest.mock('~/integrations/edit/event_hub');
jest.mock('@sentry/browser');
jest.mock('~/lib/utils/url_utility');
@@ -36,13 +33,6 @@ describe('IntegrationForm', () => {
let wrapper;
let dispatch;
let mockAxios;
- let mockForm;
- let vueIntegrationFormFeatureFlag;
-
- const createForm = () => {
- mockForm = document.createElement('form');
- jest.spyOn(document, 'querySelector').mockReturnValue(mockForm);
- };
const createComponent = ({
customStateProps = {},
@@ -56,10 +46,6 @@ describe('IntegrationForm', () => {
});
dispatch = jest.spyOn(store, 'dispatch').mockImplementation();
- if (!vueIntegrationFormFeatureFlag) {
- createForm();
- }
-
wrapper = mountFn(IntegrationForm, {
propsData: { ...props },
store,
@@ -75,11 +61,6 @@ describe('IntegrationForm', () => {
show: mockToastShow,
},
},
- provide: {
- glFeatures: {
- vueIntegrationForm: vueIntegrationFormFeatureFlag,
- },
- },
});
};
@@ -96,12 +77,7 @@ describe('IntegrationForm', () => {
const findTriggerFields = () => wrapper.findComponent(TriggerFields);
const findGlForm = () => wrapper.findComponent(GlForm);
const findRedirectToField = () => wrapper.findByTestId('redirect-to-field');
- const findFormElement = () => (vueIntegrationFormFeatureFlag ? findGlForm().element : mockForm);
-
- const mockFormFunctions = ({ checkValidityReturn }) => {
- jest.spyOn(findFormElement(), 'checkValidity').mockReturnValue(checkValidityReturn);
- jest.spyOn(findFormElement(), 'submit');
- };
+ const findDynamicField = () => wrapper.findComponent(DynamicField);
beforeEach(() => {
mockAxios = new MockAdapter(axios);
@@ -357,17 +333,14 @@ describe('IntegrationForm', () => {
});
});
- describe('when `vueIntegrationForm` feature flag is $vueIntegrationFormEnabled', () => {
- it('renders hidden fields', () => {
- vueIntegrationFormFeatureFlag = true;
- createComponent({
- customStateProps: {
- redirectTo: '/services',
- },
- });
-
- expect(findRedirectToField().attributes('value')).toBe('/services');
+ it('renders hidden fields', () => {
+ createComponent({
+ customStateProps: {
+ redirectTo: '/services',
+ },
});
+
+ expect(findRedirectToField().attributes('value')).toBe('/services');
});
});
@@ -389,216 +362,200 @@ describe('IntegrationForm', () => {
});
describe.each`
- formActive | vueIntegrationFormEnabled | novalidate
- ${true} | ${true} | ${null}
- ${false} | ${true} | ${'novalidate'}
- ${true} | ${false} | ${null}
- ${false} | ${false} | ${'true'}
+ formActive | novalidate
+ ${true} | ${undefined}
+ ${false} | ${'true'}
`(
- 'when `vueIntegrationForm` feature flag is $vueIntegrationFormEnabled and `toggle-integration-active` is emitted with $formActive',
- ({ formActive, vueIntegrationFormEnabled, novalidate }) => {
+ 'when `toggle-integration-active` is emitted with $formActive',
+ ({ formActive, novalidate }) => {
beforeEach(async () => {
- vueIntegrationFormFeatureFlag = vueIntegrationFormEnabled;
-
createComponent({
customStateProps: {
showActive: true,
initialActivated: false,
},
- mountFn: mountExtended,
});
- mockFormFunctions({ checkValidityReturn: false });
await findActiveCheckbox().vm.$emit('toggle-integration-active', formActive);
});
it(`sets noValidate to ${novalidate}`, () => {
- expect(findFormElement().getAttribute('novalidate')).toBe(novalidate);
+ expect(findGlForm().attributes('novalidate')).toBe(novalidate);
});
},
);
});
- describe.each`
- vueIntegrationFormEnabled
- ${true}
- ${false}
- `(
- 'when `vueIntegrationForm` feature flag is $vueIntegrationFormEnabled',
- ({ vueIntegrationFormEnabled }) => {
- beforeEach(() => {
- vueIntegrationFormFeatureFlag = vueIntegrationFormEnabled;
- });
-
- describe('when `save` button is clicked', () => {
- describe('buttons', () => {
- beforeEach(async () => {
- createComponent({
- customStateProps: {
- showActive: true,
- canTest: true,
- initialActivated: true,
- },
- mountFn: mountExtended,
- });
-
- await findProjectSaveButton().vm.$emit('click', new Event('click'));
- });
+ describe('when `save` button is clicked', () => {
+ describe('buttons', () => {
+ beforeEach(async () => {
+ createComponent({
+ customStateProps: {
+ showActive: true,
+ canTest: true,
+ initialActivated: true,
+ },
+ mountFn: mountExtended,
+ });
- it('sets save button `loading` prop to `true`', () => {
- expect(findProjectSaveButton().props('loading')).toBe(true);
- });
+ await findProjectSaveButton().vm.$emit('click', new Event('click'));
+ });
- it('sets test button `disabled` prop to `true`', () => {
- expect(findTestButton().props('disabled')).toBe(true);
+ it('sets save button `loading` prop to `true`', () => {
+ expect(findProjectSaveButton().props('loading')).toBe(true);
+ });
+
+ it('sets test button `disabled` prop to `true`', () => {
+ expect(findTestButton().props('disabled')).toBe(true);
+ });
+ });
+
+ describe.each`
+ checkValidityReturn | integrationActive
+ ${true} | ${false}
+ ${true} | ${true}
+ ${false} | ${false}
+ `(
+ 'when form is valid (checkValidity returns $checkValidityReturn and integrationActive is $integrationActive)',
+ ({ integrationActive, checkValidityReturn }) => {
+ beforeEach(async () => {
+ createComponent({
+ customStateProps: {
+ showActive: true,
+ canTest: true,
+ initialActivated: integrationActive,
+ },
+ mountFn: mountExtended,
});
+ jest.spyOn(findGlForm().element, 'submit');
+ jest.spyOn(findGlForm().element, 'checkValidity').mockReturnValue(checkValidityReturn);
+
+ await findProjectSaveButton().vm.$emit('click', new Event('click'));
+ });
+
+ it('submit form', () => {
+ expect(findGlForm().element.submit).toHaveBeenCalledTimes(1);
});
+ },
+ );
- describe.each`
- checkValidityReturn | integrationActive
- ${true} | ${false}
- ${true} | ${true}
- ${false} | ${false}
- `(
- 'when form is valid (checkValidity returns $checkValidityReturn and integrationActive is $integrationActive)',
- ({ integrationActive, checkValidityReturn }) => {
- beforeEach(async () => {
- createComponent({
- customStateProps: {
- showActive: true,
- canTest: true,
- initialActivated: integrationActive,
- },
- mountFn: mountExtended,
- });
-
- mockFormFunctions({ checkValidityReturn });
-
- await findProjectSaveButton().vm.$emit('click', new Event('click'));
- });
-
- it('submits form', () => {
- expect(findFormElement().submit).toHaveBeenCalledTimes(1);
- });
+ describe('when form is invalid (checkValidity returns false and integrationActive is true)', () => {
+ beforeEach(async () => {
+ createComponent({
+ customStateProps: {
+ showActive: true,
+ canTest: true,
+ initialActivated: true,
+ fields: [mockField],
},
- );
-
- describe('when form is invalid (checkValidity returns false and integrationActive is true)', () => {
- beforeEach(async () => {
- createComponent({
- customStateProps: {
- showActive: true,
- canTest: true,
- initialActivated: true,
- },
- mountFn: mountExtended,
- });
- mockFormFunctions({ checkValidityReturn: false });
-
- await findProjectSaveButton().vm.$emit('click', new Event('click'));
- });
+ mountFn: mountExtended,
+ });
+ jest.spyOn(findGlForm().element, 'submit');
+ jest.spyOn(findGlForm().element, 'checkValidity').mockReturnValue(false);
- it('does not submit form', () => {
- expect(findFormElement().submit).not.toHaveBeenCalled();
- });
+ await findProjectSaveButton().vm.$emit('click', new Event('click'));
+ });
- it('sets save button `loading` prop to `false`', () => {
- expect(findProjectSaveButton().props('loading')).toBe(false);
- });
+ it('does not submit form', () => {
+ expect(findGlForm().element.submit).not.toHaveBeenCalled();
+ });
- it('sets test button `disabled` prop to `false`', () => {
- expect(findTestButton().props('disabled')).toBe(false);
- });
+ it('sets save button `loading` prop to `false`', () => {
+ expect(findProjectSaveButton().props('loading')).toBe(false);
+ });
- it('emits `VALIDATE_INTEGRATION_FORM_EVENT`', () => {
- expect(eventHub.$emit).toHaveBeenCalledWith(VALIDATE_INTEGRATION_FORM_EVENT);
- });
+ it('sets test button `disabled` prop to `false`', () => {
+ expect(findTestButton().props('disabled')).toBe(false);
+ });
+
+ it('sets `isValidated` props on form fields', () => {
+ expect(findDynamicField().props('isValidated')).toBe(true);
+ });
+ });
+ });
+
+ describe('when `test` button is clicked', () => {
+ describe('when form is invalid', () => {
+ it('sets `isValidated` props on form fields', async () => {
+ createComponent({
+ customStateProps: {
+ showActive: true,
+ canTest: true,
+ fields: [mockField],
+ },
+ mountFn: mountExtended,
});
+ jest.spyOn(findGlForm().element, 'checkValidity').mockReturnValue(false);
+
+ await findTestButton().vm.$emit('click', new Event('click'));
+
+ expect(findDynamicField().props('isValidated')).toBe(true);
});
+ });
- describe('when `test` button is clicked', () => {
- describe('when form is invalid', () => {
- it('emits `VALIDATE_INTEGRATION_FORM_EVENT` event to the event hub', () => {
- createComponent({
- customStateProps: {
- showActive: true,
- canTest: true,
- },
- mountFn: mountExtended,
- });
- mockFormFunctions({ checkValidityReturn: false });
+ describe('when form is valid', () => {
+ const mockTestPath = '/test';
- findTestButton().vm.$emit('click', new Event('click'));
+ beforeEach(() => {
+ createComponent({
+ customStateProps: {
+ showActive: true,
+ canTest: true,
+ testPath: mockTestPath,
+ },
+ mountFn: mountExtended,
+ });
+ jest.spyOn(findGlForm().element, 'checkValidity').mockReturnValue(true);
+ });
- expect(eventHub.$emit).toHaveBeenCalledWith(VALIDATE_INTEGRATION_FORM_EVENT);
- });
+ describe('buttons', () => {
+ beforeEach(async () => {
+ await findTestButton().vm.$emit('click', new Event('click'));
});
- describe('when form is valid', () => {
- const mockTestPath = '/test';
+ it('sets test button `loading` prop to `true`', () => {
+ expect(findTestButton().props('loading')).toBe(true);
+ });
- beforeEach(() => {
- createComponent({
- customStateProps: {
- showActive: true,
- canTest: true,
- testPath: mockTestPath,
- },
- mountFn: mountExtended,
- });
- mockFormFunctions({ checkValidityReturn: true });
+ it('sets save button `disabled` prop to `true`', () => {
+ expect(findProjectSaveButton().props('disabled')).toBe(true);
+ });
+ });
+
+ describe.each`
+ scenario | replyStatus | errorMessage | expectToast | expectSentry
+ ${'when "test settings" request fails'} | ${httpStatus.INTERNAL_SERVER_ERROR} | ${undefined} | ${I18N_DEFAULT_ERROR_MESSAGE} | ${true}
+ ${'when "test settings" returns an error'} | ${httpStatus.OK} | ${'an error'} | ${'an error'} | ${false}
+ ${'when "test settings" succeeds'} | ${httpStatus.OK} | ${undefined} | ${I18N_SUCCESSFUL_CONNECTION_MESSAGE} | ${false}
+ `('$scenario', ({ replyStatus, errorMessage, expectToast, expectSentry }) => {
+ beforeEach(async () => {
+ mockAxios.onPut(mockTestPath).replyOnce(replyStatus, {
+ error: Boolean(errorMessage),
+ message: errorMessage,
});
- describe('buttons', () => {
- beforeEach(async () => {
- await findTestButton().vm.$emit('click', new Event('click'));
- });
+ await findTestButton().vm.$emit('click', new Event('click'));
+ await waitForPromises();
+ });
- it('sets test button `loading` prop to `true`', () => {
- expect(findTestButton().props('loading')).toBe(true);
- });
+ it(`calls toast with '${expectToast}'`, () => {
+ expect(mockToastShow).toHaveBeenCalledWith(expectToast);
+ });
- it('sets save button `disabled` prop to `true`', () => {
- expect(findProjectSaveButton().props('disabled')).toBe(true);
- });
- });
+ it('sets `loading` prop of test button to `false`', () => {
+ expect(findTestButton().props('loading')).toBe(false);
+ });
- describe.each`
- scenario | replyStatus | errorMessage | expectToast | expectSentry
- ${'when "test settings" request fails'} | ${httpStatus.INTERNAL_SERVER_ERROR} | ${undefined} | ${I18N_DEFAULT_ERROR_MESSAGE} | ${true}
- ${'when "test settings" returns an error'} | ${httpStatus.OK} | ${'an error'} | ${'an error'} | ${false}
- ${'when "test settings" succeeds'} | ${httpStatus.OK} | ${undefined} | ${I18N_SUCCESSFUL_CONNECTION_MESSAGE} | ${false}
- `('$scenario', ({ replyStatus, errorMessage, expectToast, expectSentry }) => {
- beforeEach(async () => {
- mockAxios.onPut(mockTestPath).replyOnce(replyStatus, {
- error: Boolean(errorMessage),
- message: errorMessage,
- });
-
- await findTestButton().vm.$emit('click', new Event('click'));
- await waitForPromises();
- });
-
- it(`calls toast with '${expectToast}'`, () => {
- expect(mockToastShow).toHaveBeenCalledWith(expectToast);
- });
-
- it('sets `loading` prop of test button to `false`', () => {
- expect(findTestButton().props('loading')).toBe(false);
- });
-
- it('sets save button `disabled` prop to `false`', () => {
- expect(findProjectSaveButton().props('disabled')).toBe(false);
- });
-
- it(`${expectSentry ? 'does' : 'does not'} capture exception in Sentry`, () => {
- expect(Sentry.captureException).toHaveBeenCalledTimes(expectSentry ? 1 : 0);
- });
- });
+ it('sets save button `disabled` prop to `false`', () => {
+ expect(findProjectSaveButton().props('disabled')).toBe(false);
+ });
+
+ it(`${expectSentry ? 'does' : 'does not'} capture exception in Sentry`, () => {
+ expect(Sentry.captureException).toHaveBeenCalledTimes(expectSentry ? 1 : 0);
});
});
- },
- );
+ });
+ });
describe('when `reset-confirmation-modal` emits `reset` event', () => {
const mockResetPath = '/reset';
diff --git a/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js b/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js
index b5a8eed3598..33fd08a5959 100644
--- a/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js
+++ b/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js
@@ -1,9 +1,8 @@
import { GlFormCheckbox, GlFormInput } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { VALIDATE_INTEGRATION_FORM_EVENT } from '~/integrations/constants';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
-import eventHub from '~/integrations/edit/event_hub';
import { createStore } from '~/integrations/edit/store';
describe('JiraIssuesFields', () => {
@@ -195,7 +194,7 @@ describe('JiraIssuesFields', () => {
await setEnableCheckbox(true);
expect(findJiraForVulnerabilities().attributes('show-full-feature')).toBe('true');
wrapper.setProps({ showJiraVulnerabilitiesIntegration: false });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findJiraForVulnerabilities().attributes('show-full-feature')).toBeUndefined();
});
@@ -222,7 +221,7 @@ describe('JiraIssuesFields', () => {
});
describe('Project key input field', () => {
- beforeEach(() => {
+ it('sets Project Key `state` attribute to `true` by default', () => {
createComponent({
props: {
initialProjectKey: '',
@@ -230,29 +229,32 @@ describe('JiraIssuesFields', () => {
},
mountFn: shallowMountExtended,
});
- });
- it('sets Project Key `state` attribute to `true` by default', () => {
assertProjectKeyState('true');
});
- describe('when event hub recieves `VALIDATE_INTEGRATION_FORM_EVENT` event', () => {
+ describe('when `isValidated` prop is true', () => {
+ beforeEach(() => {
+ createComponent({
+ props: {
+ initialProjectKey: '',
+ initialEnableJiraIssues: true,
+ isValidated: true,
+ },
+ mountFn: shallowMountExtended,
+ });
+ });
+
describe('with no project key', () => {
it('sets Project Key `state` attribute to `undefined`', async () => {
- eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT);
- await wrapper.vm.$nextTick();
-
assertProjectKeyState(undefined);
});
});
describe('when project key is set', () => {
it('sets Project Key `state` attribute to `true`', async () => {
- eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT);
-
// set the project key
await findProjectKey().vm.$emit('input', 'AB');
- await wrapper.vm.$nextTick();
assertProjectKeyState('true');
});
diff --git a/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js b/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js
index 9e01371f542..49fbebb9396 100644
--- a/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js
+++ b/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js
@@ -1,4 +1,5 @@
import { GlFormCheckbox } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
@@ -71,12 +72,11 @@ describe('JiraTriggerFields', () => {
});
describe('on enable comments', () => {
- it('shows comment detail', () => {
+ it('shows comment detail', async () => {
findCommentSettingsCheckbox().vm.$emit('input', true);
- return wrapper.vm.$nextTick().then(() => {
- expect(findCommentDetail().isVisible()).toBe(true);
- });
+ await nextTick();
+ expect(findCommentDetail().isVisible()).toBe(true);
});
});
});
@@ -107,7 +107,7 @@ describe('JiraTriggerFields', () => {
});
describe('initialJiraIssueTransitionAutomatic is false, initialJiraIssueTransitionId is not set', () => {
- it('selects automatic transitions when enabling transitions', () => {
+ it('selects automatic transitions when enabling transitions', async () => {
createComponent({
initialTriggerCommit: true,
initialEnableComments: true,
@@ -117,11 +117,10 @@ describe('JiraTriggerFields', () => {
expect(checkbox.element.checked).toBe(false);
checkbox.trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- const [radio1, radio2] = findIssueTransitionModeRadios().wrappers;
- expect(radio1.element.checked).toBe(true);
- expect(radio2.element.checked).toBe(false);
- });
+ await nextTick();
+ const [radio1, radio2] = findIssueTransitionModeRadios().wrappers;
+ expect(radio1.element.checked).toBe(true);
+ expect(radio2.element.checked).toBe(false);
});
});
diff --git a/spec/frontend/integrations/edit/mock_data.js b/spec/frontend/integrations/edit/mock_data.js
index 3c45ed0fb1b..39e5f8521e8 100644
--- a/spec/frontend/integrations/edit/mock_data.js
+++ b/spec/frontend/integrations/edit/mock_data.js
@@ -20,3 +20,12 @@ export const mockJiraIssueTypes = [
{ id: '2', name: 'bug', description: 'bug' },
{ id: '3', name: 'epic', description: 'epic' },
];
+
+export const mockField = {
+ help: 'The URL of the project',
+ name: 'project_url',
+ placeholder: 'https://jira.example.com',
+ title: 'Project URL',
+ type: 'text',
+ value: '1',
+};
diff --git a/spec/frontend/invite_members/components/group_select_spec.js b/spec/frontend/invite_members/components/group_select_spec.js
index 2ef8fe07650..192f3fdd381 100644
--- a/spec/frontend/invite_members/components/group_select_spec.js
+++ b/spec/frontend/invite_members/components/group_select_spec.js
@@ -4,14 +4,21 @@ import waitForPromises from 'helpers/wait_for_promises';
import * as groupsApi from '~/api/groups_api';
import GroupSelect from '~/invite_members/components/group_select.vue';
-const createComponent = () => {
- return mount(GroupSelect, {});
-};
-
+const accessLevels = { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 };
const group1 = { id: 1, full_name: 'Group One', avatar_url: 'test' };
const group2 = { id: 2, full_name: 'Group Two', avatar_url: 'test' };
const allGroups = [group1, group2];
+const createComponent = (props = {}) => {
+ return mount(GroupSelect, {
+ propsData: {
+ invalidGroups: [],
+ accessLevels,
+ ...props,
+ },
+ });
+};
+
describe('GroupSelect', () => {
let wrapper;
@@ -61,6 +68,7 @@ describe('GroupSelect', () => {
expect(groupsApi.getGroups).toHaveBeenCalledWith(group1.name, {
active: true,
exclude_internal: true,
+ min_access_level: accessLevels.Guest,
});
});
@@ -83,6 +91,20 @@ describe('GroupSelect', () => {
size: '32',
});
});
+
+ describe('when filtering out the group from results', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ invalidGroups: [group1.id] });
+ });
+
+ it('does not find an invalid group', () => {
+ expect(findAvatarByLabel(group1.full_name)).toBe(undefined);
+ });
+
+ it('finds a group that is valid', () => {
+ expect(findAvatarByLabel(group2.full_name).exists()).toBe(true);
+ });
+ });
});
describe('when group is selected from the dropdown', () => {
diff --git a/spec/frontend/invite_members/components/import_a_project_modal_spec.js b/spec/frontend/invite_members/components/import_a_project_modal_spec.js
index fecbf84fb57..6db881d5c75 100644
--- a/spec/frontend/invite_members/components/import_a_project_modal_spec.js
+++ b/spec/frontend/invite_members/components/import_a_project_modal_spec.js
@@ -1,5 +1,6 @@
import { GlFormGroup, GlSprintf, GlModal } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import { stubComponent } from 'helpers/stub_component';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -91,7 +92,7 @@ describe('ImportAProjectModal', () => {
it('sets isLoading to true when the Invite button is clicked', async () => {
clickImportButton();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findImportButton().props('loading')).toBe(true);
});
@@ -157,7 +158,7 @@ describe('ImportAProjectModal', () => {
clickCancelButton();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(formGroupInvalidFeedback()).toBe('');
expect(formGroupErrorState()).not.toBe(false);
diff --git a/spec/frontend/invite_members/components/invite_group_trigger_spec.js b/spec/frontend/invite_members/components/invite_group_trigger_spec.js
index cb9967ebe8c..84ddb779a9e 100644
--- a/spec/frontend/invite_members/components/invite_group_trigger_spec.js
+++ b/spec/frontend/invite_members/components/invite_group_trigger_spec.js
@@ -44,7 +44,7 @@ describe('InviteGroupTrigger', () => {
});
it('emits event that triggers opening the modal', () => {
- expect(eventHub.$emit).toHaveBeenLastCalledWith('openModal', { inviteeType: 'group' });
+ expect(eventHub.$emit).toHaveBeenLastCalledWith('openGroupModal');
});
});
});
diff --git a/spec/frontend/invite_members/components/invite_groups_modal_spec.js b/spec/frontend/invite_members/components/invite_groups_modal_spec.js
new file mode 100644
index 00000000000..49c55d56080
--- /dev/null
+++ b/spec/frontend/invite_members/components/invite_groups_modal_spec.js
@@ -0,0 +1,143 @@
+import { GlModal, GlSprintf } from '@gitlab/ui';
+import { nextTick } from 'vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import Api from '~/api';
+import InviteGroupsModal from '~/invite_members/components/invite_groups_modal.vue';
+import InviteModalBase from '~/invite_members/components/invite_modal_base.vue';
+import GroupSelect from '~/invite_members/components/group_select.vue';
+import { stubComponent } from 'helpers/stub_component';
+import { propsData, sharedGroup } from '../mock_data/group_modal';
+
+describe('InviteGroupsModal', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMountExtended(InviteGroupsModal, {
+ propsData: {
+ ...propsData,
+ ...props,
+ },
+ stubs: {
+ InviteModalBase,
+ GlSprintf,
+ GlModal: stubComponent(GlModal, {
+ template: '<div><slot></slot><slot name="modal-footer"></slot></div>',
+ }),
+ },
+ });
+ };
+
+ const createInviteGroupToProjectWrapper = () => {
+ createComponent({ isProject: true });
+ };
+
+ const createInviteGroupToGroupWrapper = () => {
+ createComponent({ isProject: false });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findGroupSelect = () => wrapper.findComponent(GroupSelect);
+ const findIntroText = () => wrapper.findByTestId('modal-base-intro-text').text();
+ const findCancelButton = () => wrapper.findByTestId('cancel-button');
+ const findInviteButton = () => wrapper.findByTestId('invite-button');
+ const findMembersFormGroup = () => wrapper.findByTestId('members-form-group');
+ const membersFormGroupInvalidFeedback = () =>
+ findMembersFormGroup().attributes('invalid-feedback');
+ const clickInviteButton = () => findInviteButton().vm.$emit('click');
+ const clickCancelButton = () => findCancelButton().vm.$emit('click');
+ const triggerGroupSelect = (val) => findGroupSelect().vm.$emit('input', val);
+
+ describe('displaying the correct introText and form group description', () => {
+ describe('when inviting to a project', () => {
+ it('includes the correct type, and formatted intro text', () => {
+ createInviteGroupToProjectWrapper();
+
+ expect(findIntroText()).toBe("You're inviting a group to the test name project.");
+ });
+ });
+
+ describe('when inviting to a group', () => {
+ it('includes the correct type, and formatted intro text', () => {
+ createInviteGroupToGroupWrapper();
+
+ expect(findIntroText()).toBe("You're inviting a group to the test name group.");
+ });
+ });
+ });
+
+ describe('submitting the invite form', () => {
+ describe('when sharing the group is successful', () => {
+ const groupPostData = {
+ group_id: sharedGroup.id,
+ group_access: propsData.defaultAccessLevel,
+ expires_at: undefined,
+ format: 'json',
+ };
+
+ beforeEach(() => {
+ createComponent();
+ triggerGroupSelect(sharedGroup);
+
+ wrapper.vm.$toast = { show: jest.fn() };
+ jest.spyOn(Api, 'groupShareWithGroup').mockResolvedValue({ data: groupPostData });
+
+ clickInviteButton();
+ });
+
+ it('calls Api groupShareWithGroup with the correct params', () => {
+ expect(Api.groupShareWithGroup).toHaveBeenCalledWith(propsData.id, groupPostData);
+ });
+
+ it('displays the successful toastMessage', () => {
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('Members were successfully added', {
+ onComplete: expect.any(Function),
+ });
+ });
+ });
+
+ describe('when sharing the group fails', () => {
+ beforeEach(() => {
+ createInviteGroupToGroupWrapper();
+ triggerGroupSelect(sharedGroup);
+
+ wrapper.vm.$toast = { show: jest.fn() };
+
+ jest
+ .spyOn(Api, 'groupShareWithGroup')
+ .mockRejectedValue({ response: { data: { success: false } } });
+
+ clickInviteButton();
+ });
+
+ it('does not show the toast message on failure', () => {
+ expect(wrapper.vm.$toast.show).not.toHaveBeenCalled();
+ });
+
+ it('displays the generic error for http server error', () => {
+ expect(membersFormGroupInvalidFeedback()).toBe('Something went wrong');
+ });
+
+ describe('clearing the invalid state and message', () => {
+ it('clears the error when the cancel button is clicked', async () => {
+ clickCancelButton();
+
+ await nextTick();
+
+ expect(membersFormGroupInvalidFeedback()).toBe('');
+ });
+
+ it('clears the error when the modal is hidden', async () => {
+ wrapper.findComponent(GlModal).vm.$emit('hide');
+
+ await nextTick();
+
+ expect(membersFormGroupInvalidFeedback()).toBe('');
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/invite_members/components/invite_members_modal_spec.js b/spec/frontend/invite_members/components/invite_members_modal_spec.js
index 3ab89b3dff2..15a366474e4 100644
--- a/spec/frontend/invite_members/components/invite_members_modal_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_modal_spec.js
@@ -1,28 +1,19 @@
-import {
- GlDropdown,
- GlDropdownItem,
- GlDatepicker,
- GlFormGroup,
- GlSprintf,
- GlLink,
- GlModal,
-} from '@gitlab/ui';
+import { GlLink, GlModal, GlSprintf } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import { stubComponent } from 'helpers/stub_component';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
import ExperimentTracking from '~/experimentation/experiment_tracking';
import InviteMembersModal from '~/invite_members/components/invite_members_modal.vue';
+import InviteModalBase from '~/invite_members/components/invite_modal_base.vue';
import ModalConfetti from '~/invite_members/components/confetti.vue';
import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
import {
INVITE_MEMBERS_FOR_TASK,
- CANCEL_BUTTON_TEXT,
- INVITE_BUTTON_TEXT,
MEMBERS_MODAL_CELEBRATE_INTRO,
MEMBERS_MODAL_CELEBRATE_TITLE,
- MEMBERS_MODAL_DEFAULT_TITLE,
MEMBERS_PLACEHOLDER,
MEMBERS_TO_PROJECT_CELEBRATE_INTRO_TEXT,
LEARN_GITLAB,
@@ -32,9 +23,16 @@ import axios from '~/lib/utils/axios_utils';
import httpStatus from '~/lib/utils/http_status';
import { getParameterValues } from '~/lib/utils/url_utility';
import { apiPaths, membersApiResponse, invitationsApiResponse } from '../mock_data/api_responses';
-
-let wrapper;
-let mock;
+import {
+ propsData,
+ inviteSource,
+ newProjectPath,
+ user1,
+ user2,
+ user3,
+ user4,
+ GlEmoji,
+} from '../mock_data/member_modal';
jest.mock('~/experimentation/experiment_tracking');
jest.mock('~/lib/utils/url_utility', () => ({
@@ -42,211 +40,125 @@ jest.mock('~/lib/utils/url_utility', () => ({
getParameterValues: jest.fn(() => []),
}));
-const id = '1';
-const name = 'test name';
-const isProject = false;
-const inviteeType = 'members';
-const accessLevels = { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 };
-const defaultAccessLevel = 10;
-const inviteSource = 'unknown';
-const helpLink = 'https://example.com';
-const tasksToBeDoneOptions = [
- { text: 'First task', value: 'first' },
- { text: 'Second task', value: 'second' },
-];
-const newProjectPath = 'projects/new';
-const projects = [
- { text: 'First project', value: '1' },
- { text: 'Second project', value: '2' },
-];
-
-const user1 = { id: 1, name: 'Name One', username: 'one_1', avatar_url: '' };
-const user2 = { id: 2, name: 'Name Two', username: 'one_2', avatar_url: '' };
-const user3 = {
- id: 'user-defined-token',
- name: 'email@example.com',
- username: 'one_2',
- avatar_url: '',
-};
-const user4 = {
- id: 'user-defined-token',
- name: 'email4@example.com',
- username: 'one_4',
- avatar_url: '',
-};
-const sharedGroup = { id: '981' };
-const GlEmoji = { template: '<img/>' };
-
-const createComponent = (data = {}, props = {}) => {
- wrapper = shallowMountExtended(InviteMembersModal, {
- provide: {
- newProjectPath,
- },
- propsData: {
- id,
- name,
- isProject,
- inviteeType,
- accessLevels,
- defaultAccessLevel,
- tasksToBeDoneOptions,
- projects,
- helpLink,
- ...props,
- },
- data() {
- return data;
- },
- stubs: {
- GlModal: stubComponent(GlModal, {
- template:
- '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>',
- }),
- GlDropdown: true,
- GlDropdownItem: true,
- GlEmoji,
- GlSprintf,
- GlFormGroup: stubComponent(GlFormGroup, {
- props: ['state', 'invalidFeedback', 'description'],
- }),
- },
- });
-};
-
-const createInviteMembersToProjectWrapper = () => {
- createComponent({ inviteeType: 'members' }, { isProject: true });
-};
-
-const createInviteMembersToGroupWrapper = () => {
- createComponent({ inviteeType: 'members' }, { isProject: false });
-};
+describe('InviteMembersModal', () => {
+ let wrapper;
+ let mock;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMountExtended(InviteMembersModal, {
+ provide: {
+ newProjectPath,
+ },
+ propsData: {
+ ...propsData,
+ ...props,
+ },
+ stubs: {
+ InviteModalBase,
+ GlSprintf,
+ GlModal: stubComponent(GlModal, {
+ template: '<div><slot></slot><slot name="modal-footer"></slot></div>',
+ }),
+ GlEmoji,
+ },
+ });
+ };
-const createInviteGroupToProjectWrapper = () => {
- createComponent({ inviteeType: 'group' }, { isProject: true });
-};
+ const createInviteMembersToProjectWrapper = () => {
+ createComponent({ isProject: true });
+ };
-const createInviteGroupToGroupWrapper = () => {
- createComponent({ inviteeType: 'group' }, { isProject: false });
-};
+ const createInviteMembersToGroupWrapper = () => {
+ createComponent({ isProject: false });
+ };
-beforeEach(() => {
- gon.api_version = 'v4';
- mock = new MockAdapter(axios);
-});
+ beforeEach(() => {
+ gon.api_version = 'v4';
+ mock = new MockAdapter(axios);
+ });
-afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- mock.restore();
-});
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ mock.restore();
+ });
-describe('InviteMembersModal', () => {
- const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findDropdownItems = () => findDropdown().findAllComponents(GlDropdownItem);
- const findDatepicker = () => wrapper.findComponent(GlDatepicker);
- const findLink = () => wrapper.findComponent(GlLink);
- const findIntroText = () => wrapper.find({ ref: 'introText' }).text();
+ const findBase = () => wrapper.findComponent(InviteModalBase);
+ const findIntroText = () => wrapper.findByTestId('modal-base-intro-text').text();
const findCancelButton = () => wrapper.findByTestId('cancel-button');
const findInviteButton = () => wrapper.findByTestId('invite-button');
const clickInviteButton = () => findInviteButton().vm.$emit('click');
const clickCancelButton = () => findCancelButton().vm.$emit('click');
const findMembersFormGroup = () => wrapper.findByTestId('members-form-group');
- const membersFormGroupInvalidFeedback = () => findMembersFormGroup().props('invalidFeedback');
- const membersFormGroupDescription = () => findMembersFormGroup().props('description');
+ const membersFormGroupInvalidFeedback = () =>
+ findMembersFormGroup().attributes('invalid-feedback');
+ const membersFormGroupDescription = () => findMembersFormGroup().attributes('description');
const findMembersSelect = () => wrapper.findComponent(MembersTokenSelect);
const findTasksToBeDone = () => wrapper.findByTestId('invite-members-modal-tasks-to-be-done');
const findTasks = () => wrapper.findByTestId('invite-members-modal-tasks');
const findProjectSelect = () => wrapper.findByTestId('invite-members-modal-project-select');
const findNoProjectsAlert = () => wrapper.findByTestId('invite-members-modal-no-projects-alert');
- const findCelebrationEmoji = () => wrapper.findComponent(GlModal).find(GlEmoji);
-
- describe('rendering the modal', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('renders the modal with the correct title', () => {
- expect(wrapper.findComponent(GlModal).props('title')).toBe(MEMBERS_MODAL_DEFAULT_TITLE);
- });
-
- it('renders the Cancel button text correctly', () => {
- expect(findCancelButton().text()).toBe(CANCEL_BUTTON_TEXT);
- });
-
- it('renders the Invite button text correctly', () => {
- expect(findInviteButton().text()).toBe(INVITE_BUTTON_TEXT);
- });
-
- it('renders the Invite button modal without isLoading', () => {
- expect(findInviteButton().props('loading')).toBe(false);
- });
-
- describe('rendering the access levels dropdown', () => {
- it('sets the default dropdown text to the default access level name', () => {
- expect(findDropdown().attributes('text')).toBe('Guest');
- });
-
- it('renders dropdown items for each accessLevel', () => {
- expect(findDropdownItems()).toHaveLength(5);
- });
- });
-
- describe('rendering the help link', () => {
- it('renders the correct link', () => {
- expect(findLink().attributes('href')).toBe(helpLink);
- });
- });
-
- describe('rendering the access expiration date field', () => {
- it('renders the datepicker', () => {
- expect(findDatepicker().exists()).toBe(true);
- });
- });
- });
+ const findCelebrationEmoji = () => wrapper.findComponent(GlEmoji);
+ const triggerOpenModal = async ({ mode = 'default', source }) => {
+ eventHub.$emit('openModal', { mode, source });
+ await nextTick();
+ };
+ const triggerMembersTokenSelect = async (val) => {
+ findMembersSelect().vm.$emit('input', val);
+ await nextTick();
+ };
+ const triggerTasks = async (val) => {
+ findTasks().vm.$emit('input', val);
+ await nextTick();
+ };
+ const triggerAccessLevel = async (val) => {
+ findBase().vm.$emit('access-level', val);
+ await nextTick();
+ };
describe('rendering the tasks to be done', () => {
- const setupComponent = (
- extraData = {},
- props = {},
- urlParameter = ['invite_members_for_task'],
- ) => {
- const data = {
- selectedAccessLevel: 30,
- selectedTasksToBeDone: ['ci', 'code'],
- ...extraData,
- };
+ const setupComponent = async (props = {}, urlParameter = ['invite_members_for_task']) => {
getParameterValues.mockImplementation(() => urlParameter);
- createComponent(data, props);
+ createComponent(props);
+
+ await triggerAccessLevel(30);
+ };
+
+ const setupComponentWithTasks = async (...args) => {
+ await setupComponent(...args);
+ await triggerTasks(['ci', 'code']);
};
afterAll(() => {
getParameterValues.mockImplementation(() => []);
});
- it('renders the tasks to be done', () => {
- setupComponent();
+ it('renders the tasks to be done', async () => {
+ await setupComponent();
expect(findTasksToBeDone().exists()).toBe(true);
});
describe('when the selected access level is lower than 30', () => {
- it('does not render the tasks to be done', () => {
- setupComponent({ selectedAccessLevel: 20 });
+ it('does not render the tasks to be done', async () => {
+ await setupComponent();
+ await triggerAccessLevel(20);
expect(findTasksToBeDone().exists()).toBe(false);
});
});
describe('when the url does not contain the parameter `open_modal=invite_members_for_task`', () => {
- it('does not render the tasks to be done', () => {
- setupComponent({}, {}, []);
+ it('does not render the tasks to be done', async () => {
+ await setupComponent({}, []);
expect(findTasksToBeDone().exists()).toBe(false);
});
describe('when opened from the Learn GitLab page', () => {
- it('does render the tasks to be done', () => {
- setupComponent({ source: LEARN_GITLAB }, {}, []);
+ it('does render the tasks to be done', async () => {
+ await setupComponent({}, []);
+ await triggerOpenModal({ source: LEARN_GITLAB });
expect(findTasksToBeDone().exists()).toBe(true);
});
@@ -254,27 +166,27 @@ describe('InviteMembersModal', () => {
});
describe('rendering the tasks', () => {
- it('renders the tasks', () => {
- setupComponent();
+ it('renders the tasks', async () => {
+ await setupComponent();
expect(findTasks().exists()).toBe(true);
});
- it('does not render an alert', () => {
- setupComponent();
+ it('does not render an alert', async () => {
+ await setupComponent();
expect(findNoProjectsAlert().exists()).toBe(false);
});
describe('when there are no projects passed in the data', () => {
- it('does not render the tasks', () => {
- setupComponent({}, { projects: [] });
+ it('does not render the tasks', async () => {
+ await setupComponent({ projects: [] });
expect(findTasks().exists()).toBe(false);
});
- it('renders an alert with a link to the new projects path', () => {
- setupComponent({}, { projects: [] });
+ it('renders an alert with a link to the new projects path', async () => {
+ await setupComponent({ projects: [] });
expect(findNoProjectsAlert().exists()).toBe(true);
expect(findNoProjectsAlert().findComponent(GlLink).attributes('href')).toBe(
@@ -285,23 +197,23 @@ describe('InviteMembersModal', () => {
});
describe('rendering the project dropdown', () => {
- it('renders the project select', () => {
- setupComponent();
+ it('renders the project select', async () => {
+ await setupComponentWithTasks();
expect(findProjectSelect().exists()).toBe(true);
});
describe('when the modal is shown for a project', () => {
- it('does not render the project select', () => {
- setupComponent({}, { isProject: true });
+ it('does not render the project select', async () => {
+ await setupComponentWithTasks({ isProject: true });
expect(findProjectSelect().exists()).toBe(false);
});
});
describe('when no tasks are selected', () => {
- it('does not render the project select', () => {
- setupComponent({ selectedTasksToBeDone: [] });
+ it('does not render the project select', async () => {
+ await setupComponent();
expect(findProjectSelect().exists()).toBe(false);
});
@@ -309,8 +221,8 @@ describe('InviteMembersModal', () => {
});
describe('tracking events', () => {
- it('tracks the view for invite_members_for_task', () => {
- setupComponent();
+ it('tracks the view for invite_members_for_task', async () => {
+ await setupComponentWithTasks();
expect(ExperimentTracking).toHaveBeenCalledWith(INVITE_MEMBERS_FOR_TASK.name);
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(
@@ -318,8 +230,8 @@ describe('InviteMembersModal', () => {
);
});
- it('tracks the submit for invite_members_for_task', () => {
- setupComponent();
+ it('tracks the submit for invite_members_for_task', async () => {
+ await setupComponentWithTasks();
clickInviteButton();
expect(ExperimentTracking).toHaveBeenCalledWith(INVITE_MEMBERS_FOR_TASK.name, {
@@ -352,8 +264,9 @@ describe('InviteMembersModal', () => {
});
describe('when inviting members with celebration', () => {
- beforeEach(() => {
- createComponent({ mode: 'celebrate', inviteeType: 'members' }, { isProject: true });
+ beforeEach(async () => {
+ createComponent({ isProject: true });
+ await triggerOpenModal({ mode: 'celebrate' });
});
it('renders the modal with confetti', () => {
@@ -372,34 +285,14 @@ describe('InviteMembersModal', () => {
expect(membersFormGroupDescription()).toBe(MEMBERS_PLACEHOLDER);
});
});
-
- describe('when sharing with a group', () => {
- it('includes the correct invitee, type, and formatted name', () => {
- createInviteGroupToProjectWrapper();
-
- expect(findIntroText()).toBe("You're inviting a group to the test name project.");
- expect(membersFormGroupDescription()).toBe('');
- });
- });
});
describe('when inviting to a group', () => {
- describe('when inviting members', () => {
- it('includes the correct invitee, type, and formatted name', () => {
- createInviteMembersToGroupWrapper();
-
- expect(findIntroText()).toBe("You're inviting members to the test name group.");
- expect(membersFormGroupDescription()).toBe(MEMBERS_PLACEHOLDER);
- });
- });
-
- describe('when sharing with a group', () => {
- it('includes the correct invitee, type, and formatted name', () => {
- createInviteGroupToGroupWrapper();
+ it('includes the correct invitee, type, and formatted name', () => {
+ createInviteMembersToGroupWrapper();
- expect(findIntroText()).toBe("You're inviting a group to the test name group.");
- expect(membersFormGroupDescription()).toBe('');
- });
+ expect(findIntroText()).toBe("You're inviting members to the test name group.");
+ expect(membersFormGroupDescription()).toBe(MEMBERS_PLACEHOLDER);
});
});
});
@@ -419,7 +312,7 @@ describe('InviteMembersModal', () => {
describe('when inviting an existing user to group by user ID', () => {
const postData = {
user_id: '1,2',
- access_level: defaultAccessLevel,
+ access_level: propsData.defaultAccessLevel,
expires_at: undefined,
invite_source: inviteSource,
format: 'json',
@@ -428,8 +321,9 @@ describe('InviteMembersModal', () => {
};
describe('when member is added successfully', () => {
- beforeEach(() => {
- createComponent({ newUsersToInvite: [user1, user2] });
+ beforeEach(async () => {
+ createComponent();
+ await triggerMembersTokenSelect([user1, user2]);
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(Api, 'addGroupMembersByUserId').mockResolvedValue({ data: postData });
@@ -445,19 +339,17 @@ describe('InviteMembersModal', () => {
});
it('calls Api addGroupMembersByUserId with the correct params', () => {
- expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(id, postData);
+ expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(propsData.id, postData);
});
it('displays the successful toastMessage', () => {
- expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('Members were successfully added', {
- onComplete: expect.any(Function),
- });
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('Members were successfully added');
});
});
describe('when opened from a Learn GitLab page', () => {
it('emits the `showSuccessfulInvitationsAlert` event', async () => {
- eventHub.$emit('openModal', { inviteeType: 'members', source: LEARN_GITLAB });
+ await triggerOpenModal({ source: LEARN_GITLAB });
jest.spyOn(eventHub, '$emit').mockImplementation();
@@ -471,12 +363,10 @@ describe('InviteMembersModal', () => {
});
describe('when member is not added successfully', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createInviteMembersToGroupWrapper();
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ newUsersToInvite: [user1] });
+ await triggerMembersTokenSelect([user1]);
});
it('displays "Member already exists" api message for http status conflict', async () => {
@@ -487,7 +377,6 @@ describe('InviteMembersModal', () => {
await waitForPromises();
expect(membersFormGroupInvalidFeedback()).toBe('Member already exists');
- expect(findMembersFormGroup().props('state')).toBe(false);
expect(findMembersSelect().props('validationState')).toBe(false);
expect(findInviteButton().props('loading')).toBe(false);
});
@@ -503,35 +392,31 @@ describe('InviteMembersModal', () => {
it('clears the error when the list of members to invite is cleared', async () => {
expect(membersFormGroupInvalidFeedback()).toBe('Member already exists');
- expect(findMembersFormGroup().props('state')).toBe(false);
expect(findMembersSelect().props('validationState')).toBe(false);
findMembersSelect().vm.$emit('clear');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(membersFormGroupInvalidFeedback()).toBe('');
- expect(findMembersFormGroup().props('state')).not.toBe(false);
expect(findMembersSelect().props('validationState')).not.toBe(false);
});
it('clears the error when the cancel button is clicked', async () => {
clickCancelButton();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(membersFormGroupInvalidFeedback()).toBe('');
- expect(findMembersFormGroup().props('state')).not.toBe(false);
expect(findMembersSelect().props('validationState')).not.toBe(false);
});
it('clears the error when the modal is hidden', async () => {
wrapper.findComponent(GlModal).vm.$emit('hide');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(membersFormGroupInvalidFeedback()).toBe('');
- expect(findMembersFormGroup().props('state')).not.toBe(false);
expect(findMembersSelect().props('validationState')).not.toBe(false);
});
});
@@ -544,7 +429,6 @@ describe('InviteMembersModal', () => {
await waitForPromises();
expect(membersFormGroupInvalidFeedback()).toBe('Member already exists');
- expect(findMembersFormGroup().props('state')).toBe(false);
expect(findMembersSelect().props('validationState')).toBe(false);
expect(findInviteButton().props('loading')).toBe(false);
@@ -553,8 +437,7 @@ describe('InviteMembersModal', () => {
await waitForPromises();
expect(membersFormGroupInvalidFeedback()).toBe('');
- expect(findMembersFormGroup().props('state')).not.toBe(false);
- expect(findMembersSelect().props('validationState')).not.toBe(false);
+ expect(findMembersSelect().props('validationState')).toBe(null);
expect(findInviteButton().props('loading')).toBe(false);
});
@@ -608,7 +491,7 @@ describe('InviteMembersModal', () => {
describe('when inviting a new user by email address', () => {
const postData = {
- access_level: defaultAccessLevel,
+ access_level: propsData.defaultAccessLevel,
expires_at: undefined,
email: 'email@example.com',
invite_source: inviteSource,
@@ -618,8 +501,9 @@ describe('InviteMembersModal', () => {
};
describe('when invites are sent successfully', () => {
- beforeEach(() => {
- createComponent({ newUsersToInvite: [user3] });
+ beforeEach(async () => {
+ createComponent();
+ await triggerMembersTokenSelect([user3]);
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({ data: postData });
@@ -631,24 +515,20 @@ describe('InviteMembersModal', () => {
});
it('calls Api inviteGroupMembersByEmail with the correct params', () => {
- expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(id, postData);
+ expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(propsData.id, postData);
});
it('displays the successful toastMessage', () => {
- expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('Members were successfully added', {
- onComplete: expect.any(Function),
- });
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('Members were successfully added');
});
});
});
describe('when invites are not sent successfully', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createInviteMembersToGroupWrapper();
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ newUsersToInvite: [user3] });
+ await triggerMembersTokenSelect([user3]);
});
it('displays the api error for invalid email syntax', async () => {
@@ -683,9 +563,7 @@ describe('InviteMembersModal', () => {
await waitForPromises();
- expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('Members were successfully added', {
- onComplete: expect.any(Function),
- });
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('Members were successfully added');
expect(findMembersSelect().props('validationState')).toBe(null);
});
@@ -716,9 +594,7 @@ describe('InviteMembersModal', () => {
it('displays the invalid syntax error if one of the emails is invalid', async () => {
createInviteMembersToGroupWrapper();
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ newUsersToInvite: [user3, user4] });
+ await triggerMembersTokenSelect([user3, user4]);
mockInvitationsApi(httpStatus.CREATED, invitationsApiResponse.ERROR_EMAIL_INVALID);
clickInviteButton();
@@ -733,7 +609,7 @@ describe('InviteMembersModal', () => {
describe('when inviting members and non-members in same click', () => {
const postData = {
- access_level: defaultAccessLevel,
+ access_level: propsData.defaultAccessLevel,
expires_at: undefined,
invite_source: inviteSource,
format: 'json',
@@ -745,8 +621,9 @@ describe('InviteMembersModal', () => {
const idPostData = { ...postData, user_id: '1' };
describe('when invites are sent successfully', () => {
- beforeEach(() => {
- createComponent({ newUsersToInvite: [user1, user3] });
+ beforeEach(async () => {
+ createComponent();
+ await triggerMembersTokenSelect([user1, user3]);
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({ data: postData });
@@ -759,30 +636,28 @@ describe('InviteMembersModal', () => {
});
it('calls Api inviteGroupMembersByEmail with the correct params', () => {
- expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(id, emailPostData);
+ expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(propsData.id, emailPostData);
});
it('calls Api addGroupMembersByUserId with the correct params', () => {
- expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(id, idPostData);
+ expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(propsData.id, idPostData);
});
it('displays the successful toastMessage', () => {
- expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('Members were successfully added', {
- onComplete: expect.any(Function),
- });
+ expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('Members were successfully added');
});
});
- it('calls Apis with the invite source passed through to openModal', () => {
- eventHub.$emit('openModal', { inviteeType: 'members', source: '_invite_source_' });
+ it('calls Apis with the invite source passed through to openModal', async () => {
+ await triggerOpenModal({ source: '_invite_source_' });
clickInviteButton();
- expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(id, {
+ expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(propsData.id, {
...emailPostData,
invite_source: '_invite_source_',
});
- expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(id, {
+ expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(propsData.id, {
...idPostData,
invite_source: '_invite_source_',
});
@@ -790,12 +665,10 @@ describe('InviteMembersModal', () => {
});
describe('when any invite failed for any reason', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createInviteMembersToGroupWrapper();
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ newUsersToInvite: [user1, user3] });
+ await triggerMembersTokenSelect([user1, user3]);
mockInvitationsApi(httpStatus.BAD_REQUEST, invitationsApiResponse.EMAIL_INVALID);
mockMembersApi(httpStatus.OK, '200 OK');
@@ -811,71 +684,17 @@ describe('InviteMembersModal', () => {
});
});
- describe('when inviting a group to share', () => {
- describe('when sharing the group is successful', () => {
- const groupPostData = {
- group_id: sharedGroup.id,
- group_access: defaultAccessLevel,
- expires_at: undefined,
- format: 'json',
- };
-
- beforeEach(() => {
- createComponent({ groupToBeSharedWith: sharedGroup });
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ inviteeType: 'group' });
- wrapper.vm.$toast = { show: jest.fn() };
- jest.spyOn(Api, 'groupShareWithGroup').mockResolvedValue({ data: groupPostData });
-
- clickInviteButton();
- });
-
- it('calls Api groupShareWithGroup with the correct params', () => {
- expect(Api.groupShareWithGroup).toHaveBeenCalledWith(id, groupPostData);
- });
-
- it('displays the successful toastMessage', () => {
- expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('Members were successfully added', {
- onComplete: expect.any(Function),
- });
- });
- });
-
- describe('when sharing the group fails', () => {
- beforeEach(() => {
- createInviteGroupToGroupWrapper();
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ groupToBeSharedWith: sharedGroup });
- wrapper.vm.$toast = { show: jest.fn() };
-
- jest
- .spyOn(Api, 'groupShareWithGroup')
- .mockRejectedValue({ response: { data: { success: false } } });
-
- clickInviteButton();
- });
-
- it('displays the generic error message', () => {
- expect(membersFormGroupInvalidFeedback()).toBe('Something went wrong');
- expect(membersFormGroupDescription()).toBe('');
- });
- });
- });
-
describe('tracking', () => {
- beforeEach(() => {
- createComponent({ newUsersToInvite: [user3] });
+ beforeEach(async () => {
+ createComponent();
+ await triggerMembersTokenSelect([user3]);
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({});
});
it('tracks the view for learn_gitlab source', () => {
- eventHub.$emit('openModal', { inviteeType: 'members', source: LEARN_GITLAB });
+ eventHub.$emit('openModal', { source: LEARN_GITLAB });
expect(ExperimentTracking).toHaveBeenCalledWith(INVITE_MEMBERS_FOR_TASK.name);
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(LEARN_GITLAB);
diff --git a/spec/frontend/invite_members/components/invite_members_trigger_spec.js b/spec/frontend/invite_members/components/invite_members_trigger_spec.js
index 429b6fad24a..28402c8331c 100644
--- a/spec/frontend/invite_members/components/invite_members_trigger_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_trigger_spec.js
@@ -71,7 +71,6 @@ describe.each(triggerItems)('with triggerElement as %s', (triggerItem) => {
findButton().vm.$emit('click');
expect(spy).toHaveBeenCalledWith('openModal', {
- inviteeType: 'members',
source: triggerSource,
});
});
diff --git a/spec/frontend/invite_members/components/invite_modal_base_spec.js b/spec/frontend/invite_members/components/invite_modal_base_spec.js
new file mode 100644
index 00000000000..4b183bfd670
--- /dev/null
+++ b/spec/frontend/invite_members/components/invite_modal_base_spec.js
@@ -0,0 +1,103 @@
+import {
+ GlDropdown,
+ GlDropdownItem,
+ GlDatepicker,
+ GlFormGroup,
+ GlSprintf,
+ GlLink,
+ GlModal,
+} from '@gitlab/ui';
+import { stubComponent } from 'helpers/stub_component';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import InviteModalBase from '~/invite_members/components/invite_modal_base.vue';
+import { CANCEL_BUTTON_TEXT, INVITE_BUTTON_TEXT } from '~/invite_members/constants';
+import { propsData } from '../mock_data/modal_base';
+
+describe('InviteModalBase', () => {
+ let wrapper;
+
+ const createComponent = (data = {}, props = {}) => {
+ wrapper = shallowMountExtended(InviteModalBase, {
+ propsData: {
+ ...propsData,
+ ...props,
+ },
+ data() {
+ return data;
+ },
+ stubs: {
+ GlModal: stubComponent(GlModal, {
+ template:
+ '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>',
+ }),
+ GlDropdown: true,
+ GlDropdownItem: true,
+ GlSprintf,
+ GlFormGroup: stubComponent(GlFormGroup, {
+ props: ['state', 'invalidFeedback', 'description'],
+ }),
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findDropdownItems = () => findDropdown().findAllComponents(GlDropdownItem);
+ const findDatepicker = () => wrapper.findComponent(GlDatepicker);
+ const findLink = () => wrapper.findComponent(GlLink);
+ const findIntroText = () => wrapper.findByTestId('modal-base-intro-text').text();
+ const findCancelButton = () => wrapper.findByTestId('cancel-button');
+ const findInviteButton = () => wrapper.findByTestId('invite-button');
+
+ describe('rendering the modal', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders the modal with the correct title', () => {
+ expect(wrapper.findComponent(GlModal).props('title')).toBe(propsData.modalTitle);
+ });
+
+ it('displays the introText', () => {
+ expect(findIntroText()).toBe(propsData.labelIntroText);
+ });
+
+ it('renders the Cancel button text correctly', () => {
+ expect(findCancelButton().text()).toBe(CANCEL_BUTTON_TEXT);
+ });
+
+ it('renders the Invite button text correctly', () => {
+ expect(findInviteButton().text()).toBe(INVITE_BUTTON_TEXT);
+ });
+
+ it('renders the Invite button modal without isLoading', () => {
+ expect(findInviteButton().props('loading')).toBe(false);
+ });
+
+ describe('rendering the access levels dropdown', () => {
+ it('sets the default dropdown text to the default access level name', () => {
+ expect(findDropdown().attributes('text')).toBe('Guest');
+ });
+
+ it('renders dropdown items for each accessLevel', () => {
+ expect(findDropdownItems()).toHaveLength(5);
+ });
+ });
+
+ describe('rendering the help link', () => {
+ it('renders the correct link', () => {
+ expect(findLink().attributes('href')).toBe(propsData.helpLink);
+ });
+ });
+
+ describe('rendering the access expiration date field', () => {
+ it('renders the datepicker', () => {
+ expect(findDatepicker().exists()).toBe(true);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/invite_members/mock_data/group_modal.js b/spec/frontend/invite_members/mock_data/group_modal.js
new file mode 100644
index 00000000000..c05c4edb7d0
--- /dev/null
+++ b/spec/frontend/invite_members/mock_data/group_modal.js
@@ -0,0 +1,11 @@
+export const propsData = {
+ id: '1',
+ name: 'test name',
+ isProject: false,
+ invalidGroups: [],
+ accessLevels: { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 },
+ defaultAccessLevel: 10,
+ helpLink: 'https://example.com',
+};
+
+export const sharedGroup = { id: '981' };
diff --git a/spec/frontend/invite_members/mock_data/member_modal.js b/spec/frontend/invite_members/mock_data/member_modal.js
new file mode 100644
index 00000000000..590502909b2
--- /dev/null
+++ b/spec/frontend/invite_members/mock_data/member_modal.js
@@ -0,0 +1,36 @@
+export const propsData = {
+ id: '1',
+ name: 'test name',
+ isProject: false,
+ accessLevels: { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 },
+ defaultAccessLevel: 30,
+ helpLink: 'https://example.com',
+ tasksToBeDoneOptions: [
+ { text: 'First task', value: 'first' },
+ { text: 'Second task', value: 'second' },
+ ],
+ projects: [
+ { text: 'First project', value: '1' },
+ { text: 'Second project', value: '2' },
+ ],
+};
+
+export const inviteSource = 'unknown';
+export const newProjectPath = 'projects/new';
+
+export const user1 = { id: 1, name: 'Name One', username: 'one_1', avatar_url: '' };
+export const user2 = { id: 2, name: 'Name Two', username: 'one_2', avatar_url: '' };
+export const user3 = {
+ id: 'user-defined-token',
+ name: 'email@example.com',
+ username: 'one_2',
+ avatar_url: '',
+};
+export const user4 = {
+ id: 'user-defined-token',
+ name: 'email4@example.com',
+ username: 'one_4',
+ avatar_url: '',
+};
+
+export const GlEmoji = { template: '<img/>' };
diff --git a/spec/frontend/invite_members/mock_data/modal_base.js b/spec/frontend/invite_members/mock_data/modal_base.js
new file mode 100644
index 00000000000..ea5a8d2b00d
--- /dev/null
+++ b/spec/frontend/invite_members/mock_data/modal_base.js
@@ -0,0 +1,11 @@
+export const propsData = {
+ modalTitle: '_modal_title_',
+ modalId: '_modal_id_',
+ name: '_name_',
+ accessLevels: { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 },
+ defaultAccessLevel: 10,
+ helpLink: 'https://example.com',
+ labelIntroText: '_label_intro_text_',
+ labelSearchField: '_label_search_field_',
+ formGroupDescription: '_form_group_description_',
+};
diff --git a/spec/frontend/issuable/components/issue_milestone_spec.js b/spec/frontend/issuable/components/issue_milestone_spec.js
index 44416676180..9d67f602136 100644
--- a/spec/frontend/issuable/components/issue_milestone_spec.js
+++ b/spec/frontend/issuable/components/issue_milestone_spec.js
@@ -1,6 +1,6 @@
import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { mockMilestone } from 'jest/boards/mock_data';
import IssueMilestone from '~/issuable/components/issue_milestone.vue';
@@ -19,12 +19,12 @@ describe('IssueMilestoneComponent', () => {
let wrapper;
let vm;
- beforeEach((done) => {
+ beforeEach(async () => {
wrapper = createComponent();
({ vm } = wrapper);
- Vue.nextTick(done);
+ await nextTick();
});
afterEach(() => {
@@ -37,7 +37,7 @@ describe('IssueMilestoneComponent', () => {
wrapper.setProps({
milestone: { ...mockMilestone, start_date: '' },
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.isMilestoneStarted).toBe(false);
});
@@ -46,7 +46,7 @@ describe('IssueMilestoneComponent', () => {
await wrapper.setProps({
milestone: { ...mockMilestone, start_date: '1990-07-22' },
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.isMilestoneStarted).toBe(true);
});
@@ -57,7 +57,7 @@ describe('IssueMilestoneComponent', () => {
wrapper.setProps({
milestone: { ...mockMilestone, due_date: '' },
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.isMilestonePastDue).toBe(false);
});
@@ -80,7 +80,7 @@ describe('IssueMilestoneComponent', () => {
wrapper.setProps({
milestone: { ...mockMilestone, due_date: '' },
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.milestoneDatesAbsolute).toBe('(January 1, 2018)');
});
@@ -89,7 +89,7 @@ describe('IssueMilestoneComponent', () => {
wrapper.setProps({
milestone: { ...mockMilestone, start_date: '', due_date: '' },
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.milestoneDatesAbsolute).toBe('');
});
@@ -100,7 +100,7 @@ describe('IssueMilestoneComponent', () => {
wrapper.setProps({
milestone: { ...mockMilestone, due_date: `${new Date().getFullYear() + 10}-01-01` },
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.milestoneDatesHuman).toContain('years remaining');
});
@@ -109,7 +109,7 @@ describe('IssueMilestoneComponent', () => {
wrapper.setProps({
milestone: { ...mockMilestone, start_date: '1990-07-22', due_date: '' },
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.milestoneDatesHuman).toContain('Started');
});
@@ -122,7 +122,7 @@ describe('IssueMilestoneComponent', () => {
due_date: '',
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.milestoneDatesHuman).toContain('Starts');
});
@@ -131,7 +131,7 @@ describe('IssueMilestoneComponent', () => {
wrapper.setProps({
milestone: { ...mockMilestone, start_date: '', due_date: '' },
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.milestoneDatesHuman).toBe('');
});
diff --git a/spec/frontend/issuable/components/related_issuable_item_spec.js b/spec/frontend/issuable/components/related_issuable_item_spec.js
index 6a896ccd21a..6b48f83041a 100644
--- a/spec/frontend/issuable/components/related_issuable_item_spec.js
+++ b/spec/frontend/issuable/components/related_issuable_item_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import IssueDueDate from '~/boards/components/issue_due_date.vue';
import { formatDate } from '~/lib/utils/datetime_utility';
@@ -105,7 +106,7 @@ describe('RelatedIssuableItem', () => {
state: 'closed',
closedAt: '2018-12-01T00:00:00.00Z',
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(tokenState().classes('issue-token-state-icon-closed')).toBe(true);
});
@@ -140,7 +141,7 @@ describe('RelatedIssuableItem', () => {
closedAt: '2018-12-01T00:00:00.00Z',
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(IssueDueDate).props('closed')).toBe(true);
});
@@ -172,14 +173,14 @@ describe('RelatedIssuableItem', () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ removeDisabled: true });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findRemoveButton().attributes('disabled')).toEqual('disabled');
});
it('triggers onRemoveRequest when clicked', async () => {
findRemoveButton().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
const { relatedIssueRemoveRequest } = wrapper.emitted();
expect(relatedIssueRemoveRequest.length).toBe(1);
diff --git a/spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js b/spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js
index ff6922989cb..ce98a16dbb7 100644
--- a/spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js
+++ b/spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js
@@ -1,4 +1,6 @@
+import { GlFormGroup } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import AddIssuableForm from '~/related_issues/components/add_issuable_form.vue';
import IssueToken from '~/related_issues/components/issue_token.vue';
import { issuableTypesMap, linkedIssueTypesMap, PathIdSeparator } from '~/related_issues/constants';
@@ -152,6 +154,30 @@ describe('AddIssuableForm', () => {
});
});
+ describe('categorized issuables', () => {
+ it.each`
+ issuableType | pathIdSeparator | contextHeader | contextFooter
+ ${issuableTypesMap.ISSUE} | ${PathIdSeparator.Issue} | ${'The current issue'} | ${'the following issue(s)'}
+ ${issuableTypesMap.EPIC} | ${PathIdSeparator.Epic} | ${'The current epic'} | ${'the following epic(s)'}
+ `(
+ 'show header text as "$contextHeader" and footer text as "$contextFooter" issuableType is set to $issuableType',
+ ({ issuableType, contextHeader, contextFooter }) => {
+ wrapper = shallowMount(AddIssuableForm, {
+ propsData: {
+ issuableType,
+ inputValue: '',
+ showCategorizedIssues: true,
+ pathIdSeparator,
+ pendingReferences: [],
+ },
+ });
+
+ expect(wrapper.findComponent(GlFormGroup).attributes('label')).toBe(contextHeader);
+ expect(wrapper.find('p.bold').text()).toContain(contextFooter);
+ },
+ );
+ });
+
describe('when it is a Linked Issues form', () => {
beforeEach(() => {
wrapper = mount(AddIssuableForm, {
@@ -194,63 +220,55 @@ describe('AddIssuableForm', () => {
});
describe('when the form is submitted', () => {
- it('emits an event with a "relates_to" link type when the "relates to" radio input selected', (done) => {
+ it('emits an event with a "relates_to" link type when the "relates to" radio input selected', async () => {
jest.spyOn(wrapper.vm, '$emit').mockImplementation(() => {});
wrapper.vm.linkedIssueType = linkedIssueTypesMap.RELATES_TO;
wrapper.vm.onFormSubmit();
- wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('addIssuableFormSubmit', {
- pendingReferences: '',
- linkedIssueType: linkedIssueTypesMap.RELATES_TO,
- });
- done();
+ await nextTick();
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('addIssuableFormSubmit', {
+ pendingReferences: '',
+ linkedIssueType: linkedIssueTypesMap.RELATES_TO,
});
});
- it('emits an event with a "blocks" link type when the "blocks" radio input selected', (done) => {
+ it('emits an event with a "blocks" link type when the "blocks" radio input selected', async () => {
jest.spyOn(wrapper.vm, '$emit').mockImplementation(() => {});
wrapper.vm.linkedIssueType = linkedIssueTypesMap.BLOCKS;
wrapper.vm.onFormSubmit();
- wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('addIssuableFormSubmit', {
- pendingReferences: '',
- linkedIssueType: linkedIssueTypesMap.BLOCKS,
- });
- done();
+ await nextTick();
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('addIssuableFormSubmit', {
+ pendingReferences: '',
+ linkedIssueType: linkedIssueTypesMap.BLOCKS,
});
});
- it('emits an event with a "is_blocked_by" link type when the "is blocked by" radio input selected', (done) => {
+ it('emits an event with a "is_blocked_by" link type when the "is blocked by" radio input selected', async () => {
jest.spyOn(wrapper.vm, '$emit').mockImplementation(() => {});
wrapper.vm.linkedIssueType = linkedIssueTypesMap.IS_BLOCKED_BY;
wrapper.vm.onFormSubmit();
- wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('addIssuableFormSubmit', {
- pendingReferences: '',
- linkedIssueType: linkedIssueTypesMap.IS_BLOCKED_BY,
- });
- done();
+ await nextTick();
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('addIssuableFormSubmit', {
+ pendingReferences: '',
+ linkedIssueType: linkedIssueTypesMap.IS_BLOCKED_BY,
});
});
- it('shows error message when error is present', (done) => {
+ it('shows error message when error is present', async () => {
const itemAddFailureMessage = 'Something went wrong while submitting.';
wrapper.setProps({
hasError: true,
itemAddFailureMessage,
});
- wrapper.vm.$nextTick(() => {
- expect(wrapper.find('.gl-field-error').exists()).toBe(true);
- expect(wrapper.find('.gl-field-error').text()).toContain(itemAddFailureMessage);
- done();
- });
+ await nextTick();
+ expect(wrapper.find('.gl-field-error').exists()).toBe(true);
+ expect(wrapper.find('.gl-field-error').text()).toContain(itemAddFailureMessage);
});
});
});
diff --git a/spec/frontend/issuable/related_issues/components/related_issues_block_spec.js b/spec/frontend/issuable/related_issues/components/related_issues_block_spec.js
index 608fec45bbd..c7925034eb0 100644
--- a/spec/frontend/issuable/related_issues/components/related_issues_block_spec.js
+++ b/spec/frontend/issuable/related_issues/components/related_issues_block_spec.js
@@ -7,6 +7,7 @@ import {
} from 'jest/issuable/components/related_issuable_mock_data';
import RelatedIssuesBlock from '~/related_issues/components/related_issues_block.vue';
import {
+ issuableTypesMap,
linkedIssueTypesMap,
linkedIssueTypesTextMap,
PathIdSeparator,
@@ -29,14 +30,34 @@ describe('RelatedIssuesBlock', () => {
wrapper = mount(RelatedIssuesBlock, {
propsData: {
pathIdSeparator: PathIdSeparator.Issue,
- issuableType: 'issue',
+ issuableType: issuableTypesMap.ISSUE,
},
});
});
- it('displays "Linked issues" in the header', () => {
- expect(wrapper.find('.card-title').text()).toContain('Linked issues');
- });
+ it.each`
+ issuableType | pathIdSeparator | titleText | helpLinkText | addButtonText
+ ${'issue'} | ${PathIdSeparator.Issue} | ${'Linked issues'} | ${'Read more about related issues'} | ${'Add a related issue'}
+ ${'epic'} | ${PathIdSeparator.Epic} | ${'Linked epics'} | ${'Read more about related epics'} | ${'Add a related epic'}
+ `(
+ 'displays "$titleText" in the header, "$helpLinkText" aria-label for help link, and "$addButtonText" aria-label for add button when issuableType is set to "$issuableType"',
+ ({ issuableType, pathIdSeparator, titleText, helpLinkText, addButtonText }) => {
+ wrapper = mount(RelatedIssuesBlock, {
+ propsData: {
+ pathIdSeparator,
+ issuableType,
+ canAdmin: true,
+ helpPath: '/help/user/project/issues/related_issues',
+ },
+ });
+
+ expect(wrapper.find('.card-title').text()).toContain(titleText);
+ expect(wrapper.find('[data-testid="help-link"]').attributes('aria-label')).toBe(
+ helpLinkText,
+ );
+ expect(findIssueCountBadgeAddButton().attributes('aria-label')).toBe(addButtonText);
+ },
+ );
it('unable to add new related issues', () => {
expect(findIssueCountBadgeAddButton().exists()).toBe(false);
diff --git a/spec/frontend/issuable/related_issues/components/related_issues_root_spec.js b/spec/frontend/issuable/related_issues/components/related_issues_root_spec.js
index 01de4da7900..b59717a1f60 100644
--- a/spec/frontend/issuable/related_issues/components/related_issues_root_spec.js
+++ b/spec/frontend/issuable/related_issues/components/related_issues_root_spec.js
@@ -1,5 +1,6 @@
import { mount, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import {
defaultProps,
@@ -210,40 +211,37 @@ describe('RelatedIssuesRoot', () => {
}),
);
- it('when canceling and hiding add issuable form', () => {
+ it('when canceling and hiding add issuable form', async () => {
wrapper.vm.onPendingFormCancel();
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.isFormVisible).toEqual(false);
- expect(wrapper.vm.inputValue).toEqual('');
- expect(wrapper.vm.state.pendingReferences).toHaveLength(0);
- });
+ await nextTick();
+ expect(wrapper.vm.isFormVisible).toEqual(false);
+ expect(wrapper.vm.inputValue).toEqual('');
+ expect(wrapper.vm.state.pendingReferences).toHaveLength(0);
});
});
describe('fetchRelatedIssues', () => {
beforeEach(() => createComponent());
- it('sets isFetching while fetching', () => {
+ it('sets isFetching while fetching', async () => {
wrapper.vm.fetchRelatedIssues();
expect(wrapper.vm.isFetching).toEqual(true);
- return waitForPromises().then(() => {
- expect(wrapper.vm.isFetching).toEqual(false);
- });
+ await waitForPromises();
+ expect(wrapper.vm.isFetching).toEqual(false);
});
- it('should fetch related issues', () => {
+ it('should fetch related issues', async () => {
mock.onGet(defaultProps.endpoint).reply(200, [issuable1, issuable2]);
wrapper.vm.fetchRelatedIssues();
- return waitForPromises().then(() => {
- expect(wrapper.vm.state.relatedIssues).toHaveLength(2);
- expect(wrapper.vm.state.relatedIssues[0].id).toEqual(issuable1.id);
- expect(wrapper.vm.state.relatedIssues[1].id).toEqual(issuable2.id);
- });
+ await waitForPromises();
+ expect(wrapper.vm.state.relatedIssues).toHaveLength(2);
+ expect(wrapper.vm.state.relatedIssues[0].id).toEqual(issuable1.id);
+ expect(wrapper.vm.state.relatedIssues[1].id).toEqual(issuable2.id);
});
});
diff --git a/spec/frontend/issues/create_merge_request_dropdown_spec.js b/spec/frontend/issues/create_merge_request_dropdown_spec.js
index fdc0bd7d72e..637b4d31999 100644
--- a/spec/frontend/issues/create_merge_request_dropdown_spec.js
+++ b/spec/frontend/issues/create_merge_request_dropdown_spec.js
@@ -59,7 +59,7 @@ describe('CreateMergeRequestDropdown', () => {
describe('updateCreatePaths', () => {
it('escapes branch names correctly', () => {
dropdown.createBranchPath = `${TEST_HOST}/branches?branch_name=some-branch&issue=42`;
- dropdown.createMrPath = `${TEST_HOST}/create_merge_request?branch_name=some-branch&ref=main`;
+ dropdown.createMrPath = `${TEST_HOST}/create_merge_request?merge_request%5Bsource_branch%5D=test&merge_request%5Btarget_branch%5D=master`;
dropdown.updateCreatePaths('branch', 'contains#hash');
@@ -68,7 +68,7 @@ describe('CreateMergeRequestDropdown', () => {
);
expect(dropdown.createMrPath).toBe(
- `${TEST_HOST}/create_merge_request?branch_name=contains%23hash&ref=main`,
+ `${TEST_HOST}/create_merge_request?merge_request%5Bsource_branch%5D=contains%23hash&merge_request%5Btarget_branch%5D=master`,
);
});
});
diff --git a/spec/frontend/issues/list/components/issues_list_app_spec.js b/spec/frontend/issues/list/components/issues_list_app_spec.js
index 66428ee0492..88652ddc3cc 100644
--- a/spec/frontend/issues/list/components/issues_list_app_spec.js
+++ b/spec/frontend/issues/list/components/issues_list_app_spec.js
@@ -16,6 +16,8 @@ import {
getIssuesQueryResponse,
filteredTokens,
locationSearch,
+ setSortPreferenceMutationResponse,
+ setSortPreferenceMutationResponseWithErrors,
urlParams,
} from 'jest/issues/list/mock_data';
import createFlash, { FLASH_TYPES } from '~/flash';
@@ -28,8 +30,6 @@ import IssuesListApp from '~/issues/list/components/issues_list_app.vue';
import NewIssueDropdown from '~/issues/list/components/new_issue_dropdown.vue';
import {
CREATED_DESC,
- DUE_DATE_OVERDUE,
- PARAM_DUE_DATE,
RELATIVE_POSITION,
RELATIVE_POSITION_ASC,
TOKEN_TYPE_ASSIGNEE,
@@ -43,16 +43,15 @@ import {
urlSortParams,
} from '~/issues/list/constants';
import eventHub from '~/issues/list/eventhub';
-import { getSortOptions } from '~/issues/list/utils';
+import setSortPreferenceMutation from '~/issues/list/queries/set_sort_preference.mutation.graphql';
+import { getSortKey, getSortOptions } from '~/issues/list/utils';
import axios from '~/lib/utils/axios_utils';
import { scrollUp } from '~/lib/utils/scroll_utils';
import { joinPaths } from '~/lib/utils/url_utility';
jest.mock('@sentry/browser');
jest.mock('~/flash');
-jest.mock('~/lib/utils/scroll_utils', () => ({
- scrollUp: jest.fn().mockName('scrollUpMock'),
-}));
+jest.mock('~/lib/utils/scroll_utils', () => ({ scrollUp: jest.fn() }));
describe('CE IssuesListApp component', () => {
let axiosMock;
@@ -61,6 +60,7 @@ describe('CE IssuesListApp component', () => {
Vue.use(VueApollo);
const defaultProvide = {
+ autocompleteAwardEmojisPath: 'autocomplete/award/emojis/path',
calendarPath: 'calendar/path',
canBulkUpdate: false,
emptyStateSvgPath: 'empty-state.svg',
@@ -72,10 +72,16 @@ describe('CE IssuesListApp component', () => {
hasIssuableHealthStatusFeature: true,
hasIssueWeightsFeature: true,
hasIterationsFeature: true,
+ hasMultipleIssueAssigneesFeature: true,
+ initialEmail: 'email@example.com',
+ initialSort: CREATED_DESC,
+ isAnonymousSearchDisabled: false,
+ isIssueRepositioningDisabled: false,
isProject: true,
isSignedIn: true,
jiraIntegrationPath: 'jira/integration/path',
newIssuePath: 'new/issue/path',
+ releasesPath: 'releases/path',
rssPath: 'rss/path',
showNewIssueLink: true,
signInPath: 'sign/in/path',
@@ -103,11 +109,13 @@ describe('CE IssuesListApp component', () => {
provide = {},
issuesQueryResponse = jest.fn().mockResolvedValue(defaultQueryResponse),
issuesCountsQueryResponse = jest.fn().mockResolvedValue(getIssuesCountsQueryResponse),
+ sortPreferenceMutationResponse = jest.fn().mockResolvedValue(setSortPreferenceMutationResponse),
mountFn = shallowMount,
} = {}) => {
const requestHandlers = [
[getIssuesQuery, issuesQueryResponse],
[getIssuesCountsQuery, issuesCountsQueryResponse],
+ [setSortPreferenceMutation, sortPreferenceMutationResponse],
];
const apolloProvider = createMockApollo(requestHandlers);
@@ -131,9 +139,10 @@ describe('CE IssuesListApp component', () => {
});
describe('IssuableList', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper = mountComponent();
jest.runOnlyPendingTimers();
+ await waitForPromises();
});
it('renders', () => {
@@ -167,8 +176,9 @@ describe('CE IssuesListApp component', () => {
});
describe('header action buttons', () => {
- it('renders rss button', () => {
+ it('renders rss button', async () => {
wrapper = mountComponent({ mountFn: mount });
+ await waitForPromises();
expect(findGlButtonAt(0).props('icon')).toBe('rss');
expect(findGlButtonAt(0).attributes()).toMatchObject({
@@ -177,8 +187,9 @@ describe('CE IssuesListApp component', () => {
});
});
- it('renders calendar button', () => {
+ it('renders calendar button', async () => {
wrapper = mountComponent({ mountFn: mount });
+ await waitForPromises();
expect(findGlButtonAt(1).props('icon')).toBe('calendar');
expect(findGlButtonAt(1).attributes()).toMatchObject({
@@ -189,19 +200,21 @@ describe('CE IssuesListApp component', () => {
describe('csv import/export component', () => {
describe('when user is signed in', () => {
- const search = '?search=refactor&sort=created_date&state=opened';
+ beforeEach(async () => {
+ setWindowLocation('?search=refactor&state=opened');
- beforeEach(() => {
- setWindowLocation(search);
-
- wrapper = mountComponent({ provide: { isSignedIn: true }, mountFn: mount });
+ wrapper = mountComponent({
+ provide: { initialSortBy: CREATED_DESC, isSignedIn: true },
+ mountFn: mount,
+ });
jest.runOnlyPendingTimers();
+ await waitForPromises();
});
it('renders', () => {
expect(findCsvImportExportButtons().props()).toMatchObject({
- exportCsvPath: `${defaultProvide.exportCsvPath}${search}`,
+ exportCsvPath: `${defaultProvide.exportCsvPath}?search=refactor&sort=created_date&state=opened`,
issuableCount: 1,
});
});
@@ -281,16 +294,6 @@ describe('CE IssuesListApp component', () => {
});
describe('initial url params', () => {
- describe('due_date', () => {
- it('is set from the url params', () => {
- setWindowLocation(`?${PARAM_DUE_DATE}=${DUE_DATE_OVERDUE}`);
-
- wrapper = mountComponent();
-
- expect(findIssuableList().props('urlParams')).toMatchObject({ due_date: DUE_DATE_OVERDUE });
- });
- });
-
describe('search', () => {
it('is set from the url params', () => {
setWindowLocation(locationSearch);
@@ -302,31 +305,57 @@ describe('CE IssuesListApp component', () => {
});
describe('sort', () => {
- it.each(Object.keys(urlSortParams))('is set as %s from the url params', (sortKey) => {
- setWindowLocation(`?sort=${urlSortParams[sortKey]}`);
+ describe('when initial sort value uses old enum values', () => {
+ const oldEnumSortValues = Object.values(urlSortParams);
- wrapper = mountComponent();
+ it.each(oldEnumSortValues)('initial sort is set with value %s', (sort) => {
+ wrapper = mountComponent({ provide: { initialSort: sort } });
- expect(findIssuableList().props()).toMatchObject({
- initialSortBy: sortKey,
- urlParams: {
- sort: urlSortParams[sortKey],
- },
+ expect(findIssuableList().props()).toMatchObject({
+ initialSortBy: getSortKey(sort),
+ urlParams: { sort },
+ });
+ });
+ });
+
+ describe('when initial sort value uses new GraphQL enum values', () => {
+ const graphQLEnumSortValues = Object.keys(urlSortParams);
+
+ it.each(graphQLEnumSortValues)('initial sort is set with value %s', (sort) => {
+ wrapper = mountComponent({ provide: { initialSort: sort.toLowerCase() } });
+
+ expect(findIssuableList().props()).toMatchObject({
+ initialSortBy: sort,
+ urlParams: { sort: urlSortParams[sort] },
+ });
});
});
- describe('when issue repositioning is disabled and the sort is manual', () => {
+ describe('when initial sort value is invalid', () => {
+ it.each(['', 'asdf', null, undefined])(
+ 'initial sort is set to value CREATED_DESC',
+ (sort) => {
+ wrapper = mountComponent({ provide: { initialSort: sort } });
+
+ expect(findIssuableList().props()).toMatchObject({
+ initialSortBy: CREATED_DESC,
+ urlParams: { sort: urlSortParams[CREATED_DESC] },
+ });
+ },
+ );
+ });
+
+ describe('when sort is manual and issue repositioning is disabled', () => {
beforeEach(() => {
- setWindowLocation(`?sort=${RELATIVE_POSITION}`);
- wrapper = mountComponent({ provide: { isIssueRepositioningDisabled: true } });
+ wrapper = mountComponent({
+ provide: { initialSort: RELATIVE_POSITION, isIssueRepositioningDisabled: true },
+ });
});
it('changes the sort to the default of created descending', () => {
expect(findIssuableList().props()).toMatchObject({
initialSortBy: CREATED_DESC,
- urlParams: {
- sort: urlSortParams[CREATED_DESC],
- },
+ urlParams: { sort: urlSortParams[CREATED_DESC] },
});
});
@@ -585,16 +614,17 @@ describe('CE IssuesListApp component', () => {
${'fetching issues'} | ${'issuesQueryResponse'} | ${IssuesListApp.i18n.errorFetchingIssues}
${'fetching issue counts'} | ${'issuesCountsQueryResponse'} | ${IssuesListApp.i18n.errorFetchingCounts}
`('when there is an error $error', ({ mountOption, message }) => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper = mountComponent({
[mountOption]: jest.fn().mockRejectedValue(new Error('ERROR')),
});
jest.runOnlyPendingTimers();
+ await waitForPromises();
});
it('shows an error message', () => {
expect(findIssuableList().props('error')).toBe(message);
- expect(Sentry.captureException).toHaveBeenCalledWith(new Error('Network error: ERROR'));
+ expect(Sentry.captureException).toHaveBeenCalledWith(new Error('ERROR'));
});
});
@@ -687,12 +717,13 @@ describe('CE IssuesListApp component', () => {
`(
'when moving issue $description',
({ issueToMove, oldIndex, newIndex, moveBeforeId, moveAfterId }) => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper = mountComponent({
provide: { isProject },
issuesQueryResponse: jest.fn().mockResolvedValue(response(isProject)),
});
jest.runOnlyPendingTimers();
+ await waitForPromises();
});
it('makes API call to reorder the issue', async () => {
@@ -705,7 +736,6 @@ describe('CE IssuesListApp component', () => {
data: JSON.stringify({
move_before_id: getIdFromGraphQLId(moveBeforeId),
move_after_id: getIdFromGraphQLId(moveAfterId),
- group_full_path: isProject ? undefined : defaultProvide.fullPath,
}),
});
});
@@ -715,11 +745,12 @@ describe('CE IssuesListApp component', () => {
});
describe('when unsuccessful', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper = mountComponent({
issuesQueryResponse: jest.fn().mockResolvedValue(response()),
});
jest.runOnlyPendingTimers();
+ await waitForPromises();
});
it('displays an error message', async () => {
@@ -758,8 +789,9 @@ describe('CE IssuesListApp component', () => {
const initialSort = CREATED_DESC;
beforeEach(() => {
- setWindowLocation(`?sort=${initialSort}`);
- wrapper = mountComponent({ provide: { isIssueRepositioningDisabled: true } });
+ wrapper = mountComponent({
+ provide: { initialSort, isIssueRepositioningDisabled: true },
+ });
findIssuableList().vm.$emit('sort', RELATIVE_POSITION_ASC);
});
@@ -777,6 +809,43 @@ describe('CE IssuesListApp component', () => {
});
});
});
+
+ describe('when user is signed in', () => {
+ it('calls mutation to save sort preference', () => {
+ const mutationMock = jest.fn().mockResolvedValue(setSortPreferenceMutationResponse);
+ wrapper = mountComponent({ sortPreferenceMutationResponse: mutationMock });
+
+ findIssuableList().vm.$emit('sort', CREATED_DESC);
+
+ expect(mutationMock).toHaveBeenCalledWith({ input: { issuesSort: CREATED_DESC } });
+ });
+
+ it('captures error when mutation response has errors', async () => {
+ const mutationMock = jest
+ .fn()
+ .mockResolvedValue(setSortPreferenceMutationResponseWithErrors);
+ wrapper = mountComponent({ sortPreferenceMutationResponse: mutationMock });
+
+ findIssuableList().vm.$emit('sort', CREATED_DESC);
+ await waitForPromises();
+
+ expect(Sentry.captureException).toHaveBeenCalledWith(new Error('oh no!'));
+ });
+ });
+
+ describe('when user is signed out', () => {
+ it('does not call mutation to save sort preference', () => {
+ const mutationMock = jest.fn().mockResolvedValue(setSortPreferenceMutationResponse);
+ wrapper = mountComponent({
+ provide: { isSignedIn: false },
+ sortPreferenceMutationResponse: mutationMock,
+ });
+
+ findIssuableList().vm.$emit('sort', CREATED_DESC);
+
+ expect(mutationMock).not.toHaveBeenCalled();
+ });
+ });
});
describe('when "update-legacy-bulk-edit" event is emitted by IssuableList', () => {
diff --git a/spec/frontend/issues/list/components/jira_issues_import_status_app_spec.js b/spec/frontend/issues/list/components/jira_issues_import_status_app_spec.js
index d6d6bb14e9d..2d773e8bf56 100644
--- a/spec/frontend/issues/list/components/jira_issues_import_status_app_spec.js
+++ b/spec/frontend/issues/list/components/jira_issues_import_status_app_spec.js
@@ -1,6 +1,6 @@
import { GlAlert, GlLabel } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import { nextTick } from 'vue';
import JiraIssuesImportStatus from '~/issues/list/components/jira_issues_import_status_app.vue';
describe('JiraIssuesImportStatus', () => {
@@ -100,7 +100,7 @@ describe('JiraIssuesImportStatus', () => {
});
describe('alert message', () => {
- it('is hidden when dismissed', () => {
+ it('is hidden when dismissed', async () => {
wrapper = mountComponent({
shouldShowInProgressAlert: true,
});
@@ -109,9 +109,8 @@ describe('JiraIssuesImportStatus', () => {
findAlert().vm.$emit('dismiss');
- return Vue.nextTick(() => {
- expect(wrapper.find(GlAlert).exists()).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.find(GlAlert).exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/issues/list/components/new_issue_dropdown_spec.js b/spec/frontend/issues/list/components/new_issue_dropdown_spec.js
index 0c52e66ff14..2c8cf9caf5d 100644
--- a/spec/frontend/issues/list/components/new_issue_dropdown_spec.js
+++ b/spec/frontend/issues/list/components/new_issue_dropdown_spec.js
@@ -1,10 +1,13 @@
import { GlDropdown, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
-import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
+import { mount, shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import NewIssueDropdown from '~/issues/list/components/new_issue_dropdown.vue';
import searchProjectsQuery from '~/issues/list/queries/search_projects.query.graphql';
import { DASH_SCOPE, joinPaths } from '~/lib/utils/url_utility';
+import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
import {
emptySearchProjectsQueryResponse,
project1,
@@ -15,8 +18,7 @@ import {
describe('NewIssueDropdown component', () => {
let wrapper;
- const localVue = createLocalVue();
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const mountComponent = ({
search = '',
@@ -27,7 +29,6 @@ describe('NewIssueDropdown component', () => {
const apolloProvider = createMockApollo(requestHandlers);
return mountFn(NewIssueDropdown, {
- localVue,
apolloProvider,
provide: {
fullPath: 'mushroom-kingdom',
@@ -42,8 +43,9 @@ describe('NewIssueDropdown component', () => {
const findInput = () => wrapper.findComponent(GlSearchBoxByType);
const showDropdown = async () => {
findDropdown().vm.$emit('shown');
- await wrapper.vm.$apollo.queries.projects.refetch();
- jest.runOnlyPendingTimers();
+ await waitForPromises();
+ jest.advanceTimersByTime(DEBOUNCE_DELAY);
+ await waitForPromises();
};
afterEach(() => {
@@ -74,7 +76,6 @@ describe('NewIssueDropdown component', () => {
it('renders projects with issues enabled', async () => {
wrapper = mountComponent({ mountFn: mount });
-
await showDropdown();
const listItems = wrapper.findAll('li');
@@ -112,10 +113,11 @@ describe('NewIssueDropdown component', () => {
describe('when a project is selected', () => {
beforeEach(async () => {
wrapper = mountComponent({ mountFn: mount });
-
+ await waitForPromises();
await showDropdown();
wrapper.findComponent(GlDropdownItem).vm.$emit('click', project1);
+ await waitForPromises();
});
it('dropdown button is a link', () => {
diff --git a/spec/frontend/issues/list/mock_data.js b/spec/frontend/issues/list/mock_data.js
index 948699876ce..c883b20682e 100644
--- a/spec/frontend/issues/list/mock_data.js
+++ b/spec/frontend/issues/list/mock_data.js
@@ -7,8 +7,10 @@ export const getIssuesQueryResponse = {
data: {
project: {
id: '1',
+ __typename: 'Project',
issues: {
pageInfo: {
+ __typename: 'PageInfo',
hasNextPage: true,
hasPreviousPage: false,
startCursor: 'startcursor',
@@ -16,6 +18,7 @@ export const getIssuesQueryResponse = {
},
nodes: [
{
+ __typename: 'Issue',
id: 'gid://gitlab/Issue/123456',
iid: '789',
closedAt: null,
@@ -36,6 +39,7 @@ export const getIssuesQueryResponse = {
assignees: {
nodes: [
{
+ __typename: 'UserCore',
id: 'gid://gitlab/User/234',
avatarUrl: 'avatar/url',
name: 'Marge Simpson',
@@ -45,6 +49,7 @@ export const getIssuesQueryResponse = {
],
},
author: {
+ __typename: 'UserCore',
id: 'gid://gitlab/User/456',
avatarUrl: 'avatar/url',
name: 'Homer Simpson',
@@ -90,6 +95,22 @@ export const getIssuesCountsQueryResponse = {
},
};
+export const setSortPreferenceMutationResponse = {
+ data: {
+ userPreferencesUpdate: {
+ errors: [],
+ },
+ },
+};
+
+export const setSortPreferenceMutationResponseWithErrors = {
+ data: {
+ userPreferencesUpdate: {
+ errors: ['oh no!'],
+ },
+ },
+};
+
export const locationSearch = [
'?search=find+issues',
'author_username=homer',
diff --git a/spec/frontend/issues/list/utils_spec.js b/spec/frontend/issues/list/utils_spec.js
index 0e4979fd7b4..1d3e94df897 100644
--- a/spec/frontend/issues/list/utils_spec.js
+++ b/spec/frontend/issues/list/utils_spec.js
@@ -10,7 +10,6 @@ import {
} from 'jest/issues/list/mock_data';
import {
defaultPageSizeParams,
- DUE_DATE_VALUES,
largePageSizeParams,
RELATIVE_POSITION_ASC,
urlSortParams,
@@ -19,11 +18,11 @@ import {
convertToApiParams,
convertToSearchQuery,
convertToUrlParams,
- getDueDateValue,
getFilterTokens,
getInitialPageParams,
getSortKey,
getSortOptions,
+ isSortKey,
} from '~/issues/list/utils';
describe('getInitialPageParams', () => {
@@ -45,13 +44,13 @@ describe('getSortKey', () => {
});
});
-describe('getDueDateValue', () => {
- it.each(DUE_DATE_VALUES)('returns the argument when it is `%s`', (value) => {
- expect(getDueDateValue(value)).toBe(value);
+describe('isSortKey', () => {
+ it.each(Object.keys(urlSortParams))('returns true given %s', (sort) => {
+ expect(isSortKey(sort)).toBe(true);
});
- it('returns undefined when the argument is invalid', () => {
- expect(getDueDateValue('invalid value')).toBeUndefined();
+ it.each(['', 'asdf', null, undefined])('returns false given %s', (sort) => {
+ expect(isSortKey(sort)).toBe(false);
});
});
diff --git a/spec/frontend/issues/new/components/title_suggestions_spec.js b/spec/frontend/issues/new/components/title_suggestions_spec.js
index f6b93cc5a62..0a64890e4ca 100644
--- a/spec/frontend/issues/new/components/title_suggestions_spec.js
+++ b/spec/frontend/issues/new/components/title_suggestions_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import TitleSuggestions from '~/issues/new/components/title_suggestions.vue';
import TitleSuggestionsItem from '~/issues/new/components/title_suggestions_item.vue';
@@ -22,12 +23,11 @@ describe('Issue title suggestions component', () => {
wrapper.destroy();
});
- it('does not render with empty search', () => {
+ it('does not render with empty search', async () => {
wrapper.setProps({ search: '' });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.isVisible()).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.isVisible()).toBe(false);
});
describe('with data', () => {
@@ -37,28 +37,26 @@ describe('Issue title suggestions component', () => {
data = { issues: [{ id: 1 }, { id: 2 }] };
});
- it('renders component', () => {
+ it('renders component', 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(data);
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.findAll('li').length).toBe(data.issues.length);
- });
+ await nextTick();
+ expect(wrapper.findAll('li').length).toBe(data.issues.length);
});
- it('does not render with empty search', () => {
+ it('does not render with empty search', async () => {
wrapper.setProps({ search: '' });
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData(data);
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.isVisible()).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.isVisible()).toBe(false);
});
- it('does not render when loading', () => {
+ it('does not render when loading', 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({
@@ -66,49 +64,44 @@ describe('Issue title suggestions component', () => {
loading: 1,
});
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.isVisible()).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.isVisible()).toBe(false);
});
- it('does not render with empty issues data', () => {
+ it('does not render with empty issues data', 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({ issues: [] });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.isVisible()).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.isVisible()).toBe(false);
});
- it('renders list of issues', () => {
+ it('renders list of issues', 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(data);
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.findAll(TitleSuggestionsItem).length).toBe(2);
- });
+ await nextTick();
+ expect(wrapper.findAll(TitleSuggestionsItem).length).toBe(2);
});
- it('adds margin class to first item', () => {
+ it('adds margin class to first item', 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(data);
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.findAll('li').at(0).classes()).toContain('gl-mb-3');
- });
+ await nextTick();
+ expect(wrapper.findAll('li').at(0).classes()).toContain('gl-mb-3');
});
- it('does not add margin class to last item', () => {
+ it('does not add margin class to last item', 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(data);
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.findAll('li').at(1).classes()).not.toContain('gl-mb-3');
- });
+ await nextTick();
+ expect(wrapper.findAll('li').at(1).classes()).not.toContain('gl-mb-3');
});
});
});
diff --git a/spec/frontend/issues/related_merge_requests/components/related_merge_requests_spec.js b/spec/frontend/issues/related_merge_requests/components/related_merge_requests_spec.js
index 4d780a674be..4df04cd5257 100644
--- a/spec/frontend/issues/related_merge_requests/components/related_merge_requests_spec.js
+++ b/spec/frontend/issues/related_merge_requests/components/related_merge_requests_spec.js
@@ -1,4 +1,4 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import mockData from 'test_fixtures/issues/related_merge_requests.json';
import axios from '~/lib/utils/axios_utils';
@@ -7,13 +7,12 @@ import createStore from '~/issues/related_merge_requests/store/index';
import RelatedIssuableItem from '~/issuable/components/related_issuable_item.vue';
const API_ENDPOINT = '/api/v4/projects/2/issues/33/related_merge_requests';
-const localVue = createLocalVue();
describe('RelatedMergeRequests', () => {
let wrapper;
let mock;
- beforeEach((done) => {
+ beforeEach(() => {
// put the fixture in DOM as the component expects
document.body.innerHTML = `<div id="js-issuable-app"></div>`;
document.getElementById('js-issuable-app').dataset.initial = JSON.stringify(mockData);
@@ -21,8 +20,7 @@ describe('RelatedMergeRequests', () => {
mock = new MockAdapter(axios);
mock.onGet(`${API_ENDPOINT}?per_page=100`).reply(200, mockData, { 'x-total': 2 });
- wrapper = mount(localVue.extend(RelatedMergeRequests), {
- localVue,
+ wrapper = mount(RelatedMergeRequests, {
store: createStore(),
propsData: {
endpoint: API_ENDPOINT,
@@ -31,7 +29,7 @@ describe('RelatedMergeRequests', () => {
},
});
- setImmediate(done);
+ return axios.waitForAll();
});
afterEach(() => {
diff --git a/spec/frontend/issues/show/components/app_spec.js b/spec/frontend/issues/show/components/app_spec.js
index 02db82b84dc..ac2717a5028 100644
--- a/spec/frontend/issues/show/components/app_spec.js
+++ b/spec/frontend/issues/show/components/app_spec.js
@@ -145,33 +145,30 @@ describe('Issuable output', () => {
});
});
- it('shows actions if permissions are correct', () => {
+ it('shows actions if permissions are correct', async () => {
wrapper.vm.showForm = true;
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find('.markdown-selector').exists()).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find('.markdown-selector').exists()).toBe(true);
});
- it('does not show actions if permissions are incorrect', () => {
+ it('does not show actions if permissions are incorrect', async () => {
wrapper.vm.showForm = true;
wrapper.setProps({ canUpdate: false });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find('.markdown-selector').exists()).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.find('.markdown-selector').exists()).toBe(false);
});
- it('does not update formState if form is already open', () => {
+ it('does not update formState if form is already open', async () => {
wrapper.vm.updateAndShowForm();
wrapper.vm.state.titleText = 'testing 123';
wrapper.vm.updateAndShowForm();
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.store.formState.title).not.toBe('testing 123');
- });
+ await nextTick();
+ expect(wrapper.vm.store.formState.title).not.toBe('testing 123');
});
describe('Pinned links propagated', () => {
@@ -186,31 +183,29 @@ describe('Issuable output', () => {
});
describe('updateIssuable', () => {
- it('fetches new data after update', () => {
+ it('fetches new data after update', async () => {
const updateStoreSpy = jest.spyOn(wrapper.vm, 'updateStoreState');
const getDataSpy = jest.spyOn(wrapper.vm.service, 'getData');
jest.spyOn(wrapper.vm.service, 'updateIssuable').mockResolvedValue({
data: { web_url: window.location.pathname },
});
- return wrapper.vm.updateIssuable().then(() => {
- expect(updateStoreSpy).toHaveBeenCalled();
- expect(getDataSpy).toHaveBeenCalled();
- });
+ await wrapper.vm.updateIssuable();
+ expect(updateStoreSpy).toHaveBeenCalled();
+ expect(getDataSpy).toHaveBeenCalled();
});
- it('correctly updates issuable data', () => {
+ it('correctly updates issuable data', async () => {
const spy = jest.spyOn(wrapper.vm.service, 'updateIssuable').mockResolvedValue({
data: { web_url: window.location.pathname },
});
- return wrapper.vm.updateIssuable().then(() => {
- expect(spy).toHaveBeenCalledWith(wrapper.vm.formState);
- expect(eventHub.$emit).toHaveBeenCalledWith('close.form');
- });
+ await wrapper.vm.updateIssuable();
+ expect(spy).toHaveBeenCalledWith(wrapper.vm.formState);
+ expect(eventHub.$emit).toHaveBeenCalledWith('close.form');
});
- it('does not redirect if issue has not moved', () => {
+ it('does not redirect if issue has not moved', async () => {
jest.spyOn(wrapper.vm.service, 'updateIssuable').mockResolvedValue({
data: {
web_url: window.location.pathname,
@@ -218,12 +213,11 @@ describe('Issuable output', () => {
},
});
- return wrapper.vm.updateIssuable().then(() => {
- expect(visitUrl).not.toHaveBeenCalled();
- });
+ await wrapper.vm.updateIssuable();
+ expect(visitUrl).not.toHaveBeenCalled();
});
- it('does not redirect if issue has not moved and user has switched tabs', () => {
+ it('does not redirect if issue has not moved and user has switched tabs', async () => {
jest.spyOn(wrapper.vm.service, 'updateIssuable').mockResolvedValue({
data: {
web_url: '',
@@ -231,12 +225,11 @@ describe('Issuable output', () => {
},
});
- return wrapper.vm.updateIssuable().then(() => {
- expect(visitUrl).not.toHaveBeenCalled();
- });
+ await wrapper.vm.updateIssuable();
+ expect(visitUrl).not.toHaveBeenCalled();
});
- it('redirects if returned web_url has changed', () => {
+ it('redirects if returned web_url has changed', async () => {
jest.spyOn(wrapper.vm.service, 'updateIssuable').mockResolvedValue({
data: {
web_url: '/testing-issue-move',
@@ -246,108 +239,95 @@ describe('Issuable output', () => {
wrapper.vm.updateIssuable();
- return wrapper.vm.updateIssuable().then(() => {
- expect(visitUrl).toHaveBeenCalledWith('/testing-issue-move');
- });
+ await wrapper.vm.updateIssuable();
+ expect(visitUrl).toHaveBeenCalledWith('/testing-issue-move');
});
describe('shows dialog when issue has unsaved changed', () => {
- it('confirms on title change', () => {
+ it('confirms on title change', async () => {
wrapper.vm.showForm = true;
wrapper.vm.state.titleText = 'title has changed';
const e = { returnValue: null };
wrapper.vm.handleBeforeUnloadEvent(e);
- return wrapper.vm.$nextTick().then(() => {
- expect(e.returnValue).not.toBeNull();
- });
+ await nextTick();
+ expect(e.returnValue).not.toBeNull();
});
- it('confirms on description change', () => {
+ it('confirms on description change', async () => {
wrapper.vm.showForm = true;
wrapper.vm.state.descriptionText = 'description has changed';
const e = { returnValue: null };
wrapper.vm.handleBeforeUnloadEvent(e);
- return wrapper.vm.$nextTick().then(() => {
- expect(e.returnValue).not.toBeNull();
- });
+ await nextTick();
+ expect(e.returnValue).not.toBeNull();
});
- it('does nothing when nothing has changed', () => {
+ it('does nothing when nothing has changed', async () => {
const e = { returnValue: null };
wrapper.vm.handleBeforeUnloadEvent(e);
- return wrapper.vm.$nextTick().then(() => {
- expect(e.returnValue).toBeNull();
- });
+ await nextTick();
+ expect(e.returnValue).toBeNull();
});
});
describe('error when updating', () => {
- it('closes form on error', () => {
+ it('closes form on error', async () => {
jest.spyOn(wrapper.vm.service, 'updateIssuable').mockRejectedValue();
- return wrapper.vm.updateIssuable().then(() => {
- expect(eventHub.$emit).not.toHaveBeenCalledWith('close.form');
- expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
- `Error updating issue`,
- );
- });
+ await wrapper.vm.updateIssuable();
+ expect(eventHub.$emit).not.toHaveBeenCalledWith('close.form');
+ expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
+ `Error updating issue`,
+ );
});
- it('returns the correct error message for issuableType', () => {
+ it('returns the correct error message for issuableType', async () => {
jest.spyOn(wrapper.vm.service, 'updateIssuable').mockRejectedValue();
wrapper.setProps({ issuableType: 'merge request' });
- return wrapper.vm
- .$nextTick()
- .then(wrapper.vm.updateIssuable)
- .then(() => {
- expect(eventHub.$emit).not.toHaveBeenCalledWith('close.form');
- expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
- `Error updating merge request`,
- );
- });
+ await nextTick();
+ await wrapper.vm.updateIssuable();
+ expect(eventHub.$emit).not.toHaveBeenCalledWith('close.form');
+ expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
+ `Error updating merge request`,
+ );
});
- it('shows error message from backend if exists', () => {
+ it('shows error message from backend if exists', async () => {
const msg = 'Custom error message from backend';
jest
.spyOn(wrapper.vm.service, 'updateIssuable')
.mockRejectedValue({ response: { data: { errors: [msg] } } });
- return wrapper.vm.updateIssuable().then(() => {
- expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
- `${wrapper.vm.defaultErrorMessage}. ${msg}`,
- );
- });
+ await wrapper.vm.updateIssuable();
+ expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
+ `${wrapper.vm.defaultErrorMessage}. ${msg}`,
+ );
});
});
});
describe('updateAndShowForm', () => {
- it('shows locked warning if form is open & data is different', () => {
- return wrapper.vm
- .$nextTick()
- .then(() => {
- wrapper.vm.updateAndShowForm();
-
- wrapper.vm.poll.makeRequest();
-
- return new Promise((resolve) => {
- wrapper.vm.$watch('formState.lockedWarningVisible', (value) => {
- if (value) {
- resolve();
- }
- });
- });
- })
- .then(() => {
- expect(wrapper.vm.formState.lockedWarningVisible).toBe(true);
- expect(wrapper.vm.formState.lock_version).toBe(1);
- expect(findAlert().exists()).toBe(true);
+ it('shows locked warning if form is open & data is different', async () => {
+ await nextTick();
+ wrapper.vm.updateAndShowForm();
+
+ wrapper.vm.poll.makeRequest();
+
+ await new Promise((resolve) => {
+ wrapper.vm.$watch('formState.lockedWarningVisible', (value) => {
+ if (value) {
+ resolve();
+ }
});
+ });
+
+ expect(wrapper.vm.formState.lockedWarningVisible).toBe(true);
+ expect(wrapper.vm.formState.lock_version).toBe(1);
+ expect(findAlert().exists()).toBe(true);
});
});
@@ -398,12 +378,11 @@ describe('Issuable output', () => {
expect(wrapper.find('.btn-edit').exists()).toBe(true);
});
- it('should render if showInlineEditButton', () => {
+ it('should render if showInlineEditButton', async () => {
wrapper.setProps({ showInlineEditButton: true });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find('.btn-edit').exists()).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find('.btn-edit').exists()).toBe(true);
});
});
diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js
index d39e00b9c9e..3890fc7a353 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,119 @@ 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', () => {
+ describe('empty description', () => {
+ beforeEach(async () => {
+ createComponent({
+ props: {
+ descriptionHtml: '',
+ },
+ provide: {
+ glFeatures: {
+ workItems: true,
+ },
+ },
+ });
+ await nextTick();
+ });
- expect(spy).toHaveBeenCalledWith('taskListUpdateStarted');
+ it('renders without error', () => {
+ expect(findTaskActionButtons()).toHaveLength(0);
+ });
});
- });
- describe('taskListUpdateSuccess', () => {
- it('emits event to parent', () => {
- const spy = jest.spyOn(vm, '$emit');
+ describe('description with checkboxes', () => {
+ beforeEach(async () => {
+ createComponent({
+ props: {
+ descriptionHtml: descriptionHtmlWithCheckboxes,
+ },
+ provide: {
+ glFeatures: {
+ workItems: true,
+ },
+ },
+ });
+ await nextTick();
+ });
- vm.taskListUpdateSuccess();
+ it('renders a list of hidden buttons corresponding to checkboxes in description HTML', () => {
+ expect(findTaskActionButtons()).toHaveLength(3);
+ });
- expect(spy).toHaveBeenCalledWith('taskListUpdateSucceeded');
- });
- });
+ 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'),
+ );
+ });
- 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('does not show a modal by default', () => {
+ expect(findModal().props('visible')).toBe(false);
+ });
- vm.taskListUpdateError();
+ 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');
+ });
+
+ it('closes the modal on `closeCreateTaskModal` event', () => {
+ findConvertToTaskButton().vm.$emit('click');
+ findCreateWorkItem().vm.$emit('closeModal');
+ expect(hideModal).toHaveBeenCalled();
+ });
- expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(msg);
- expect(spy).toHaveBeenCalledWith('taskListUpdateFailed');
+ 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(findTaskSvg().exists()).toBe(true);
+ expect(wrapper.text()).toContain(newTitle);
+ });
});
});
});
diff --git a/spec/frontend/issues/show/components/fields/description_spec.js b/spec/frontend/issues/show/components/fields/description_spec.js
index 3043c4c3673..dd511c3945c 100644
--- a/spec/frontend/issues/show/components/fields/description_spec.js
+++ b/spec/frontend/issues/show/components/fields/description_spec.js
@@ -25,6 +25,7 @@ describe('Description field component', () => {
beforeEach(() => {
jest.spyOn(eventHub, '$emit');
+ gon.features = { markdownContinueLists: true };
});
afterEach(() => {
diff --git a/spec/frontend/issues/show/components/fields/type_spec.js b/spec/frontend/issues/show/components/fields/type_spec.js
index 7f7b16583e6..3333ceffca9 100644
--- a/spec/frontend/issues/show/components/fields/type_spec.js
+++ b/spec/frontend/issues/show/components/fields/type_spec.js
@@ -1,5 +1,6 @@
import { GlFormGroup, GlDropdown, GlDropdownItem, GlIcon } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -10,8 +11,7 @@ import {
updateIssueStateQueryResponse,
} from '../../mock_data/apollo_mock';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
describe('Issue type field component', () => {
let wrapper;
@@ -43,7 +43,6 @@ describe('Issue type field component', () => {
fakeApollo = createMockApollo([], mockResolvers);
wrapper = shallowMount(IssueTypeField, {
- localVue,
apolloProvider: fakeApollo,
data() {
return {
@@ -93,7 +92,7 @@ describe('Issue type field component', () => {
it('updates the `issue_type` in the apollo cache when the value is changed', async () => {
findTypeFromDropDownItems().at(1).vm.$emit('click', issuableTypes.incident);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findTypeFromDropDown().attributes('value')).toBe(issuableTypes.incident);
});
diff --git a/spec/frontend/issues/show/components/form_spec.js b/spec/frontend/issues/show/components/form_spec.js
index db49d2635ba..5c0fe991b22 100644
--- a/spec/frontend/issues/show/components/form_spec.js
+++ b/spec/frontend/issues/show/components/form_spec.js
@@ -1,5 +1,6 @@
import { GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import Autosave from '~/autosave';
import DescriptionTemplate from '~/issues/show/components/fields/description_template.vue';
import IssueTypeField from '~/issues/show/components/fields/type.vue';
@@ -148,7 +149,7 @@ describe('Inline edit form component', () => {
formState: { ...defaultProps.formState, lock_version: 'lock version from server' },
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findAlert().exists()).toBe(true);
});
});
diff --git a/spec/frontend/issues/show/components/header_actions_spec.js b/spec/frontend/issues/show/components/header_actions_spec.js
index d09bf6faa13..4a557a60b94 100644
--- a/spec/frontend/issues/show/components/header_actions_spec.js
+++ b/spec/frontend/issues/show/components/header_actions_spec.js
@@ -1,5 +1,5 @@
import { GlButton, GlDropdown, GlDropdownItem, GlLink, GlModal } from '@gitlab/ui';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import { mockTracking } from 'helpers/tracking_helper';
@@ -153,7 +153,7 @@ describe('HeaderActions component', () => {
it('dispatches a custom event to update the issue page', async () => {
findToggleIssueStateButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(dispatchEventSpy).toHaveBeenCalledTimes(1);
});
diff --git a/spec/frontend/issues/show/components/title_spec.js b/spec/frontend/issues/show/components/title_spec.js
index f9026557be2..29b5353ef1c 100644
--- a/spec/frontend/issues/show/components/title_spec.js
+++ b/spec/frontend/issues/show/components/title_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import titleComponent from '~/issues/show/components/title.vue';
import eventHub from '~/issues/show/event_hub';
import Store from '~/issues/show/stores';
@@ -29,36 +29,33 @@ describe('Title component', () => {
expect(vm.$el.querySelector('.title').innerHTML.trim()).toBe('Testing <img>');
});
- it('updates page title when changing titleHtml', () => {
+ it('updates page title when changing titleHtml', async () => {
const spy = jest.spyOn(vm, 'setPageTitle');
vm.titleHtml = 'test';
- return vm.$nextTick().then(() => {
- expect(spy).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(spy).toHaveBeenCalled();
});
- it('animates title changes', () => {
+ it('animates title changes', async () => {
vm.titleHtml = 'test';
- return vm
- .$nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-pre-pulse');
- jest.runAllTimers();
- return vm.$nextTick();
- })
- .then(() => {
- expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-trigger-pulse');
- });
+
+ await nextTick();
+
+ expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-pre-pulse');
+ jest.runAllTimers();
+
+ await nextTick();
+
+ expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-trigger-pulse');
});
- it('updates page title after changing title', () => {
+ it('updates page title after changing title', async () => {
vm.titleHtml = 'changed';
vm.titleText = 'changed';
- return vm.$nextTick().then(() => {
- expect(document.querySelector('title').textContent.trim()).toContain('changed');
- });
+ await nextTick();
+ expect(document.querySelector('title').textContent.trim()).toContain('changed');
});
describe('inline edit button', () => {
@@ -80,16 +77,15 @@ describe('Title component', () => {
expect(vm.$el.querySelector('.btn-edit')).toBeDefined();
});
- it('should trigger open.form event when clicked', () => {
+ it('should trigger open.form event when clicked', async () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
vm.showInlineEditButton = true;
vm.canUpdate = true;
- Vue.nextTick(() => {
- vm.$el.querySelector('.btn-edit').click();
+ await nextTick();
+ vm.$el.querySelector('.btn-edit').click();
- expect(eventHub.$emit).toHaveBeenCalledWith('open.form');
- });
+ expect(eventHub.$emit).toHaveBeenCalledWith('open.form');
});
});
});
diff --git a/spec/frontend/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/jira_connect/branches/components/new_branch_form_spec.js b/spec/frontend/jira_connect/branches/components/new_branch_form_spec.js
index 7326b84ad54..b9fed5f34f1 100644
--- a/spec/frontend/jira_connect/branches/components/new_branch_form_spec.js
+++ b/spec/frontend/jira_connect/branches/components/new_branch_form_spec.js
@@ -1,5 +1,6 @@
-import { GlAlert, GlForm, GlFormInput, GlButton } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { GlAlert, GlForm, GlFormInput, GlButton, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -9,17 +10,12 @@ import SourceBranchDropdown from '~/jira_connect/branches/components/source_bran
import {
CREATE_BRANCH_ERROR_GENERIC,
CREATE_BRANCH_ERROR_WITH_CONTEXT,
+ I18N_NEW_BRANCH_PERMISSION_ALERT,
} from '~/jira_connect/branches/constants';
import createBranchMutation from '~/jira_connect/branches/graphql/mutations/create_branch.mutation.graphql';
+import { mockProjects } from '../mock_data';
-const mockProject = {
- id: 'test',
- fullPath: 'test-path',
- repository: {
- branchNames: ['main', 'f-test', 'release'],
- rootRef: 'main',
- },
-};
+const mockProject = mockProjects[0];
const mockCreateBranchMutationResponse = {
data: {
createBranch: {
@@ -45,28 +41,27 @@ const mockCreateBranchMutationWithErrors = jest
const mockCreateBranchMutationFailed = jest.fn().mockRejectedValue(new Error('GraphQL error'));
const mockMutationLoading = jest.fn().mockReturnValue(new Promise(() => {}));
-const localVue = createLocalVue();
-
describe('NewBranchForm', () => {
let wrapper;
const findSourceBranchDropdown = () => wrapper.findComponent(SourceBranchDropdown);
const findProjectDropdown = () => wrapper.findComponent(ProjectDropdown);
const findAlert = () => wrapper.findComponent(GlAlert);
+ const findAlertSprintf = () => findAlert().findComponent(GlSprintf);
const findForm = () => wrapper.findComponent(GlForm);
const findInput = () => wrapper.findComponent(GlFormInput);
const findButton = () => wrapper.findComponent(GlButton);
const completeForm = async () => {
- await findInput().vm.$emit('input', 'cool-branch-name');
await findProjectDropdown().vm.$emit('change', mockProject);
await findSourceBranchDropdown().vm.$emit('change', 'source-branch');
+ await findInput().vm.$emit('input', 'cool-branch-name');
};
function createMockApolloProvider({
mockCreateBranchMutation = mockCreateBranchMutationSuccess,
} = {}) {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const mockApollo = createMockApollo([[createBranchMutation, mockCreateBranchMutation]]);
@@ -75,7 +70,6 @@ describe('NewBranchForm', () => {
function createComponent({ mockApollo, provide } = {}) {
wrapper = shallowMount(NewBranchForm, {
- localVue,
apolloProvider: mockApollo || createMockApolloProvider(),
provide: {
initialBranchName: '',
@@ -89,27 +83,107 @@ describe('NewBranchForm', () => {
});
describe('when selecting items from dropdowns', () => {
- describe('when a project is selected', () => {
- it('sets the `selectedProject` prop for ProjectDropdown and SourceBranchDropdown', async () => {
+ describe('when no project selected', () => {
+ beforeEach(() => {
createComponent();
+ });
- const projectDropdown = findProjectDropdown();
- await projectDropdown.vm.$emit('change', mockProject);
+ it('hides source branch selection and branch name input', () => {
+ expect(findSourceBranchDropdown().exists()).toBe(false);
+ expect(findInput().exists()).toBe(false);
+ });
- expect(projectDropdown.props('selectedProject')).toEqual(mockProject);
- expect(findSourceBranchDropdown().props('selectedProject')).toEqual(mockProject);
+ it('disables the submit button', () => {
+ expect(findButton().props('disabled')).toBe(true);
});
});
- describe('when a source branch is selected', () => {
- it('sets the `selectedBranchName` prop for SourceBranchDropdown', async () => {
+ describe('when a valid project is selected', () => {
+ describe("when a source branch isn't selected", () => {
+ beforeEach(async () => {
+ createComponent();
+ await findProjectDropdown().vm.$emit('change', mockProject);
+ });
+
+ it('sets the `selectedProject` prop for ProjectDropdown and SourceBranchDropdown', () => {
+ expect(findProjectDropdown().props('selectedProject')).toEqual(mockProject);
+ expect(findSourceBranchDropdown().exists()).toBe(true);
+ expect(findSourceBranchDropdown().props('selectedProject')).toEqual(mockProject);
+ });
+
+ it('disables the submit button', () => {
+ expect(findButton().props('disabled')).toBe(true);
+ });
+
+ it('renders branch input field', () => {
+ expect(findInput().exists()).toBe(true);
+ });
+ });
+
+ describe('when `initialBranchName` is provided', () => {
+ it('sets value of branch name input to `initialBranchName` by default', async () => {
+ const mockInitialBranchName = 'ap1-test-branch-name';
+
+ createComponent({ provide: { initialBranchName: mockInitialBranchName } });
+ await findProjectDropdown().vm.$emit('change', mockProject);
+
+ expect(findInput().attributes('value')).toBe(mockInitialBranchName);
+ });
+ });
+
+ describe('when a source branch is selected', () => {
+ it('sets the `selectedBranchName` prop for SourceBranchDropdown', async () => {
+ createComponent();
+ await completeForm();
+
+ const mockBranchName = 'main';
+ const sourceBranchDropdown = findSourceBranchDropdown();
+ await sourceBranchDropdown.vm.$emit('change', mockBranchName);
+
+ expect(sourceBranchDropdown.props('selectedBranchName')).toBe(mockBranchName);
+ });
+
+ describe.each`
+ branchName | submitButtonDisabled
+ ${undefined} | ${true}
+ ${''} | ${true}
+ ${' '} | ${true}
+ ${'test-branch'} | ${false}
+ `('when branch name is $branchName', ({ branchName, submitButtonDisabled }) => {
+ it(`sets submit button 'disabled' prop to ${submitButtonDisabled}`, async () => {
+ createComponent();
+ await completeForm();
+ await findInput().vm.$emit('input', branchName);
+
+ expect(findButton().props('disabled')).toBe(submitButtonDisabled);
+ });
+ });
+ });
+ });
+
+ describe("when user doesn't have push permissions for the selected project", () => {
+ beforeEach(async () => {
createComponent();
- const mockBranchName = 'main';
- const sourceBranchDropdown = findSourceBranchDropdown();
- await sourceBranchDropdown.vm.$emit('change', mockBranchName);
+ const projectDropdown = findProjectDropdown();
+ await projectDropdown.vm.$emit('change', {
+ ...mockProject,
+ userPermissions: { pushCode: false },
+ });
+ });
+
+ it('displays an alert', () => {
+ const alert = findAlert();
+
+ expect(alert.exists()).toBe(true);
+ expect(findAlertSprintf().attributes('message')).toBe(I18N_NEW_BRANCH_PERMISSION_ALERT);
+ expect(alert.props('variant')).toBe('warning');
+ expect(alert.props('dismissible')).toBe(false);
+ });
- expect(sourceBranchDropdown.props('selectedBranchName')).toBe(mockBranchName);
+ it('hides source branch selection and branch name input', () => {
+ expect(findSourceBranchDropdown().exists()).toBe(false);
+ expect(findInput().exists()).toBe(false);
});
});
});
@@ -181,7 +255,7 @@ describe('NewBranchForm', () => {
it('displays an alert', () => {
const alert = findAlert();
expect(alert.exists()).toBe(true);
- expect(alert.text()).toBe(alertText);
+ expect(findAlertSprintf().attributes('message')).toBe(alertText);
expect(alert.props()).toMatchObject({ title: alertTitle, variant: 'danger' });
});
@@ -192,15 +266,6 @@ describe('NewBranchForm', () => {
});
});
- describe('when `initialBranchName` is specified', () => {
- it('sets value of branch name input to `initialBranchName` by default', () => {
- const mockInitialBranchName = 'ap1-test-branch-name';
-
- createComponent({ provide: { initialBranchName: mockInitialBranchName } });
- expect(findInput().attributes('value')).toBe(mockInitialBranchName);
- });
- });
-
describe('error handling', () => {
describe.each`
component | componentName
@@ -211,13 +276,15 @@ describe('NewBranchForm', () => {
beforeEach(async () => {
createComponent();
+ await completeForm();
await wrapper.findComponent(component).vm.$emit('error', { message: mockErrorMessage });
});
it('displays an alert', () => {
const alert = findAlert();
+
expect(alert.exists()).toBe(true);
- expect(alert.text()).toBe(mockErrorMessage);
+ expect(findAlertSprintf().attributes('message')).toBe(mockErrorMessage);
expect(alert.props('variant')).toBe('danger');
});
diff --git a/spec/frontend/jira_connect/branches/components/project_dropdown_spec.js b/spec/frontend/jira_connect/branches/components/project_dropdown_spec.js
index ec4cb2739f8..136a5967ee4 100644
--- a/spec/frontend/jira_connect/branches/components/project_dropdown_spec.js
+++ b/spec/frontend/jira_connect/branches/components/project_dropdown_spec.js
@@ -1,5 +1,12 @@
-import { GlDropdown, GlDropdownItem, GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
-import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
+import {
+ GlAvatarLabeled,
+ GlDropdown,
+ GlDropdownItem,
+ GlLoadingIcon,
+ GlSearchBoxByType,
+} from '@gitlab/ui';
+import { mount, shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -7,32 +14,7 @@ import ProjectDropdown from '~/jira_connect/branches/components/project_dropdown
import { PROJECTS_PER_PAGE } from '~/jira_connect/branches/constants';
import getProjectsQuery from '~/jira_connect/branches/graphql/queries/get_projects.query.graphql';
-const localVue = createLocalVue();
-
-const mockProjects = [
- {
- id: 'test',
- name: 'test',
- nameWithNamespace: 'test',
- avatarUrl: 'https://gitlab.com',
- path: 'test-path',
- fullPath: 'test-path',
- repository: {
- empty: false,
- },
- },
- {
- id: 'gitlab',
- name: 'GitLab',
- nameWithNamespace: 'gitlab-org/gitlab',
- avatarUrl: 'https://gitlab.com',
- path: 'gitlab',
- fullPath: 'gitlab-org/gitlab',
- repository: {
- empty: false,
- },
- },
-];
+import { mockProjects } from '../mock_data';
const mockProjectsQueryResponse = {
data: {
@@ -57,12 +39,12 @@ describe('ProjectDropdown', () => {
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
- const findDropdownItemByText = (text) =>
- findAllDropdownItems().wrappers.find((item) => item.text() === text);
+ const findDropdownItemByProjectId = (projectId) =>
+ wrapper.find(`[data-testid="test-project-${projectId}"]`);
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
function createMockApolloProvider({ mockGetProjectsQuery = mockGetProjectsQuerySuccess } = {}) {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const mockApollo = createMockApollo([[getProjectsQuery, mockGetProjectsQuery]]);
@@ -71,7 +53,6 @@ describe('ProjectDropdown', () => {
function createComponent({ mockApollo, props, mountFn = shallowMount } = {}) {
wrapper = mountFn(ProjectDropdown, {
- localVue,
apolloProvider: mockApollo || createMockApolloProvider(),
propsData: props,
});
@@ -101,25 +82,38 @@ describe('ProjectDropdown', () => {
beforeEach(async () => {
createComponent();
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('sets dropdown `loading` prop to `false`', () => {
expect(findDropdown().props('loading')).toBe(false);
});
- it('renders dropdown items', () => {
+ it('renders dropdown items with correct props', () => {
const dropdownItems = findAllDropdownItems();
+ const avatars = dropdownItems.wrappers.map((item) => item.findComponent(GlAvatarLabeled));
+ const avatarAttributes = avatars.map((avatar) => avatar.attributes());
+ const avatarProps = avatars.map((avatar) => avatar.props());
+
expect(dropdownItems.wrappers).toHaveLength(mockProjects.length);
- expect(dropdownItems.wrappers.map((item) => item.text())).toEqual(
- mockProjects.map((project) => project.nameWithNamespace),
+ expect(avatarProps).toMatchObject(
+ mockProjects.map((project) => ({
+ label: project.name,
+ subLabel: project.nameWithNamespace,
+ })),
+ );
+ expect(avatarAttributes).toMatchObject(
+ mockProjects.map((project) => ({
+ src: project.avatarUrl,
+ 'entity-name': project.name,
+ })),
);
});
describe('when selecting a dropdown item', () => {
- it('emits `change` event with the selected project name', async () => {
+ it('emits `change` event with the selected project', async () => {
const mockProject = mockProjects[0];
- const itemToSelect = findDropdownItemByText(mockProject.nameWithNamespace);
+ const itemToSelect = findDropdownItemByProjectId(mockProject.id);
await itemToSelect.vm.$emit('click');
expect(wrapper.emitted('change')[0]).toEqual([mockProject]);
@@ -129,14 +123,14 @@ describe('ProjectDropdown', () => {
describe('when `selectedProject` prop is specified', () => {
const mockProject = mockProjects[0];
- beforeEach(async () => {
+ beforeEach(() => {
wrapper.setProps({
selectedProject: mockProject,
});
});
it('sets `isChecked` prop of the corresponding dropdown item to `true`', () => {
- expect(findDropdownItemByText(mockProject.nameWithNamespace).props('isChecked')).toBe(true);
+ expect(findDropdownItemByProjectId(mockProject.id).props('isChecked')).toBe(true);
});
it('sets dropdown text to `selectedBranchName` value', () => {
diff --git a/spec/frontend/jira_connect/branches/components/source_branch_dropdown_spec.js b/spec/frontend/jira_connect/branches/components/source_branch_dropdown_spec.js
index 9dd11dd6345..56eb6d75def 100644
--- a/spec/frontend/jira_connect/branches/components/source_branch_dropdown_spec.js
+++ b/spec/frontend/jira_connect/branches/components/source_branch_dropdown_spec.js
@@ -1,22 +1,22 @@
import { GlDropdown, GlDropdownItem, GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
-import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
+import { mount, shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import SourceBranchDropdown from '~/jira_connect/branches/components/source_branch_dropdown.vue';
import { BRANCHES_PER_PAGE } from '~/jira_connect/branches/constants';
import getProjectQuery from '~/jira_connect/branches/graphql/queries/get_project.query.graphql';
-
-const localVue = createLocalVue();
+import { mockProjects } from '../mock_data';
const mockProject = {
id: 'test',
- fullPath: 'test-path',
repository: {
branchNames: ['main', 'f-test', 'release'],
rootRef: 'main',
},
};
+const mockSelectedProject = mockProjects[0];
const mockProjectQueryResponse = {
data: {
@@ -45,7 +45,7 @@ describe('SourceBranchDropdown', () => {
};
function createMockApolloProvider({ getProjectQueryLoading = false } = {}) {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const mockApollo = createMockApollo([
[getProjectQuery, getProjectQueryLoading ? mockQueryLoading : mockGetProjectQuery],
@@ -56,7 +56,6 @@ describe('SourceBranchDropdown', () => {
function createComponent({ mockApollo, props, mountFn = shallowMount } = {}) {
wrapper = mountFn(SourceBranchDropdown, {
- localVue,
apolloProvider: mockApollo || createMockApolloProvider(),
propsData: props,
});
@@ -78,7 +77,7 @@ describe('SourceBranchDropdown', () => {
describe('when `selectedProject` becomes specified', () => {
beforeEach(async () => {
wrapper.setProps({
- selectedProject: mockProject,
+ selectedProject: mockSelectedProject,
});
await waitForPromises();
@@ -103,7 +102,7 @@ describe('SourceBranchDropdown', () => {
it('renders loading icon in dropdown', () => {
createComponent({
mockApollo: createMockApolloProvider({ getProjectQueryLoading: true }),
- props: { selectedProject: mockProject },
+ props: { selectedProject: mockSelectedProject },
});
expect(findLoadingIcon().isVisible()).toBe(true);
@@ -113,7 +112,7 @@ describe('SourceBranchDropdown', () => {
describe('when branches have loaded', () => {
describe('when searching branches', () => {
it('triggers a refetch', async () => {
- createComponent({ mountFn: mount, props: { selectedProject: mockProject } });
+ createComponent({ mountFn: mount, props: { selectedProject: mockSelectedProject } });
await waitForPromises();
jest.clearAllMocks();
@@ -131,7 +130,7 @@ describe('SourceBranchDropdown', () => {
describe('template', () => {
beforeEach(async () => {
- createComponent({ props: { selectedProject: mockProject } });
+ createComponent({ props: { selectedProject: mockSelectedProject } });
await waitForPromises();
});
diff --git a/spec/frontend/jira_connect/branches/mock_data.js b/spec/frontend/jira_connect/branches/mock_data.js
new file mode 100644
index 00000000000..742ab5392c8
--- /dev/null
+++ b/spec/frontend/jira_connect/branches/mock_data.js
@@ -0,0 +1,30 @@
+export const mockProjects = [
+ {
+ id: 'test',
+ name: 'test',
+ nameWithNamespace: 'test',
+ avatarUrl: 'https://gitlab.com',
+ path: 'test-path',
+ fullPath: 'test-path',
+ repository: {
+ empty: false,
+ },
+ userPermissions: {
+ pushCode: true,
+ },
+ },
+ {
+ id: 'gitlab',
+ name: 'GitLab',
+ nameWithNamespace: 'gitlab-org/gitlab',
+ avatarUrl: 'https://gitlab.com',
+ path: 'gitlab',
+ fullPath: 'gitlab-org/gitlab',
+ repository: {
+ empty: false,
+ },
+ userPermissions: {
+ pushCode: true,
+ },
+ },
+];
diff --git a/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js b/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js
index 15e9a740c83..b0d5859cd31 100644
--- a/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import * as JiraConnectApi from '~/jira_connect/subscriptions/api';
@@ -63,7 +64,7 @@ describe('GroupsListItem', () => {
clickLinkButton();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findLinkButton().props('loading')).toBe(true);
diff --git a/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_spec.js b/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_spec.js
index 04aba8bda23..d871b1e1dcc 100644
--- a/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_spec.js
@@ -1,5 +1,6 @@
import { GlAlert, GlLoadingIcon, GlSearchBoxByType, GlPagination } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { fetchGroups } from '~/jira_connect/subscriptions/api';
@@ -61,7 +62,7 @@ describe('GroupsList', () => {
fetchGroups.mockReturnValue(new Promise(() => {}));
createComponent();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findGlLoadingIcon().exists()).toBe(true);
});
@@ -100,6 +101,8 @@ describe('GroupsList', () => {
});
createComponent();
+ // wait for the initial loadGroups
+ // to finish.
await waitForPromises();
});
@@ -124,7 +127,7 @@ describe('GroupsList', () => {
findFirstItem().vm.$emit('error', errorMessage);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findGlAlert().exists()).toBe(true);
expect(findGlAlert().text()).toContain(errorMessage);
@@ -136,10 +139,12 @@ describe('GroupsList', () => {
describe('while groups are loading', () => {
beforeEach(async () => {
fetchGroups.mockClear();
+ // return a never-ending promise to make test
+ // deterministic.
fetchGroups.mockReturnValue(new Promise(() => {}));
findSearchBox().vm.$emit('input', mockSearchTeam);
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('calls `fetchGroups` with search term', () => {
@@ -172,7 +177,7 @@ describe('GroupsList', () => {
describe('when group search finishes loading', () => {
beforeEach(async () => {
fetchGroups.mockResolvedValue({ data: [mockGroup1] });
- findSearchBox().vm.$emit('input');
+ findSearchBox().vm.$emit('input', mockSearchTeam);
await waitForPromises();
});
@@ -183,32 +188,48 @@ describe('GroupsList', () => {
});
});
- it.each`
- userSearchTerm | finalSearchTerm
- ${'gitl'} | ${'gitl'}
- ${'git'} | ${'git'}
- ${'gi'} | ${''}
- ${'g'} | ${''}
- ${''} | ${''}
- ${undefined} | ${undefined}
+ describe.each`
+ previousSearch | newSearch | shouldSearch | expectedSearchValue
+ ${''} | ${'git'} | ${true} | ${'git'}
+ ${'g'} | ${'git'} | ${true} | ${'git'}
+ ${'git'} | ${'gitl'} | ${true} | ${'gitl'}
+ ${'git'} | ${'gi'} | ${true} | ${''}
+ ${'gi'} | ${'g'} | ${false} | ${undefined}
+ ${'g'} | ${''} | ${false} | ${undefined}
+ ${''} | ${'g'} | ${false} | ${undefined}
`(
- 'searches for "$finalSearchTerm" when user enters "$userSearchTerm"',
- async ({ userSearchTerm, finalSearchTerm }) => {
- fetchGroups.mockResolvedValue({
- data: [mockGroup1],
- headers: { 'X-PAGE': 1, 'X-TOTAL': 1 },
+ 'when previous search was "$previousSearch" and user enters "$newSearch"',
+ ({ previousSearch, newSearch, shouldSearch, expectedSearchValue }) => {
+ beforeEach(async () => {
+ fetchGroups.mockResolvedValue({
+ data: [mockGroup1],
+ headers: { 'X-PAGE': 1, 'X-TOTAL': 1 },
+ });
+
+ // wait for initial load
+ createComponent();
+ await waitForPromises();
+
+ // set up the "previous search"
+ findSearchBox().vm.$emit('input', previousSearch);
+ await waitForPromises();
+
+ fetchGroups.mockClear();
});
- createComponent();
- await waitForPromises();
-
- const searchBox = findSearchBox();
- searchBox.vm.$emit('input', userSearchTerm);
-
- expect(fetchGroups).toHaveBeenLastCalledWith(mockGroupsPath, {
- page: 1,
- perPage: DEFAULT_GROUPS_PER_PAGE,
- search: finalSearchTerm,
+ it(`${shouldSearch ? 'should' : 'should not'} execute fetch new results`, () => {
+ // enter the new search
+ findSearchBox().vm.$emit('input', newSearch);
+
+ if (shouldSearch) {
+ expect(fetchGroups).toHaveBeenCalledWith(mockGroupsPath, {
+ page: 1,
+ perPage: DEFAULT_GROUPS_PER_PAGE,
+ search: expectedSearchValue,
+ });
+ } else {
+ expect(fetchGroups).not.toHaveBeenCalled();
+ }
});
},
);
@@ -226,7 +247,13 @@ describe('GroupsList', () => {
await waitForPromises();
const paginationEl = findPagination();
- paginationEl.vm.$emit('input', 2);
+
+ // mock the response from page 2
+ fetchGroups.mockResolvedValue({
+ headers: { 'X-TOTAL': totalItems, 'X-PAGE': 2 },
+ data: mockGroups,
+ });
+ await paginationEl.vm.$emit('input', 2);
});
it('should load results for page 2', () => {
@@ -237,18 +264,23 @@ describe('GroupsList', () => {
});
});
- it('resets page to 1 on search `input` event', () => {
- const mockSearchTerm = 'gitlab';
- const searchBox = findSearchBox();
-
- searchBox.vm.$emit('input', mockSearchTerm);
+ it.each`
+ scenario | searchTerm | expectedPage | expectedSearchTerm
+ ${'preserves current page'} | ${'gi'} | ${2} | ${''}
+ ${'resets page to 1'} | ${'gitlab'} | ${1} | ${'gitlab'}
+ `(
+ '$scenario when search term is $searchTerm',
+ ({ searchTerm, expectedPage, expectedSearchTerm }) => {
+ const searchBox = findSearchBox();
+ searchBox.vm.$emit('input', searchTerm);
- expect(fetchGroups).toHaveBeenLastCalledWith(mockGroupsPath, {
- page: 1,
- perPage: DEFAULT_GROUPS_PER_PAGE,
- search: mockSearchTerm,
- });
- });
+ expect(fetchGroups).toHaveBeenLastCalledWith(mockGroupsPath, {
+ page: expectedPage,
+ perPage: DEFAULT_GROUPS_PER_PAGE,
+ search: expectedSearchTerm,
+ });
+ },
+ );
});
});
diff --git a/spec/frontend/jira_connect/subscriptions/components/app_spec.js b/spec/frontend/jira_connect/subscriptions/components/app_spec.js
index 47fe96262ee..aa0f1440b20 100644
--- a/spec/frontend/jira_connect/subscriptions/components/app_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/app_spec.js
@@ -1,10 +1,10 @@
-import { GlAlert, GlLink, GlEmptyState } from '@gitlab/ui';
-import { mount, shallowMount } from '@vue/test-utils';
+import { GlLink } from '@gitlab/ui';
+import { nextTick } from 'vue';
+import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import JiraConnectApp from '~/jira_connect/subscriptions/components/app.vue';
-import AddNamespaceButton from '~/jira_connect/subscriptions/components/add_namespace_button.vue';
-import SignInButton from '~/jira_connect/subscriptions/components/sign_in_button.vue';
-import SubscriptionsList from '~/jira_connect/subscriptions/components/subscriptions_list.vue';
+import SignInPage from '~/jira_connect/subscriptions/pages/sign_in.vue';
+import SubscriptionsPage from '~/jira_connect/subscriptions/pages/subscriptions.vue';
import UserLink from '~/jira_connect/subscriptions/components/user_link.vue';
import createStore from '~/jira_connect/subscriptions/store';
import { SET_ALERT } from '~/jira_connect/subscriptions/store/mutation_types';
@@ -20,14 +20,12 @@ describe('JiraConnectApp', () => {
let wrapper;
let store;
- const findAlert = () => wrapper.findComponent(GlAlert);
+ const findAlert = () => wrapper.findByTestId('jira-connect-persisted-alert');
const findAlertLink = () => findAlert().findComponent(GlLink);
- const findSignInButton = () => wrapper.findComponent(SignInButton);
- const findAddNamespaceButton = () => wrapper.findComponent(AddNamespaceButton);
- const findSubscriptionsList = () => wrapper.findComponent(SubscriptionsList);
- const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+ const findSignInPage = () => wrapper.findComponent(SignInPage);
+ const findSubscriptionsPage = () => wrapper.findComponent(SubscriptionsPage);
- const createComponent = ({ provide, mountFn = shallowMount } = {}) => {
+ const createComponent = ({ provide, mountFn = shallowMountExtended } = {}) => {
store = createStore();
wrapper = mountFn(JiraConnectApp, {
@@ -42,49 +40,35 @@ describe('JiraConnectApp', () => {
describe('template', () => {
describe.each`
- scenario | usersPath | subscriptions | expectSignInButton | expectEmptyState | expectNamespaceButton | expectSubscriptionsList
- ${'user is not signed in with subscriptions'} | ${'/users'} | ${[mockSubscription]} | ${true} | ${false} | ${false} | ${true}
- ${'user is not signed in without subscriptions'} | ${'/users'} | ${undefined} | ${true} | ${false} | ${false} | ${false}
- ${'user is signed in with subscriptions'} | ${undefined} | ${[mockSubscription]} | ${false} | ${false} | ${true} | ${true}
- ${'user is signed in without subscriptions'} | ${undefined} | ${undefined} | ${false} | ${true} | ${false} | ${false}
- `(
- 'when $scenario',
- ({
- usersPath,
- expectSignInButton,
- subscriptions,
- expectEmptyState,
- expectNamespaceButton,
- expectSubscriptionsList,
- }) => {
- beforeEach(() => {
- createComponent({
- provide: {
- usersPath,
- subscriptions,
- },
- });
- });
-
- it(`${expectSignInButton ? 'renders' : 'does not render'} sign in button`, () => {
- expect(findSignInButton().exists()).toBe(expectSignInButton);
- });
-
- it(`${expectEmptyState ? 'renders' : 'does not render'} empty state`, () => {
- expect(findEmptyState().exists()).toBe(expectEmptyState);
+ scenario | usersPath | shouldRenderSignInPage | shouldRenderSubscriptionsPage
+ ${'user is not signed in'} | ${'/users'} | ${true} | ${false}
+ ${'user is signed in'} | ${undefined} | ${false} | ${true}
+ `('when $scenario', ({ usersPath, shouldRenderSignInPage, shouldRenderSubscriptionsPage }) => {
+ beforeEach(() => {
+ createComponent({
+ provide: {
+ usersPath,
+ subscriptions: [mockSubscription],
+ },
});
+ });
- it(`${
- expectNamespaceButton ? 'renders' : 'does not render'
- } button to add namespace`, () => {
- expect(findAddNamespaceButton().exists()).toBe(expectNamespaceButton);
- });
+ it(`${shouldRenderSignInPage ? 'renders' : 'does not render'} sign in page`, () => {
+ expect(findSignInPage().exists()).toBe(shouldRenderSignInPage);
+ if (shouldRenderSignInPage) {
+ expect(findSignInPage().props('hasSubscriptions')).toBe(true);
+ }
+ });
- it(`${expectSubscriptionsList ? 'renders' : 'does not render'} subscriptions list`, () => {
- expect(findSubscriptionsList().exists()).toBe(expectSubscriptionsList);
- });
- },
- );
+ it(`${
+ shouldRenderSubscriptionsPage ? 'renders' : 'does not render'
+ } subscriptions page`, () => {
+ expect(findSubscriptionsPage().exists()).toBe(shouldRenderSubscriptionsPage);
+ if (shouldRenderSubscriptionsPage) {
+ expect(findSubscriptionsPage().props('hasSubscriptions')).toBe(true);
+ }
+ });
+ });
it('renders UserLink component', () => {
createComponent({
@@ -116,7 +100,7 @@ describe('JiraConnectApp', () => {
createComponent();
store.commit(SET_ALERT, { message, variant });
- await wrapper.vm.$nextTick();
+ await nextTick();
const alert = findAlert();
@@ -134,22 +118,22 @@ describe('JiraConnectApp', () => {
createComponent();
store.commit(SET_ALERT, { message: 'test message' });
- await wrapper.vm.$nextTick();
+ await nextTick();
findAlert().vm.$emit('dismiss');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findAlert().exists()).toBe(false);
});
it('renders link when `linkUrl` is set', async () => {
- createComponent({ mountFn: mount });
+ createComponent({ mountFn: mountExtended });
store.commit(SET_ALERT, {
message: __('test message %{linkStart}test link%{linkEnd}'),
linkUrl: 'https://gitlab.com',
});
- await wrapper.vm.$nextTick();
+ await nextTick();
const alertLink = findAlertLink();
diff --git a/spec/frontend/jira_connect/subscriptions/components/compatibility_alert_spec.js b/spec/frontend/jira_connect/subscriptions/components/compatibility_alert_spec.js
new file mode 100644
index 00000000000..f8ee8c2c664
--- /dev/null
+++ b/spec/frontend/jira_connect/subscriptions/components/compatibility_alert_spec.js
@@ -0,0 +1,56 @@
+import { GlAlert, GlLink } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
+import CompatibilityAlert from '~/jira_connect/subscriptions/components/compatibility_alert.vue';
+
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
+
+describe('CompatibilityAlert', () => {
+ let wrapper;
+
+ const createComponent = ({ mountFn = shallowMount } = {}) => {
+ wrapper = mountFn(CompatibilityAlert);
+ };
+
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findLink = () => wrapper.findComponent(GlLink);
+ const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('displays an alert', () => {
+ createComponent();
+
+ expect(findAlert().exists()).toBe(true);
+ });
+
+ it('renders help link with target="_blank" and rel="noopener noreferrer"', () => {
+ createComponent({ mountFn: mount });
+ expect(findLink().attributes()).toMatchObject({
+ target: '_blank',
+ rel: 'noopener noreferrer',
+ });
+ });
+
+ it('`local-storage-sync` value prop is initially false', () => {
+ createComponent();
+
+ expect(findLocalStorageSync().props('value')).toBe(false);
+ });
+
+ describe('when dismissed', () => {
+ beforeEach(async () => {
+ createComponent();
+ await findAlert().vm.$emit('dismiss');
+ });
+
+ it('hides alert', () => {
+ expect(findAlert().exists()).toBe(false);
+ });
+
+ it('updates value prop of `local-storage-sync`', () => {
+ expect(findLocalStorageSync().props('value')).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/jira_connect/subscriptions/components/sign_in_button_spec.js b/spec/frontend/jira_connect/subscriptions/components/sign_in_button_spec.js
index cb5ae877c47..94dcf9decec 100644
--- a/spec/frontend/jira_connect/subscriptions/components/sign_in_button_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/sign_in_button_spec.js
@@ -11,11 +11,12 @@ jest.mock('~/jira_connect/subscriptions/utils');
describe('SignInButton', () => {
let wrapper;
- const createComponent = () => {
+ const createComponent = ({ slots } = {}) => {
wrapper = shallowMount(SignInButton, {
propsData: {
usersPath: MOCK_USERS_PATH,
},
+ slots,
});
};
@@ -29,6 +30,7 @@ describe('SignInButton', () => {
createComponent();
expect(findButton().exists()).toBe(true);
+ expect(findButton().text()).toBe(SignInButton.i18n.defaultButtonText);
});
describe.each`
@@ -45,4 +47,12 @@ describe('SignInButton', () => {
expect(findButton().attributes('href')).toBe(expectedHref);
});
});
+
+ describe('with slot', () => {
+ const mockSlotContent = 'custom button content!';
+ it('renders slot content in button', () => {
+ createComponent({ slots: { default: mockSlotContent } });
+ expect(wrapper.text()).toMatchInterpolatedText(mockSlotContent);
+ });
+ });
});
diff --git a/spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js b/spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js
index 4e4a2b58600..2aad533f677 100644
--- a/spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import * as JiraConnectApi from '~/jira_connect/subscriptions/api';
@@ -71,7 +72,7 @@ describe('SubscriptionsList', () => {
clickUnlinkButton();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findUnlinkButton().props('loading')).toBe(true);
diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in_spec.js
new file mode 100644
index 00000000000..4e3297506f1
--- /dev/null
+++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in_spec.js
@@ -0,0 +1,62 @@
+import { mount } from '@vue/test-utils';
+
+import SignInPage from '~/jira_connect/subscriptions/pages/sign_in.vue';
+import SignInButton from '~/jira_connect/subscriptions/components/sign_in_button.vue';
+import SubscriptionsList from '~/jira_connect/subscriptions/components/subscriptions_list.vue';
+import createStore from '~/jira_connect/subscriptions/store';
+
+jest.mock('~/jira_connect/subscriptions/utils');
+
+describe('SignInPage', () => {
+ let wrapper;
+ let store;
+
+ const findSignInButton = () => wrapper.findComponent(SignInButton);
+ const findSubscriptionsList = () => wrapper.findComponent(SubscriptionsList);
+
+ const createComponent = ({ provide, props } = {}) => {
+ store = createStore();
+
+ wrapper = mount(SignInPage, {
+ store,
+ provide,
+ propsData: props,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('template', () => {
+ const mockUsersPath = '/test';
+ describe.each`
+ scenario | expectSubscriptionsList | signInButtonText
+ ${'with subscriptions'} | ${true} | ${SignInPage.i18n.signinButtonTextWithSubscriptions}
+ ${'without subscriptions'} | ${false} | ${SignInButton.i18n.defaultButtonText}
+ `('$scenario', ({ expectSubscriptionsList, signInButtonText }) => {
+ beforeEach(() => {
+ createComponent({
+ provide: {
+ usersPath: mockUsersPath,
+ },
+ props: {
+ hasSubscriptions: expectSubscriptionsList,
+ },
+ });
+ });
+
+ it(`renders sign in button with text ${signInButtonText}`, () => {
+ expect(findSignInButton().text()).toMatchInterpolatedText(signInButtonText);
+ });
+
+ it('renders sign in button with `usersPath` prop', () => {
+ expect(findSignInButton().props('usersPath')).toBe(mockUsersPath);
+ });
+
+ it(`${expectSubscriptionsList ? 'renders' : 'does not render'} subscriptions list`, () => {
+ expect(findSubscriptionsList().exists()).toBe(expectSubscriptionsList);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/jira_connect/subscriptions/pages/subscriptions_spec.js b/spec/frontend/jira_connect/subscriptions/pages/subscriptions_spec.js
new file mode 100644
index 00000000000..198278efc1f
--- /dev/null
+++ b/spec/frontend/jira_connect/subscriptions/pages/subscriptions_spec.js
@@ -0,0 +1,56 @@
+import { GlEmptyState } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import SubscriptionsPage from '~/jira_connect/subscriptions/pages/subscriptions.vue';
+import AddNamespaceButton from '~/jira_connect/subscriptions/components/add_namespace_button.vue';
+import SubscriptionsList from '~/jira_connect/subscriptions/components/subscriptions_list.vue';
+import createStore from '~/jira_connect/subscriptions/store';
+
+describe('SubscriptionsPage', () => {
+ let wrapper;
+ let store;
+
+ const findAddNamespaceButton = () => wrapper.findComponent(AddNamespaceButton);
+ const findSubscriptionsList = () => wrapper.findComponent(SubscriptionsList);
+ const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+
+ const createComponent = ({ props } = {}) => {
+ store = createStore();
+
+ wrapper = shallowMount(SubscriptionsPage, {
+ store,
+ propsData: props,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('template', () => {
+ describe.each`
+ scenario | expectSubscriptionsList | expectEmptyState
+ ${'with subscriptions'} | ${true} | ${false}
+ ${'without subscriptions'} | ${false} | ${true}
+ `('$scenario', ({ expectEmptyState, expectSubscriptionsList }) => {
+ beforeEach(() => {
+ createComponent({
+ props: {
+ hasSubscriptions: expectSubscriptionsList,
+ },
+ });
+ });
+
+ it('renders button to add namespace', () => {
+ expect(findAddNamespaceButton().exists()).toBe(true);
+ });
+
+ it(`${expectEmptyState ? 'renders' : 'does not render'} empty state`, () => {
+ expect(findEmptyState().exists()).toBe(expectEmptyState);
+ });
+
+ it(`${expectSubscriptionsList ? 'renders' : 'does not render'} subscriptions list`, () => {
+ expect(findSubscriptionsList().exists()).toBe(expectSubscriptionsList);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/jira_import/components/jira_import_app_spec.js b/spec/frontend/jira_import/components/jira_import_app_spec.js
index 27314a0eb6e..cd8024d4962 100644
--- a/spec/frontend/jira_import/components/jira_import_app_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_app_spec.js
@@ -1,6 +1,6 @@
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import { nextTick } from 'vue';
import JiraImportApp from '~/jira_import/components/jira_import_app.vue';
import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
import JiraImportProgress from '~/jira_import/components/jira_import_progress.vue';
@@ -230,7 +230,7 @@ describe('JiraImportApp', () => {
getFormComponent().vm.$emit('error', 'There was an error');
- await Vue.nextTick();
+ await nextTick();
expect(getAlert().exists()).toBe(true);
});
@@ -248,7 +248,7 @@ describe('JiraImportApp', () => {
getAlert().vm.$emit('dismiss');
- await Vue.nextTick();
+ await nextTick();
expect(getAlert().exists()).toBe(false);
});
diff --git a/spec/frontend/jobs/bridge/app_spec.js b/spec/frontend/jobs/bridge/app_spec.js
index c0faab90552..210dcfa364b 100644
--- a/spec/frontend/jobs/bridge/app_spec.js
+++ b/spec/frontend/jobs/bridge/app_spec.js
@@ -1,5 +1,6 @@
-import { nextTick } from 'vue';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import { GlLoadingIcon } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
@@ -17,9 +18,6 @@ import {
mockPipelineQueryResponse,
} from './mock_data';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
-
describe('Bridge Show Page', () => {
let wrapper;
let mockApollo;
@@ -47,10 +45,10 @@ describe('Bridge Show Page', () => {
const createComponentWithApollo = () => {
const handlers = [[getPipelineQuery, mockPipelineQuery]];
+ Vue.use(VueApollo);
mockApollo = createMockApollo(handlers);
createComponent({
- localVue,
apolloProvider: mockApollo,
mocks: {},
});
@@ -81,10 +79,10 @@ describe('Bridge Show Page', () => {
});
describe('after pipeline query is loaded', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mockPipelineQuery.mockResolvedValue(mockPipelineQueryResponse);
createComponentWithApollo();
- waitForPromises();
+ await waitForPromises();
});
it('query is called with correct variables', async () => {
@@ -109,10 +107,10 @@ describe('Bridge Show Page', () => {
});
describe('sidebar expansion', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mockPipelineQuery.mockResolvedValue(mockPipelineQueryResponse);
createComponentWithApollo();
- waitForPromises();
+ await waitForPromises();
});
describe('on resize', () => {
diff --git a/spec/frontend/jobs/components/job_app_spec.js b/spec/frontend/jobs/components/job_app_spec.js
index 07e6ee46c41..d4e1e711777 100644
--- a/spec/frontend/jobs/components/job_app_spec.js
+++ b/spec/frontend/jobs/components/job_app_spec.js
@@ -1,5 +1,6 @@
import { GlLoadingIcon } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import MockAdapter from 'axios-mock-adapter';
import Vuex from 'vuex';
import delayedJobFixture from 'test_fixtures/jobs/delayed.json';
@@ -16,8 +17,7 @@ import axios from '~/lib/utils/axios_utils';
import job from '../mock_data';
describe('Job App', () => {
- const localVue = createLocalVue();
- localVue.use(Vuex);
+ Vue.use(Vuex);
let store;
let wrapper;
@@ -45,7 +45,7 @@ describe('Job App', () => {
wrapper = mount(JobApp, { propsData: { ...props }, store });
};
- const setupAndMount = ({ jobData = {}, jobLogData = {} } = {}) => {
+ const setupAndMount = async ({ jobData = {}, jobLogData = {} } = {}) => {
mock.onGet(initSettings.endpoint).replyOnce(200, { ...job, ...jobData });
mock.onGet(`${initSettings.pagePath}/trace.json`).reply(200, jobLogData);
@@ -53,12 +53,10 @@ describe('Job App', () => {
createComponent();
- return asyncInit
- .then(() => {
- jest.runOnlyPendingTimers();
- })
- .then(() => axios.waitForAll())
- .then(() => wrapper.vm.$nextTick());
+ await asyncInit;
+ jest.runOnlyPendingTimers();
+ await axios.waitForAll();
+ await nextTick();
};
const findLoadingComponent = () => wrapper.find(GlLoadingIcon);
diff --git a/spec/frontend/jobs/components/job_container_item_spec.js b/spec/frontend/jobs/components/job_container_item_spec.js
index 6b488821bc1..eb2b0184e5f 100644
--- a/spec/frontend/jobs/components/job_container_item_spec.js
+++ b/spec/frontend/jobs/components/job_container_item_spec.js
@@ -1,5 +1,6 @@
import { GlIcon, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import delayedJobFixture from 'test_fixtures/jobs/delayed.json';
import JobContainerItem from '~/jobs/components/job_container_item.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
@@ -87,7 +88,7 @@ describe('JobContainerItem', () => {
});
it('displays remaining time in tooltip', async () => {
- await wrapper.vm.$nextTick();
+ await nextTick();
const link = wrapper.findComponent(GlLink);
diff --git a/spec/frontend/jobs/components/job_log_controllers_spec.js b/spec/frontend/jobs/components/job_log_controllers_spec.js
index 0ba07522243..226322a2951 100644
--- a/spec/frontend/jobs/components/job_log_controllers_spec.js
+++ b/spec/frontend/jobs/components/job_log_controllers_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import JobLogControllers from '~/jobs/components/job_log_controllers.vue';
describe('Job log controllers', () => {
@@ -111,7 +112,7 @@ describe('Job log controllers', () => {
it('emits scrollJobLogTop event on click', async () => {
findScrollTop().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted().scrollJobLogTop).toHaveLength(1);
});
@@ -133,7 +134,7 @@ describe('Job log controllers', () => {
it('does not emit scrollJobLogTop event on click', async () => {
findScrollTop().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted().scrollJobLogTop).toBeUndefined();
});
@@ -149,7 +150,7 @@ describe('Job log controllers', () => {
it('emits scrollJobLogBottom event on click', async () => {
findScrollBottom().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted().scrollJobLogBottom).toHaveLength(1);
});
@@ -171,7 +172,7 @@ describe('Job log controllers', () => {
it('does not emit scrollJobLogBottom event on click', async () => {
findScrollBottom().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted().scrollJobLogBottom).toBeUndefined();
});
diff --git a/spec/frontend/jobs/components/log/collapsible_section_spec.js b/spec/frontend/jobs/components/log/collapsible_section_spec.js
index 96bdf03796b..22ddc8b1c2d 100644
--- a/spec/frontend/jobs/components/log/collapsible_section_spec.js
+++ b/spec/frontend/jobs/components/log/collapsible_section_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import CollapsibleSection from '~/jobs/components/log/collapsible_section.vue';
import { collapsibleSectionClosed, collapsibleSectionOpened } from './mock_data';
@@ -69,7 +70,7 @@ describe('Job Log Collapsible Section', () => {
});
});
- it('emits onClickCollapsibleLine on click', () => {
+ it('emits onClickCollapsibleLine on click', async () => {
createComponent({
section: collapsibleSectionOpened,
jobLogEndpoint,
@@ -77,8 +78,7 @@ describe('Job Log Collapsible Section', () => {
findCollapsibleLine().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('onClickCollapsibleLine').length).toBe(1);
- });
+ await nextTick();
+ expect(wrapper.emitted('onClickCollapsibleLine').length).toBe(1);
});
});
diff --git a/spec/frontend/jobs/components/log/line_header_spec.js b/spec/frontend/jobs/components/log/line_header_spec.js
index 9763e2f437b..8055fe64d95 100644
--- a/spec/frontend/jobs/components/log/line_header_spec.js
+++ b/spec/frontend/jobs/components/log/line_header_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DurationBadge from '~/jobs/components/log/duration_badge.vue';
import LineHeader from '~/jobs/components/log/line_header.vue';
import LineNumber from '~/jobs/components/log/line_number.vue';
@@ -75,12 +76,11 @@ describe('Job Log Header Line', () => {
createComponent(data);
});
- it('emits toggleLine event', () => {
+ it('emits toggleLine event', async () => {
wrapper.trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().toggleLine.length).toBe(1);
- });
+ await nextTick();
+ expect(wrapper.emitted().toggleLine.length).toBe(1);
});
});
diff --git a/spec/frontend/jobs/components/log/log_spec.js b/spec/frontend/jobs/components/log/log_spec.js
index 9a5522ab4cd..7e11738f82e 100644
--- a/spec/frontend/jobs/components/log/log_spec.js
+++ b/spec/frontend/jobs/components/log/log_spec.js
@@ -1,4 +1,5 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import Log from '~/jobs/components/log/log.vue';
import { logLinesParserLegacy, logLinesParser } from '~/jobs/store/utils';
@@ -11,12 +12,10 @@ describe('Job Log', () => {
let store;
let origGon;
- const localVue = createLocalVue();
- localVue.use(Vuex);
+ Vue.use(Vuex);
const createComponent = () => {
wrapper = mount(Log, {
- localVue,
store,
});
};
@@ -91,12 +90,10 @@ describe('Job Log, infinitelyCollapsibleSections feature flag enabled', () => {
let store;
let origGon;
- const localVue = createLocalVue();
- localVue.use(Vuex);
+ Vue.use(Vuex);
const createComponent = () => {
wrapper = mount(Log, {
- localVue,
store,
});
};
diff --git a/spec/frontend/jobs/components/manual_variables_form_spec.js b/spec/frontend/jobs/components/manual_variables_form_spec.js
index a5278af8e33..6faab3ddf31 100644
--- a/spec/frontend/jobs/components/manual_variables_form_spec.js
+++ b/spec/frontend/jobs/components/manual_variables_form_spec.js
@@ -1,12 +1,10 @@
import { GlSprintf, GlLink } from '@gitlab/ui';
-import { createLocalVue, mount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ManualVariablesForm from '~/jobs/components/manual_variables_form.vue';
-const localVue = createLocalVue();
-
Vue.use(Vuex);
describe('Manual Variables Form', () => {
@@ -29,9 +27,8 @@ describe('Manual Variables Form', () => {
});
wrapper = extendedWrapper(
- mount(localVue.extend(ManualVariablesForm), {
+ mount(ManualVariablesForm, {
propsData: { ...requiredProps, ...props },
- localVue,
store,
stubs: {
GlSprintf,
diff --git a/spec/frontend/jobs/components/sidebar_spec.js b/spec/frontend/jobs/components/sidebar_spec.js
index 500a1b48950..6e327725627 100644
--- a/spec/frontend/jobs/components/sidebar_spec.js
+++ b/spec/frontend/jobs/components/sidebar_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ArtifactsBlock from '~/jobs/components/artifacts_block.vue';
import JobRetryForwardDeploymentModal from '~/jobs/components/job_retry_forward_deployment_modal.vue';
@@ -189,7 +190,7 @@ describe('Sidebar details block', () => {
locked: false,
};
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findArtifactsBlock().exists()).toBe(true);
});
diff --git a/spec/frontend/jobs/components/table/cells/actions_cell_spec.js b/spec/frontend/jobs/components/table/cells/actions_cell_spec.js
index 6caf36e1461..263698e94e1 100644
--- a/spec/frontend/jobs/components/table/cells/actions_cell_spec.js
+++ b/spec/frontend/jobs/components/table/cells/actions_cell_spec.js
@@ -1,13 +1,16 @@
import { GlModal } from '@gitlab/ui';
import { nextTick } from 'vue';
+import waitForPromises from 'helpers/wait_for_promises';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ActionsCell from '~/jobs/components/table/cells/actions_cell.vue';
import JobPlayMutation from '~/jobs/components/table/graphql/mutations/job_play.mutation.graphql';
import JobRetryMutation from '~/jobs/components/table/graphql/mutations/job_retry.mutation.graphql';
import JobUnscheduleMutation from '~/jobs/components/table/graphql/mutations/job_unschedule.mutation.graphql';
+import JobCancelMutation from '~/jobs/components/table/graphql/mutations/job_cancel.mutation.graphql';
import {
playableJob,
retryableJob,
+ cancelableJob,
scheduledJob,
cannotRetryJob,
cannotPlayJob,
@@ -20,6 +23,7 @@ describe('Job actions cell', () => {
const findRetryButton = () => wrapper.findByTestId('retry');
const findPlayButton = () => wrapper.findByTestId('play');
+ const findCancelButton = () => wrapper.findByTestId('cancel-button');
const findDownloadArtifactsButton = () => wrapper.findByTestId('download-artifacts');
const findCountdownButton = () => wrapper.findByTestId('countdown');
const findPlayScheduledJobButton = () => wrapper.findByTestId('play-scheduled');
@@ -32,6 +36,7 @@ describe('Job actions cell', () => {
data: { JobUnscheduleMutation: { jobId: scheduledJob.id } },
};
const MUTATION_SUCCESS_PLAY = { data: { JobPlayMutation: { jobId: playableJob.id } } };
+ const MUTATION_SUCCESS_CANCEL = { data: { JobCancelMutation: { jobId: cancelableJob.id } } };
const $toast = {
show: jest.fn(),
@@ -88,6 +93,7 @@ describe('Job actions cell', () => {
${findPlayButton} | ${'play'} | ${playableJob}
${findRetryButton} | ${'retry'} | ${retryableJob}
${findDownloadArtifactsButton} | ${'download artifacts'} | ${playableJob}
+ ${findCancelButton} | ${'cancel'} | ${cancelableJob}
`('displays the $action button', ({ button, jobType }) => {
createComponent(jobType);
@@ -95,9 +101,10 @@ describe('Job actions cell', () => {
});
it.each`
- button | mutationResult | action | jobType | mutationFile
- ${findPlayButton} | ${MUTATION_SUCCESS_PLAY} | ${'play'} | ${playableJob} | ${JobPlayMutation}
- ${findRetryButton} | ${MUTATION_SUCCESS} | ${'retry'} | ${retryableJob} | ${JobRetryMutation}
+ button | mutationResult | action | jobType | mutationFile
+ ${findPlayButton} | ${MUTATION_SUCCESS_PLAY} | ${'play'} | ${playableJob} | ${JobPlayMutation}
+ ${findRetryButton} | ${MUTATION_SUCCESS} | ${'retry'} | ${retryableJob} | ${JobRetryMutation}
+ ${findCancelButton} | ${MUTATION_SUCCESS_CANCEL} | ${'cancel'} | ${cancelableJob} | ${JobCancelMutation}
`('performs the $action mutation', ({ button, mutationResult, jobType, mutationFile }) => {
createComponent(jobType, mutationResult);
@@ -111,6 +118,24 @@ describe('Job actions cell', () => {
});
});
+ it.each`
+ button | action | jobType
+ ${findPlayButton} | ${'play'} | ${playableJob}
+ ${findRetryButton} | ${'retry'} | ${retryableJob}
+ ${findCancelButton} | ${'cancel'} | ${cancelableJob}
+ ${findUnscheduleButton} | ${'unschedule'} | ${scheduledJob}
+ `('disables the $action button after first request', async ({ button, jobType }) => {
+ createComponent(jobType);
+
+ expect(button().props('disabled')).toBe(false);
+
+ button().vm.$emit('click');
+
+ await waitForPromises();
+
+ expect(button().props('disabled')).toBe(true);
+ });
+
describe('Scheduled Jobs', () => {
const today = () => new Date('2021-08-31');
diff --git a/spec/frontend/jobs/components/table/job_table_app_spec.js b/spec/frontend/jobs/components/table/job_table_app_spec.js
index 05988eecb10..5ccd38af735 100644
--- a/spec/frontend/jobs/components/table/job_table_app_spec.js
+++ b/spec/frontend/jobs/components/table/job_table_app_spec.js
@@ -1,5 +1,6 @@
import { GlSkeletonLoader, GlAlert, GlEmptyState, GlPagination } from '@gitlab/ui';
-import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
+import { mount, shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -7,11 +8,15 @@ import getJobsQuery from '~/jobs/components/table/graphql/queries/get_jobs.query
import JobsTable from '~/jobs/components/table/jobs_table.vue';
import JobsTableApp from '~/jobs/components/table/jobs_table_app.vue';
import JobsTableTabs from '~/jobs/components/table/jobs_table_tabs.vue';
-import { mockJobsQueryResponse, mockJobsQueryEmptyResponse } from '../../mock_data';
+import {
+ mockJobsQueryResponse,
+ mockJobsQueryEmptyResponse,
+ mockJobsQueryResponseLastPage,
+ mockJobsQueryResponseFirstPage,
+} from '../../mock_data';
const projectPath = 'gitlab-org/gitlab';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
describe('Job table app', () => {
let wrapper;
@@ -50,7 +55,6 @@ describe('Job table app', () => {
provide: {
projectPath,
},
- localVue,
apolloProvider: createMockApolloProvider(handler),
});
};
@@ -96,35 +100,14 @@ describe('Job table app', () => {
describe('pagination', () => {
it('should disable the next page button on the last page', async () => {
createComponent({
- handler: successHandler,
+ handler: jest.fn().mockResolvedValue(mockJobsQueryResponseLastPage),
mountFn: mount,
data: {
- pagination: {
- currentPage: 3,
- },
- jobs: {
- pageInfo: {
- hasPreviousPage: true,
- startCursor: 'abc',
- endCursor: 'bcd',
- },
- },
+ pagination: { currentPage: 3 },
},
});
- await wrapper.vm.$nextTick();
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- jobs: {
- pageInfo: {
- hasNextPage: false,
- },
- },
- });
-
- await wrapper.vm.$nextTick();
+ await waitForPromises();
expect(findPrevious().exists()).toBe(true);
expect(findNext().exists()).toBe(true);
@@ -133,24 +116,16 @@ describe('Job table app', () => {
it('should disable the previous page button on the first page', async () => {
createComponent({
- handler: successHandler,
+ handler: jest.fn().mockResolvedValue(mockJobsQueryResponseFirstPage),
mountFn: mount,
data: {
pagination: {
currentPage: 1,
},
- jobs: {
- pageInfo: {
- hasNextPage: true,
- hasPreviousPage: false,
- startCursor: 'abc',
- endCursor: 'bcd',
- },
- },
},
});
- await wrapper.vm.$nextTick();
+ await waitForPromises();
expect(findPrevious().exists()).toBe(true);
expect(findPrevious().classes('disabled')).toBe(true);
diff --git a/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js b/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js
index 63dcd72f967..1d3845b19bb 100644
--- a/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js
+++ b/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import delayedJobFixture from 'test_fixtures/jobs/delayed.json';
import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
@@ -34,7 +35,7 @@ describe('DelayedJobMixin', () => {
});
it('does not update remaining time after mounting', async () => {
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.text()).toBe('00:00:00');
});
@@ -57,7 +58,7 @@ describe('DelayedJobMixin', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('sets remaining time', () => {
@@ -68,7 +69,7 @@ describe('DelayedJobMixin', () => {
remainingTimeInMilliseconds = 41000;
jest.advanceTimersByTime(1000);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.text()).toBe('00:00:41');
});
});
@@ -104,7 +105,7 @@ describe('DelayedJobMixin', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('sets remaining time', () => {
@@ -115,7 +116,7 @@ describe('DelayedJobMixin', () => {
remainingTimeInMilliseconds = 41000;
jest.advanceTimersByTime(1000);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.text()).toBe('00:00:41');
});
});
diff --git a/spec/frontend/jobs/mock_data.js b/spec/frontend/jobs/mock_data.js
index 45d297ba364..2be78bac8a9 100644
--- a/spec/frontend/jobs/mock_data.js
+++ b/spec/frontend/jobs/mock_data.js
@@ -1579,6 +1579,44 @@ export const mockJobsQueryResponse = {
},
};
+export const mockJobsQueryResponseLastPage = {
+ data: {
+ project: {
+ id: '1',
+ jobs: {
+ ...mockJobsQueryResponse.data.project.jobs,
+ pageInfo: {
+ endCursor: 'eyJpZCI6IjIzMTcifQ',
+ hasNextPage: false,
+ hasPreviousPage: true,
+ startCursor: 'eyJpZCI6IjIzMzYifQ',
+ __typename: 'PageInfo',
+ },
+ },
+ __typename: 'Project',
+ },
+ },
+};
+
+export const mockJobsQueryResponseFirstPage = {
+ data: {
+ project: {
+ id: '1',
+ jobs: {
+ ...mockJobsQueryResponse.data.project.jobs,
+ pageInfo: {
+ endCursor: 'eyJpZCI6IjIzMTcifQ',
+ hasNextPage: true,
+ hasPreviousPage: false,
+ startCursor: 'eyJpZCI6IjIzMzYifQ',
+ __typename: 'PageInfo',
+ },
+ },
+ __typename: 'Project',
+ },
+ },
+};
+
export const mockJobsQueryEmptyResponse = {
data: {
project: {
@@ -1653,6 +1691,65 @@ export const retryableJob = {
__typename: 'CiJob',
};
+export const cancelableJob = {
+ artifacts: {
+ nodes: [],
+ __typename: 'CiJobArtifactConnection',
+ },
+ allowFailure: false,
+ status: 'PENDING',
+ scheduledAt: null,
+ manualJob: false,
+ triggered: null,
+ createdByTag: false,
+ detailedStatus: {
+ id: 'pending-1305-1305',
+ detailsPath: '/root/lots-of-jobs-project/-/jobs/1305',
+ group: 'pending',
+ icon: 'status_pending',
+ label: 'pending',
+ text: 'pending',
+ tooltip: 'pending',
+ action: {
+ id: 'Ci::Build-pending-1305',
+ buttonTitle: 'Cancel this job',
+ icon: 'cancel',
+ method: 'post',
+ path: '/root/lots-of-jobs-project/-/jobs/1305/cancel',
+ title: 'Cancel',
+ __typename: 'StatusAction',
+ },
+ __typename: 'DetailedStatus',
+ },
+ id: 'gid://gitlab/Ci::Build/1305',
+ refName: 'main',
+ refPath: '/root/lots-of-jobs-project/-/commits/main',
+ tags: [],
+ shortSha: '750605f2',
+ commitPath: '/root/lots-of-jobs-project/-/commit/750605f29530778cf0912779eba6d073128962a5',
+ stage: {
+ id: 'gid://gitlab/Ci::Stage/181',
+ name: 'deploy',
+ __typename: 'CiStage',
+ },
+ name: 'job_212',
+ duration: null,
+ finishedAt: null,
+ coverage: null,
+ retryable: false,
+ playable: false,
+ cancelable: true,
+ active: true,
+ stuck: false,
+ userPermissions: {
+ readBuild: true,
+ readJobArtifacts: true,
+ updateBuild: true,
+ __typename: 'JobPermissions',
+ },
+ __typename: 'CiJob',
+};
+
export const cannotRetryJob = {
...retryableJob,
userPermissions: { readBuild: true, updateBuild: false, __typename: 'JobPermissions' },
diff --git a/spec/frontend/lib/apollo/suppress_network_errors_during_navigation_link_spec.js b/spec/frontend/lib/apollo/suppress_network_errors_during_navigation_link_spec.js
index 7b604724977..971ba8b583c 100644
--- a/spec/frontend/lib/apollo/suppress_network_errors_during_navigation_link_spec.js
+++ b/spec/frontend/lib/apollo/suppress_network_errors_during_navigation_link_spec.js
@@ -1,4 +1,4 @@
-import { ApolloLink, Observable } from 'apollo-link';
+import { ApolloLink, Observable } from '@apollo/client/core';
import waitForPromises from 'helpers/wait_for_promises';
import { getSuppressNetworkErrorsDuringNavigationLink } from '~/lib/apollo/suppress_network_errors_during_navigation_link';
import { isNavigatingAway } from '~/lib/utils/is_navigating_away';
diff --git a/spec/frontend/lib/utils/apollo_startup_js_link_spec.js b/spec/frontend/lib/utils/apollo_startup_js_link_spec.js
index c0e5b06651f..e58bc063004 100644
--- a/spec/frontend/lib/utils/apollo_startup_js_link_spec.js
+++ b/spec/frontend/lib/utils/apollo_startup_js_link_spec.js
@@ -1,4 +1,4 @@
-import { ApolloLink, Observable } from 'apollo-link';
+import { ApolloLink, Observable } from '@apollo/client/core';
import { StartupJSLink } from '~/lib/utils/apollo_startup_js_link';
describe('StartupJSLink', () => {
diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js
index 3e2ba918d9b..3fea08d5512 100644
--- a/spec/frontend/lib/utils/common_utils_spec.js
+++ b/spec/frontend/lib/utils/common_utils_spec.js
@@ -394,8 +394,7 @@ describe('common_utils', () => {
describe('backOff', () => {
beforeEach(() => {
- // shortcut our timeouts otherwise these tests will take a long time to finish
- jest.spyOn(window, 'setTimeout').mockImplementation((cb) => setImmediate(cb, 0));
+ jest.spyOn(window, 'setTimeout');
});
it('solves the promise from the callback', (done) => {
@@ -446,6 +445,7 @@ describe('common_utils', () => {
if (numberOfCalls < 3) {
numberOfCalls += 1;
next();
+ jest.runOnlyPendingTimers();
} else {
stop(resp);
}
@@ -464,7 +464,10 @@ describe('common_utils', () => {
it('rejects the backOff promise after timing out', (done) => {
commonUtils
- .backOff((next) => next(), 64000)
+ .backOff((next) => {
+ next();
+ jest.runOnlyPendingTimers();
+ }, 64000)
.catch((errBackoffResp) => {
const timeouts = window.setTimeout.mock.calls.map(([, timeout]) => timeout);
diff --git a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js
index d19f9352bbc..e06d1384610 100644
--- a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js
+++ b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js
@@ -6,11 +6,13 @@ describe('Confirm Modal', () => {
let wrapper;
let modal;
- const createComponent = ({ primaryText, primaryVariant } = {}) => {
+ const createComponent = ({ primaryText, primaryVariant, title, hideCancel = false } = {}) => {
wrapper = mount(ConfirmModal, {
propsData: {
primaryText,
primaryVariant,
+ hideCancel,
+ title,
},
});
};
@@ -55,5 +57,19 @@ describe('Confirm Modal', () => {
expect(customProps.text).toBe('OK');
expect(customProps.attributes.variant).toBe('confirm');
});
+
+ it('should hide the cancel button if `hideCancel` is set', () => {
+ createComponent({ hideCancel: true });
+ const props = findGlModal().props();
+
+ expect(props.actionCancel).toBeNull();
+ });
+
+ it('should set the modal title when the `title` prop is set', () => {
+ const title = 'Modal title';
+ createComponent({ title });
+
+ expect(findGlModal().props().title).toBe(title);
+ });
});
});
diff --git a/spec/frontend/lib/utils/number_utility_spec.js b/spec/frontend/lib/utils/number_utility_spec.js
index e743678ea90..dc4aa0ea5ed 100644
--- a/spec/frontend/lib/utils/number_utility_spec.js
+++ b/spec/frontend/lib/utils/number_utility_spec.js
@@ -4,6 +4,7 @@ import {
bytesToMiB,
bytesToGiB,
numberToHumanSize,
+ numberToMetricPrefix,
sum,
isOdd,
median,
@@ -99,6 +100,21 @@ describe('Number Utils', () => {
});
});
+ describe('numberToMetricPrefix', () => {
+ it.each`
+ number | expected
+ ${123} | ${'123'}
+ ${1234} | ${'1.2k'}
+ ${12345} | ${'12.3k'}
+ ${123456} | ${'123.5k'}
+ ${1234567} | ${'1.2m'}
+ ${12345678} | ${'12.3m'}
+ ${123456789} | ${'123.5m'}
+ `('returns $expected given $number', ({ number, expected }) => {
+ expect(numberToMetricPrefix(number)).toBe(expected);
+ });
+ });
+
describe('sum', () => {
it('should add up two values', () => {
expect(sum(1, 2)).toEqual(3);
diff --git a/spec/frontend/lib/utils/table_utility_spec.js b/spec/frontend/lib/utils/table_utility_spec.js
index 75b9252aa40..0ceccbe4c74 100644
--- a/spec/frontend/lib/utils/table_utility_spec.js
+++ b/spec/frontend/lib/utils/table_utility_spec.js
@@ -8,4 +8,35 @@ describe('table_utility', () => {
expect(tableUtils.thWidthClass(width)).toBe(`gl-w-${width}p ${DEFAULT_TH_CLASSES}`);
});
});
+
+ describe('sortObjectToString', () => {
+ it('returns the expected sorting string ending in "DESC" when sortDesc is true', () => {
+ expect(tableUtils.sortObjectToString({ sortBy: 'mergedAt', sortDesc: true })).toBe(
+ 'MERGED_AT_DESC',
+ );
+ });
+
+ it('returns the expected sorting string ending in "ASC" when sortDesc is false', () => {
+ expect(tableUtils.sortObjectToString({ sortBy: 'mergedAt', sortDesc: false })).toBe(
+ 'MERGED_AT_ASC',
+ );
+ });
+ });
+
+ describe('sortStringToObject', () => {
+ it.each`
+ sortBy | sortDesc | sortString
+ ${'mergedAt'} | ${true} | ${'MERGED_AT_DESC'}
+ ${'mergedAt'} | ${false} | ${'MERGED_AT_ASC'}
+ ${'severity'} | ${true} | ${'SEVERITY_DESC'}
+ ${'severity'} | ${false} | ${'SEVERITY_ASC'}
+ ${null} | ${null} | ${'SEVERITY'}
+ ${null} | ${null} | ${''}
+ `(
+ 'returns the expected sort object when the sort string is "$sortString"',
+ ({ sortBy, sortDesc, sortString }) => {
+ expect(tableUtils.sortStringToObject(sortString)).toStrictEqual({ sortBy, sortDesc });
+ },
+ );
+ });
});
diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js
index ab81ec47b64..dded32cc890 100644
--- a/spec/frontend/lib/utils/text_markdown_spec.js
+++ b/spec/frontend/lib/utils/text_markdown_spec.js
@@ -165,6 +165,80 @@ describe('init markdown', () => {
// cursor placement should be between tags
expect(textArea.selectionStart).toBe(start.length + tag.length);
});
+
+ describe('Continuing markdown lists', () => {
+ const enterEvent = new KeyboardEvent('keydown', { key: 'Enter' });
+
+ beforeEach(() => {
+ gon.features = { markdownContinueLists: true };
+ });
+
+ it.each`
+ text | expected
+ ${'- item'} | ${'- item\n- '}
+ ${'- [ ] item'} | ${'- [ ] item\n- [ ] '}
+ ${'- [x] item'} | ${'- [x] item\n- [x] '}
+ ${'- item\n - second'} | ${'- item\n - second\n - '}
+ ${'1. item'} | ${'1. item\n1. '}
+ ${'1. [ ] item'} | ${'1. [ ] item\n1. [ ] '}
+ ${'1. [x] item'} | ${'1. [x] item\n1. [x] '}
+ ${'108. item'} | ${'108. item\n108. '}
+ ${'108. item\n - second'} | ${'108. item\n - second\n - '}
+ ${'108. item\n 1. second'} | ${'108. item\n 1. second\n 1. '}
+ `('adds correct list continuation characters', ({ text, expected }) => {
+ textArea.value = text;
+ textArea.setSelectionRange(text.length, text.length);
+
+ textArea.addEventListener('keydown', keypressNoteText);
+ textArea.dispatchEvent(enterEvent);
+
+ expect(textArea.value).toEqual(expected);
+ expect(textArea.selectionStart).toBe(expected.length);
+ });
+
+ // test that when pressing Enter on an empty list item, the empty
+ // list item text is selected, so that when the Enter propagates,
+ // it's removed
+ it.each`
+ text | expected
+ ${'- item\n- '} | ${'- item\n'}
+ ${'- [ ] item\n- [ ] '} | ${'- [ ] item\n'}
+ ${'- [x] item\n- [x] '} | ${'- [x] item\n'}
+ ${'- item\n - second\n - '} | ${'- item\n - second\n'}
+ ${'1. item\n1. '} | ${'1. item\n'}
+ ${'1. [ ] item\n1. [ ] '} | ${'1. [ ] item\n'}
+ ${'1. [x] item\n1. [x] '} | ${'1. [x] item\n'}
+ ${'108. item\n108. '} | ${'108. item\n'}
+ ${'108. item\n - second\n - '} | ${'108. item\n - second\n'}
+ ${'108. item\n 1. second\n 1. '} | ${'108. item\n 1. second\n'}
+ `('adds correct list continuation characters', ({ text, expected }) => {
+ textArea.value = text;
+ textArea.setSelectionRange(text.length, text.length);
+
+ textArea.addEventListener('keydown', keypressNoteText);
+ textArea.dispatchEvent(enterEvent);
+
+ expect(textArea.value.substr(0, textArea.selectionStart)).toEqual(expected);
+ expect(textArea.selectionStart).toBe(expected.length);
+ expect(textArea.selectionEnd).toBe(text.length);
+ });
+
+ it('does nothing if feature flag disabled', () => {
+ gon.features = { markdownContinueLists: false };
+
+ const text = '- item';
+ const expected = '- item';
+
+ textArea.value = text;
+ textArea.setSelectionRange(text.length, text.length);
+
+ textArea.addEventListener('keydown', keypressNoteText);
+ textArea.dispatchEvent(enterEvent);
+
+ expect(textArea.value).toEqual(expected);
+ expect(textArea.selectionStart).toBe(expected.length);
+ });
+ });
});
describe('with selection', () => {
diff --git a/spec/frontend/lib/utils/vuex_module_mappers_spec.js b/spec/frontend/lib/utils/vuex_module_mappers_spec.js
index d7e51e4daca..1821a15f677 100644
--- a/spec/frontend/lib/utils/vuex_module_mappers_spec.js
+++ b/spec/frontend/lib/utils/vuex_module_mappers_spec.js
@@ -1,4 +1,4 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import {
@@ -10,13 +10,12 @@ import {
const TEST_MODULE_NAME = 'testModuleName';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
// setup test component and store ----------------------------------------------
//
// These are used to indirectly test `vuex_module_mappers`.
-const TestComponent = Vue.extend({
+const TestComponent = {
props: {
vuexModule: {
type: String,
@@ -47,7 +46,7 @@ const TestComponent = Vue.extend({
<pre data-testid="state">{{ stateJson }}</pre>
<pre data-testid="getters">{{ gettersJson }}</pre>
</div>`,
-});
+};
const createTestStore = () => {
return new Vuex.Store({
@@ -94,7 +93,6 @@ describe('~/lib/utils/vuex_module_mappers', () => {
vuexModule: TEST_MODULE_NAME,
},
store,
- localVue,
});
});
diff --git a/spec/frontend/lib/utils/yaml_spec.js b/spec/frontend/lib/utils/yaml_spec.js
new file mode 100644
index 00000000000..d1ce00130e2
--- /dev/null
+++ b/spec/frontend/lib/utils/yaml_spec.js
@@ -0,0 +1,105 @@
+import { Document, parseDocument } from 'yaml';
+import { merge } from '~/lib/utils/yaml';
+
+// Mock data for Comments on pairs
+const COMMENTS_ON_PAIRS_SOURCE = `foo:
+ # barbaz
+ bar: baz
+
+ # bazboo
+ baz: boo
+`;
+
+const COMMENTS_ON_PAIRS_TARGET = `foo:
+ # abcdef
+ abc: def
+ # boobaz
+ boo: baz
+`;
+
+const COMMENTS_ON_PAIRS_EXPECTED = `foo:
+ # abcdef
+ abc: def
+ # boobaz
+ boo: baz
+ # barbaz
+ bar: baz
+
+ # bazboo
+ baz: boo
+`;
+
+// Mock data for Comments on seqs
+const COMMENTS_ON_SEQS_SOURCE = `foo:
+ # barbaz
+ - barbaz
+ # bazboo
+ - baz: boo
+`;
+
+const COMMENTS_ON_SEQS_TARGET = `foo:
+ # abcdef
+ - abcdef
+
+ # boobaz
+ - boobaz
+`;
+
+const COMMENTS_ON_SEQS_EXPECTED = `foo:
+ # abcdef
+ - abcdef
+
+ # boobaz
+ - boobaz
+ # barbaz
+ - barbaz
+ # bazboo
+ - baz: boo
+`;
+
+describe('Yaml utility functions', () => {
+ describe('merge', () => {
+ const getAsNode = (yamlStr) => {
+ return parseDocument(yamlStr).contents;
+ };
+
+ describe('Merge two Nodes', () => {
+ it.each`
+ scenario | source | target | options | expected
+ ${'merge a map'} | ${getAsNode('foo:\n bar: baz\n')} | ${'foo:\n abc: def\n'} | ${undefined} | ${'foo:\n abc: def\n bar: baz\n'}
+ ${'merge a seq'} | ${getAsNode('foo:\n - bar\n')} | ${'foo:\n - abc\n'} | ${undefined} | ${'foo:\n - bar\n'}
+ ${'merge-append seqs'} | ${getAsNode('foo:\n - bar\n')} | ${'foo:\n - abc\n'} | ${{ onSequence: 'append' }} | ${'foo:\n - abc\n - bar\n'}
+ ${'merge-replace a seq'} | ${getAsNode('foo:\n - bar\n')} | ${'foo:\n - abc\n'} | ${{ onSequence: 'replace' }} | ${'foo:\n - bar\n'}
+ ${'override existing paths'} | ${getAsNode('foo:\n bar: baz\n')} | ${'foo:\n bar: boo\n'} | ${undefined} | ${'foo:\n bar: baz\n'}
+ ${'deep maps'} | ${getAsNode('foo:\n bar:\n abc: def\n')} | ${'foo:\n bar:\n baz: boo\n jkl: mno\n'} | ${undefined} | ${'foo:\n bar:\n baz: boo\n abc: def\n jkl: mno\n'}
+ ${'append maps inside seqs'} | ${getAsNode('foo:\n - abc: def\n')} | ${'foo:\n - bar: baz\n'} | ${{ onSequence: 'append' }} | ${'foo:\n - bar: baz\n - abc: def\n'}
+ ${'inexistent paths create new nodes'} | ${getAsNode('foo:\n bar: baz\n')} | ${'abc: def\n'} | ${undefined} | ${'abc: def\nfoo:\n bar: baz\n'}
+ ${'document as source'} | ${parseDocument('foo:\n bar: baz\n')} | ${'foo:\n abc: def\n'} | ${undefined} | ${'foo:\n abc: def\n bar: baz\n'}
+ ${'object as source'} | ${{ foo: { bar: 'baz' } }} | ${'foo:\n abc: def\n'} | ${undefined} | ${'foo:\n abc: def\n bar: baz\n'}
+ ${'comments on pairs'} | ${parseDocument(COMMENTS_ON_PAIRS_SOURCE)} | ${COMMENTS_ON_PAIRS_TARGET} | ${undefined} | ${COMMENTS_ON_PAIRS_EXPECTED}
+ ${'comments on seqs'} | ${parseDocument(COMMENTS_ON_SEQS_SOURCE)} | ${COMMENTS_ON_SEQS_TARGET} | ${{ onSequence: 'append' }} | ${COMMENTS_ON_SEQS_EXPECTED}
+ `('$scenario', ({ source, target, expected, options }) => {
+ const targetDoc = parseDocument(target);
+ merge(targetDoc, source, options);
+ const expectedDoc = parseDocument(expected);
+ expect(targetDoc.toString()).toEqual(expectedDoc.toString());
+ });
+
+ it('type conflict will throw an Error', () => {
+ const sourceDoc = parseDocument('foo:\n bar:\n - baz\n');
+ const targetDoc = parseDocument('foo:\n bar: def\n');
+ expect(() => merge(targetDoc, sourceDoc)).toThrow(
+ 'Type conflict at "foo.bar": Destination node is of type Scalar, the node' +
+ ' to be merged is of type YAMLSeq',
+ );
+ });
+
+ it('merging a collection into an empty doc', () => {
+ const targetDoc = new Document();
+ merge(targetDoc, { foo: { bar: 'baz' } });
+ const expected = parseDocument('foo:\n bar: baz\n');
+ expect(targetDoc.toString()).toEqual(expected.toString());
+ });
+ });
+ });
+});
diff --git a/spec/frontend/listbox/index_spec.js b/spec/frontend/listbox/index_spec.js
new file mode 100644
index 00000000000..45659a0e523
--- /dev/null
+++ b/spec/frontend/listbox/index_spec.js
@@ -0,0 +1,111 @@
+import { nextTick } from 'vue';
+import { getAllByRole, getByRole } from '@testing-library/dom';
+import { GlDropdown } from '@gitlab/ui';
+import { createWrapper } from '@vue/test-utils';
+import { initListbox, parseAttributes } from '~/listbox';
+import { getFixture, setHTMLFixture } from 'helpers/fixtures';
+
+jest.mock('~/lib/utils/url_utility');
+
+const fixture = getFixture('listbox/redirect_listbox.html');
+
+const parsedAttributes = (() => {
+ const div = document.createElement('div');
+ div.innerHTML = fixture;
+ return parseAttributes(div.firstChild);
+})();
+
+describe('initListbox', () => {
+ let instance;
+
+ afterEach(() => {
+ if (instance) {
+ instance.$destroy();
+ }
+ });
+
+ const setup = (...args) => {
+ instance = initListbox(...args);
+ };
+
+ // TODO: Rewrite these finders to use better semantics once the
+ // implementation is switched to GlListbox
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/348738
+ const findToggleButton = () => document.body.querySelector('.gl-dropdown-toggle');
+ const findItem = (text) => getByRole(document.body, 'menuitem', { name: text });
+ const findItems = () => getAllByRole(document.body, 'menuitem');
+ const findSelectedItems = () =>
+ findItems().filter(
+ (menuitem) =>
+ !menuitem
+ .querySelector('.gl-new-dropdown-item-check-icon')
+ .classList.contains('gl-visibility-hidden'),
+ );
+
+ it('returns null given no element', () => {
+ setup();
+
+ expect(instance).toBe(null);
+ });
+
+ it('throws given an invalid element', () => {
+ expect(() => setup(document.body)).toThrow();
+ });
+
+ describe('given a valid element', () => {
+ let onChangeSpy;
+
+ beforeEach(async () => {
+ setHTMLFixture(fixture);
+ onChangeSpy = jest.fn();
+ setup(document.querySelector('.js-redirect-listbox'), { onChange: onChangeSpy });
+
+ await nextTick();
+ });
+
+ it('returns an instance', () => {
+ expect(instance).not.toBe(null);
+ });
+
+ it('renders button with selected item text', () => {
+ expect(findToggleButton().textContent.trim()).toBe('Bar');
+ });
+
+ it('has the correct item selected', () => {
+ const selectedItems = findSelectedItems();
+ expect(selectedItems).toHaveLength(1);
+ expect(selectedItems[0].textContent.trim()).toBe('Bar');
+ });
+
+ it('applies additional classes from the original element', () => {
+ expect(instance.$el.classList).toContain('test-class-1', 'test-class-2');
+ });
+
+ describe.each(parsedAttributes.items)('clicking on an item', (item) => {
+ beforeEach(async () => {
+ findItem(item.text).click();
+
+ await nextTick();
+ });
+
+ it('calls the onChange callback with the item', () => {
+ expect(onChangeSpy).toHaveBeenCalledWith(item);
+ });
+
+ it('updates the toggle button text', () => {
+ expect(findToggleButton().textContent.trim()).toBe(item.text);
+ });
+
+ it('marks the item as selected', () => {
+ const selectedItems = findSelectedItems();
+ expect(selectedItems).toHaveLength(1);
+ expect(selectedItems[0].textContent.trim()).toBe(item.text);
+ });
+ });
+
+ it('passes the "right" prop through to the underlying component', () => {
+ const wrapper = createWrapper(instance).findComponent(GlDropdown);
+ expect(wrapper.props('right')).toBe(parsedAttributes.right);
+ });
+ });
+});
diff --git a/spec/frontend/listbox/redirect_behavior_spec.js b/spec/frontend/listbox/redirect_behavior_spec.js
new file mode 100644
index 00000000000..7b2a40b65ce
--- /dev/null
+++ b/spec/frontend/listbox/redirect_behavior_spec.js
@@ -0,0 +1,51 @@
+import { initListbox } from '~/listbox';
+import { initRedirectListboxBehavior } from '~/listbox/redirect_behavior';
+import { redirectTo } from '~/lib/utils/url_utility';
+import { getFixture, setHTMLFixture } from 'helpers/fixtures';
+
+jest.mock('~/lib/utils/url_utility');
+jest.mock('~/listbox', () => ({
+ initListbox: jest.fn().mockReturnValue({ foo: true }),
+}));
+
+const fixture = getFixture('listbox/redirect_listbox.html');
+
+describe('initRedirectListboxBehavior', () => {
+ let instances;
+
+ beforeEach(() => {
+ setHTMLFixture(`
+ ${fixture}
+ ${fixture}
+ `);
+
+ instances = initRedirectListboxBehavior();
+ });
+
+ it('calls initListbox for each .js-redirect-listbox', () => {
+ expect(instances).toEqual([{ foo: true }, { foo: true }]);
+
+ expect(initListbox).toHaveBeenCalledTimes(2);
+
+ initListbox.mock.calls.forEach((callArgs, i) => {
+ const elements = document.querySelectorAll('.js-redirect-listbox');
+
+ expect(callArgs[0]).toBe(elements[i]);
+ expect(callArgs[1]).toEqual({
+ onChange: expect.any(Function),
+ });
+ });
+ });
+
+ it('passes onChange handler to initListbox that calls redirectTo', () => {
+ const [firstCallArgs] = initListbox.mock.calls;
+ const { onChange } = firstCallArgs[1];
+ const mockItem = { href: '/foo' };
+
+ expect(redirectTo).not.toHaveBeenCalled();
+
+ onChange(mockItem);
+
+ expect(redirectTo).toHaveBeenCalledWith(mockItem.href);
+ });
+});
diff --git a/spec/frontend/logs/components/environment_logs_spec.js b/spec/frontend/logs/components/environment_logs_spec.js
index b107708ac2c..84dc0bdf6cd 100644
--- a/spec/frontend/logs/components/environment_logs_spec.js
+++ b/spec/frontend/logs/components/environment_logs_spec.js
@@ -1,5 +1,6 @@
import { GlSprintf, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { scrollDown } from '~/lib/utils/scroll_utils';
import EnvironmentLogs from '~/logs/components/environment_logs.vue';
@@ -338,35 +339,32 @@ describe('EnvironmentLogs', () => {
expect(store.dispatch).not.toHaveBeenCalledWith(`${module}/fetchMoreLogsPrepend`, undefined);
});
- it('`scroll` on a scrollable target results in enabled scroll buttons', () => {
+ it('`scroll` on a scrollable target results in enabled scroll buttons', async () => {
const target = { scrollTop: 10, clientHeight: 10, scrollHeight: 21 };
state.logs.isLoading = true;
findInfiniteScroll().vm.$emit('scroll', { target });
- return wrapper.vm.$nextTick(() => {
- expect(findLogControlButtons().props('scrollDownButtonDisabled')).toEqual(false);
- });
+ await nextTick();
+ expect(findLogControlButtons().props('scrollDownButtonDisabled')).toEqual(false);
});
- it('`scroll` on a non-scrollable target in disabled scroll buttons', () => {
+ it('`scroll` on a non-scrollable target in disabled scroll buttons', async () => {
const target = { scrollTop: 10, clientHeight: 10, scrollHeight: 20 };
state.logs.isLoading = true;
findInfiniteScroll().vm.$emit('scroll', { target });
- return wrapper.vm.$nextTick(() => {
- expect(findLogControlButtons().props('scrollDownButtonDisabled')).toEqual(true);
- });
+ await nextTick();
+ expect(findLogControlButtons().props('scrollDownButtonDisabled')).toEqual(true);
});
- it('`scroll` on no target results in disabled scroll buttons', () => {
+ it('`scroll` on no target results in disabled scroll buttons', async () => {
state.logs.isLoading = true;
findInfiniteScroll().vm.$emit('scroll', { target: undefined });
- return wrapper.vm.$nextTick(() => {
- expect(findLogControlButtons().props('scrollDownButtonDisabled')).toEqual(true);
- });
+ await nextTick();
+ expect(findLogControlButtons().props('scrollDownButtonDisabled')).toEqual(true);
});
});
});
diff --git a/spec/frontend/logs/components/log_control_buttons_spec.js b/spec/frontend/logs/components/log_control_buttons_spec.js
index 9c1617e4daa..e249272b87d 100644
--- a/spec/frontend/logs/components/log_control_buttons_spec.js
+++ b/spec/frontend/logs/components/log_control_buttons_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import LogControlButtons from '~/logs/components/log_control_buttons.vue';
describe('LogControlButtons', () => {
@@ -33,7 +34,7 @@ describe('LogControlButtons', () => {
expect(findRefreshBtn().is(GlButton)).toBe(true);
});
- it('emits a `refresh` event on click on `refresh` button', () => {
+ it('emits a `refresh` event on click on `refresh` button', async () => {
initWrapper();
// An `undefined` value means no event was emitted
@@ -41,16 +42,15 @@ describe('LogControlButtons', () => {
findRefreshBtn().vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('refresh')).toHaveLength(1);
- });
+ await nextTick();
+ expect(wrapper.emitted('refresh')).toHaveLength(1);
});
describe('when scrolling actions are enabled', () => {
- beforeEach(() => {
+ beforeEach(async () => {
// mock scrolled to the middle of a long page
initWrapper();
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('click on "scroll to top" scrolls up', () => {
@@ -71,19 +71,18 @@ describe('LogControlButtons', () => {
});
describe('when scrolling actions are disabled', () => {
- beforeEach(() => {
+ beforeEach(async () => {
initWrapper({ listeners: {} });
- return wrapper.vm.$nextTick();
+ await nextTick();
});
- it('buttons are disabled', () => {
- return wrapper.vm.$nextTick(() => {
- expect(findScrollToTop().exists()).toBe(false);
- expect(findScrollToBottom().exists()).toBe(false);
- // This should be enabled when gitlab-ui contains:
- // https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/1149
- // expect(findScrollToBottom().is('[disabled]')).toBe(true);
- });
+ it('buttons are disabled', async () => {
+ await nextTick();
+ expect(findScrollToTop().exists()).toBe(false);
+ expect(findScrollToBottom().exists()).toBe(false);
+ // This should be enabled when gitlab-ui contains:
+ // https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/1149
+ // expect(findScrollToBottom().is('[disabled]')).toBe(true);
});
});
});
diff --git a/spec/frontend/members/components/action_buttons/approve_access_request_button_spec.js b/spec/frontend/members/components/action_buttons/approve_access_request_button_spec.js
index 936715e7723..08d7cf3c932 100644
--- a/spec/frontend/members/components/action_buttons/approve_access_request_button_spec.js
+++ b/spec/frontend/members/components/action_buttons/approve_access_request_button_spec.js
@@ -1,5 +1,6 @@
import { GlButton, GlForm } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ApproveAccessRequestButton from '~/members/components/action_buttons/approve_access_request_button.vue';
@@ -7,8 +8,7 @@ import { MEMBER_TYPES } from '~/members/constants';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('ApproveAccessRequestButton', () => {
let wrapper;
@@ -29,7 +29,6 @@ describe('ApproveAccessRequestButton', () => {
const createComponent = (propsData = {}, state) => {
wrapper = shallowMount(ApproveAccessRequestButton, {
- localVue,
store: createStore(state),
provide: {
namespace: MEMBER_TYPES.accessRequest,
diff --git a/spec/frontend/members/components/action_buttons/remove_group_link_button_spec.js b/spec/frontend/members/components/action_buttons/remove_group_link_button_spec.js
index f91aef131a1..ca655e36c42 100644
--- a/spec/frontend/members/components/action_buttons/remove_group_link_button_spec.js
+++ b/spec/frontend/members/components/action_buttons/remove_group_link_button_spec.js
@@ -1,13 +1,13 @@
import { GlButton } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import RemoveGroupLinkButton from '~/members/components/action_buttons/remove_group_link_button.vue';
import { MEMBER_TYPES } from '~/members/constants';
import { group } from '../../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('RemoveGroupLinkButton', () => {
let wrapper;
@@ -29,7 +29,6 @@ describe('RemoveGroupLinkButton', () => {
const createComponent = () => {
wrapper = mount(RemoveGroupLinkButton, {
- localVue,
store: createStore(),
provide: {
namespace: MEMBER_TYPES.group,
diff --git a/spec/frontend/members/components/action_buttons/remove_member_button_spec.js b/spec/frontend/members/components/action_buttons/remove_member_button_spec.js
index 1a031cc56d6..0e5b667eb9b 100644
--- a/spec/frontend/members/components/action_buttons/remove_member_button_spec.js
+++ b/spec/frontend/members/components/action_buttons/remove_member_button_spec.js
@@ -1,13 +1,13 @@
import { GlButton } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { modalData } from 'jest/members/mock_data';
import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue';
import { MEMBER_TYPES } from '~/members/constants';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('RemoveMemberButton', () => {
let wrapper;
@@ -33,7 +33,6 @@ describe('RemoveMemberButton', () => {
const createComponent = (propsData = {}, state) => {
wrapper = shallowMount(RemoveMemberButton, {
- localVue,
store: createStore(state),
provide: {
namespace: MEMBER_TYPES.user,
diff --git a/spec/frontend/members/components/action_buttons/resend_invite_button_spec.js b/spec/frontend/members/components/action_buttons/resend_invite_button_spec.js
index 547e067450c..8e933d16463 100644
--- a/spec/frontend/members/components/action_buttons/resend_invite_button_spec.js
+++ b/spec/frontend/members/components/action_buttons/resend_invite_button_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ResendInviteButton from '~/members/components/action_buttons/resend_invite_button.vue';
@@ -7,8 +8,7 @@ import { MEMBER_TYPES } from '~/members/constants';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('ResendInviteButton', () => {
let wrapper;
@@ -29,7 +29,6 @@ describe('ResendInviteButton', () => {
const createComponent = (propsData = {}, state) => {
wrapper = shallowMount(ResendInviteButton, {
- localVue,
store: createStore(state),
provide: {
namespace: MEMBER_TYPES.invite,
diff --git a/spec/frontend/members/components/app_spec.js b/spec/frontend/members/components/app_spec.js
index 9590cd9d8d4..4124a1870a6 100644
--- a/spec/frontend/members/components/app_spec.js
+++ b/spec/frontend/members/components/app_spec.js
@@ -1,6 +1,6 @@
import { GlAlert } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import * as commonUtils from '~/lib/utils/common_utils';
import MembersApp from '~/members/components/app.vue';
@@ -11,8 +11,7 @@ import { RECEIVE_MEMBER_ROLE_ERROR, HIDE_ERROR } from '~/members/store/mutation_
import mutations from '~/members/store/mutations';
describe('MembersApp', () => {
- const localVue = createLocalVue();
- localVue.use(Vuex);
+ Vue.use(Vuex);
let wrapper;
let store;
@@ -33,7 +32,6 @@ describe('MembersApp', () => {
});
wrapper = shallowMount(MembersApp, {
- localVue,
propsData: {
namespace: MEMBER_TYPES.group,
tabQueryParamValue: TAB_QUERY_PARAM_VALUES.group,
diff --git a/spec/frontend/members/components/avatars/user_avatar_spec.js b/spec/frontend/members/components/avatars/user_avatar_spec.js
index 5cf3a4cdc13..7bcf4a11413 100644
--- a/spec/frontend/members/components/avatars/user_avatar_spec.js
+++ b/spec/frontend/members/components/avatars/user_avatar_spec.js
@@ -1,7 +1,8 @@
import { GlAvatarLink, GlBadge } from '@gitlab/ui';
-import { within } from '@testing-library/dom';
-import { mount, createWrapper } from '@vue/test-utils';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import UserAvatar from '~/members/components/avatars/user_avatar.vue';
+import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
+
import { member as memberMock, member2faEnabled, orphanedMember } from '../../mock_data';
describe('UserAvatar', () => {
@@ -10,7 +11,7 @@ describe('UserAvatar', () => {
const { user } = memberMock;
const createComponent = (propsData = {}, provide = {}) => {
- wrapper = mount(UserAvatar, {
+ wrapper = mountExtended(UserAvatar, {
propsData: {
member: memberMock,
isCurrentUser: false,
@@ -23,9 +24,6 @@ describe('UserAvatar', () => {
});
};
- const getByText = (text, options) =>
- createWrapper(within(wrapper.element).findByText(text, options));
-
const findStatusEmoji = (emoji) => wrapper.find(`gl-emoji[data-name="${emoji}"]`);
afterEach(() => {
@@ -48,13 +46,13 @@ describe('UserAvatar', () => {
it("renders user's name", () => {
createComponent();
- expect(getByText(user.name).exists()).toBe(true);
+ expect(wrapper.findByText(user.name).exists()).toBe(true);
});
it("renders user's username", () => {
createComponent();
- expect(getByText(`@${user.username}`).exists()).toBe(true);
+ expect(wrapper.findByText(`@${user.username}`).exists()).toBe(true);
});
it("renders user's avatar", () => {
@@ -67,7 +65,7 @@ describe('UserAvatar', () => {
it('displays an orphaned user', () => {
createComponent({ member: orphanedMember });
- expect(getByText('Orphaned member').exists()).toBe(true);
+ expect(wrapper.findByText('Orphaned member').exists()).toBe(true);
});
});
@@ -85,13 +83,13 @@ describe('UserAvatar', () => {
it('renders the "It\'s you" badge when member is current user', () => {
createComponent({ isCurrentUser: true });
- expect(getByText("It's you").exists()).toBe(true);
+ expect(wrapper.findByText("It's you").exists()).toBe(true);
});
it('does not render 2FA badge when `canManageMembers` is `false`', () => {
createComponent({ member: member2faEnabled }, { canManageMembers: false });
- expect(within(wrapper.element).queryByText('2FA')).toBe(null);
+ expect(wrapper.findByText('2FA').exists()).toBe(false);
});
});
@@ -112,6 +110,23 @@ describe('UserAvatar', () => {
expect(findStatusEmoji(emoji).exists()).toBe(true);
});
+
+ describe('when `user.showStatus` is `false', () => {
+ it('does not display status emoji', () => {
+ createComponent({
+ member: {
+ ...memberMock,
+ user: {
+ ...memberMock.user,
+ showStatus: false,
+ status: { emoji, messageHtml: 'On vacation' },
+ },
+ },
+ });
+
+ expect(findStatusEmoji(emoji).exists()).toBe(false);
+ });
+ });
});
describe('when not set', () => {
@@ -122,4 +137,30 @@ describe('UserAvatar', () => {
});
});
});
+
+ describe('user availability', () => {
+ describe('when `user.availability` is `null`', () => {
+ it("does not show `(Busy)` next to user's name", () => {
+ createComponent();
+
+ expect(wrapper.findByText('(Busy)').exists()).toBe(false);
+ });
+ });
+
+ describe(`when user.availability is ${AVAILABILITY_STATUS.BUSY}`, () => {
+ it("shows `(Busy)` next to user's name", () => {
+ createComponent({
+ member: {
+ ...memberMock,
+ user: {
+ ...memberMock.user,
+ availability: AVAILABILITY_STATUS.BUSY,
+ },
+ },
+ });
+
+ expect(wrapper.findByText('(Busy)').exists()).toBe(true);
+ });
+ });
+ });
});
diff --git a/spec/frontend/members/components/filter_sort/filter_sort_container_spec.js b/spec/frontend/members/components/filter_sort/filter_sort_container_spec.js
index 16ac52737bc..4ca8a3bdc36 100644
--- a/spec/frontend/members/components/filter_sort/filter_sort_container_spec.js
+++ b/spec/frontend/members/components/filter_sort/filter_sort_container_spec.js
@@ -1,12 +1,12 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import FilterSortContainer from '~/members/components/filter_sort/filter_sort_container.vue';
import MembersFilteredSearchBar from '~/members/components/filter_sort/members_filtered_search_bar.vue';
import SortDropdown from '~/members/components/filter_sort/sort_dropdown.vue';
import { MEMBER_TYPES } from '~/members/constants';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('FilterSortContainer', () => {
let wrapper;
@@ -32,7 +32,6 @@ describe('FilterSortContainer', () => {
});
wrapper = shallowMount(FilterSortContainer, {
- localVue,
store,
provide: {
namespace: MEMBER_TYPES.user,
diff --git a/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js b/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js
index 3f47fa024bc..ee2fbbe57b9 100644
--- a/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js
+++ b/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js
@@ -1,5 +1,6 @@
import { GlFilteredSearchToken } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import setWindowLocation from 'helpers/set_window_location_helper';
import { redirectTo } from '~/lib/utils/url_utility';
@@ -18,8 +19,7 @@ jest.mock('~/lib/utils/url_utility', () => {
};
});
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('MembersFilteredSearchBar', () => {
let wrapper;
@@ -44,7 +44,6 @@ describe('MembersFilteredSearchBar', () => {
});
wrapper = shallowMount(MembersFilteredSearchBar, {
- localVue,
provide: {
sourceId: 1,
canManageMembers: true,
diff --git a/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js b/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js
index d0684acd487..709ad907a38 100644
--- a/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js
+++ b/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js
@@ -1,13 +1,13 @@
import { GlSorting, GlSortingItem } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import setWindowLocation from 'helpers/set_window_location_helper';
import * as urlUtilities from '~/lib/utils/url_utility';
import SortDropdown from '~/members/components/filter_sort/sort_dropdown.vue';
import { MEMBER_TYPES } from '~/members/constants';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('SortDropdown', () => {
let wrapper;
@@ -35,7 +35,6 @@ describe('SortDropdown', () => {
});
wrapper = mount(SortDropdown, {
- localVue,
provide: {
sourceId: 1,
namespace: MEMBER_TYPES.user,
diff --git a/spec/frontend/members/components/modals/leave_modal_spec.js b/spec/frontend/members/components/modals/leave_modal_spec.js
index f755f08dbf2..cdbabb2f646 100644
--- a/spec/frontend/members/components/modals/leave_modal_spec.js
+++ b/spec/frontend/members/components/modals/leave_modal_spec.js
@@ -1,8 +1,8 @@
import { GlModal, GlForm } from '@gitlab/ui';
import { within } from '@testing-library/dom';
-import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
+import { mount, createWrapper } from '@vue/test-utils';
import { cloneDeep } from 'lodash';
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import LeaveModal from '~/members/components/modals/leave_modal.vue';
import { LEAVE_MODAL_ID, MEMBER_TYPES } from '~/members/constants';
@@ -12,8 +12,7 @@ import { member } from '../../mock_data';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('LeaveModal', () => {
let wrapper;
@@ -34,7 +33,6 @@ describe('LeaveModal', () => {
const createComponent = (propsData = {}, state) => {
wrapper = mount(LeaveModal, {
- localVue,
store: createStore(state),
provide: {
namespace: MEMBER_TYPES.user,
diff --git a/spec/frontend/members/components/modals/remove_group_link_modal_spec.js b/spec/frontend/members/components/modals/remove_group_link_modal_spec.js
index 313c237f51c..447496910b8 100644
--- a/spec/frontend/members/components/modals/remove_group_link_modal_spec.js
+++ b/spec/frontend/members/components/modals/remove_group_link_modal_spec.js
@@ -1,7 +1,7 @@
import { GlModal, GlForm } from '@gitlab/ui';
import { within } from '@testing-library/dom';
-import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { mount, createWrapper } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import RemoveGroupLinkModal from '~/members/components/modals/remove_group_link_modal.vue';
import { REMOVE_GROUP_LINK_MODAL_ID, MEMBER_TYPES } from '~/members/constants';
@@ -9,8 +9,7 @@ import { group } from '../../mock_data';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('RemoveGroupLinkModal', () => {
let wrapper;
@@ -38,7 +37,6 @@ describe('RemoveGroupLinkModal', () => {
const createComponent = (state) => {
wrapper = mount(RemoveGroupLinkModal, {
- localVue,
store: createStore(state),
provide: {
namespace: MEMBER_TYPES.group,
diff --git a/spec/frontend/members/components/table/expiration_datepicker_spec.js b/spec/frontend/members/components/table/expiration_datepicker_spec.js
index 3c4a9ba37ff..4fb43fbd888 100644
--- a/spec/frontend/members/components/table/expiration_datepicker_spec.js
+++ b/spec/frontend/members/components/table/expiration_datepicker_spec.js
@@ -1,6 +1,6 @@
import { GlDatepicker } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { useFakeDate } from 'helpers/fake_date';
import waitForPromises from 'helpers/wait_for_promises';
@@ -8,8 +8,7 @@ import ExpirationDatepicker from '~/members/components/table/expiration_datepick
import { MEMBER_TYPES } from '~/members/constants';
import { member } from '../../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('ExpirationDatepicker', () => {
// March 15th, 2020 3:00
@@ -49,7 +48,6 @@ describe('ExpirationDatepicker', () => {
provide: {
namespace: MEMBER_TYPES.user,
},
- localVue,
store: createStore(),
mocks: {
$toast,
diff --git a/spec/frontend/members/components/table/members_table_cell_spec.js b/spec/frontend/members/components/table/members_table_cell_spec.js
index 5375ee11736..6575a7c7126 100644
--- a/spec/frontend/members/components/table/members_table_cell_spec.js
+++ b/spec/frontend/members/components/table/members_table_cell_spec.js
@@ -1,4 +1,5 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import MembersTableCell from '~/members/components/table/members_table_cell.vue';
import { MEMBER_TYPES } from '~/members/constants';
@@ -36,9 +37,8 @@ describe('MembersTableCell', () => {
},
};
- const localVue = createLocalVue();
- localVue.use(Vuex);
- localVue.component('WrappedComponent', WrappedComponent);
+ Vue.use(Vuex);
+ Vue.component('WrappedComponent', WrappedComponent);
const createStore = (state = {}) => {
return new Vuex.Store({
@@ -50,7 +50,6 @@ describe('MembersTableCell', () => {
const createComponent = (propsData, state) => {
wrapper = mount(MembersTableCell, {
- localVue,
propsData,
store: createStore(state),
provide: {
diff --git a/spec/frontend/members/components/table/members_table_spec.js b/spec/frontend/members/components/table/members_table_spec.js
index 580e5edd652..b2756e506eb 100644
--- a/spec/frontend/members/components/table/members_table_spec.js
+++ b/spec/frontend/members/components/table/members_table_spec.js
@@ -1,5 +1,5 @@
import { GlBadge, GlPagination, GlTable } from '@gitlab/ui';
-import { createLocalVue } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import setWindowLocation from 'helpers/set_window_location_helper';
import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
@@ -16,6 +16,7 @@ import {
MEMBER_STATE_AWAITING,
MEMBER_STATE_ACTIVE,
USER_STATE_BLOCKED_PENDING_APPROVAL,
+ BADGE_LABELS_AWAITING_USER_SIGNUP,
BADGE_LABELS_PENDING_OWNER_APPROVAL,
TAB_QUERY_PARAM_VALUES,
} from '~/members/constants';
@@ -28,8 +29,7 @@ import {
pagination,
} from '../../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('MembersTable', () => {
let wrapper;
@@ -56,7 +56,6 @@ describe('MembersTable', () => {
const createComponent = (state, provide = {}) => {
wrapper = mountExtended(MembersTable, {
- localVue,
propsData: {
tabQueryParamValue: TAB_QUERY_PARAM_VALUES.invite,
},
@@ -133,9 +132,9 @@ describe('MembersTable', () => {
describe('Invited column', () => {
describe.each`
state | userState | expectedBadgeLabel
- ${MEMBER_STATE_CREATED} | ${null} | ${''}
+ ${MEMBER_STATE_CREATED} | ${null} | ${BADGE_LABELS_AWAITING_USER_SIGNUP}
${MEMBER_STATE_CREATED} | ${USER_STATE_BLOCKED_PENDING_APPROVAL} | ${BADGE_LABELS_PENDING_OWNER_APPROVAL}
- ${MEMBER_STATE_AWAITING} | ${''} | ${''}
+ ${MEMBER_STATE_AWAITING} | ${''} | ${BADGE_LABELS_AWAITING_USER_SIGNUP}
${MEMBER_STATE_AWAITING} | ${USER_STATE_BLOCKED_PENDING_APPROVAL} | ${BADGE_LABELS_PENDING_OWNER_APPROVAL}
${MEMBER_STATE_AWAITING} | ${'something_else'} | ${BADGE_LABELS_PENDING_OWNER_APPROVAL}
${MEMBER_STATE_ACTIVE} | ${null} | ${''}
diff --git a/spec/frontend/members/components/table/role_dropdown_spec.js b/spec/frontend/members/components/table/role_dropdown_spec.js
index a4a4c620921..d4d950e99ba 100644
--- a/spec/frontend/members/components/table/role_dropdown_spec.js
+++ b/spec/frontend/members/components/table/role_dropdown_spec.js
@@ -1,8 +1,8 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { within } from '@testing-library/dom';
-import { mount, createWrapper, createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { mount, createWrapper } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
import { BV_DROPDOWN_SHOW } from '~/lib/utils/constants';
@@ -10,8 +10,7 @@ import RoleDropdown from '~/members/components/table/role_dropdown.vue';
import { MEMBER_TYPES } from '~/members/constants';
import { member } from '../../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('RoleDropdown', () => {
let wrapper;
@@ -42,7 +41,6 @@ describe('RoleDropdown', () => {
permissions: {},
...propsData,
},
- localVue,
store: createStore(),
mocks: {
$toast,
diff --git a/spec/frontend/members/mock_data.js b/spec/frontend/members/mock_data.js
index 218db0b587a..83856a00a15 100644
--- a/spec/frontend/members/mock_data.js
+++ b/spec/frontend/members/mock_data.js
@@ -25,6 +25,8 @@ export const member = {
twoFactorEnabled: false,
oncallSchedules: [{ name: 'schedule 1' }],
escalationPolicies: [{ name: 'policy 1' }],
+ availability: null,
+ showStatus: true,
},
id: 238,
createdAt: '2020-07-17T16:22:46.923Z',
diff --git a/spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js b/spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js
index a09edb50f20..750fff9b0aa 100644
--- a/spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js
+++ b/spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js
@@ -1,5 +1,6 @@
import { GlSprintf } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import InlineConflictLines from '~/merge_conflicts/components/inline_conflict_lines.vue';
import ParallelConflictLines from '~/merge_conflicts/components/parallel_conflict_lines.vue';
@@ -8,8 +9,7 @@ import { createStore } from '~/merge_conflicts/store';
import { decorateFiles } from '~/merge_conflicts/utils';
import { conflictsMock } from '../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Merge Conflict Resolver App', () => {
let wrapper;
@@ -93,7 +93,7 @@ describe('Merge Conflict Resolver App', () => {
expect(inlineButton.props('selected')).toBe(false);
inlineButton.vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(inlineButton.props('selected')).toBe(true);
});
@@ -111,7 +111,7 @@ describe('Merge Conflict Resolver App', () => {
mountComponent();
findSideBySideButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
const parallelConflictLinesComponent = findParallelConflictLines(findFiles().at(0));
diff --git a/spec/frontend/merge_conflicts/store/actions_spec.js b/spec/frontend/merge_conflicts/store/actions_spec.js
index 8fa8765a9f9..3e1774a6d56 100644
--- a/spec/frontend/merge_conflicts/store/actions_spec.js
+++ b/spec/frontend/merge_conflicts/store/actions_spec.js
@@ -207,7 +207,10 @@ describe('merge conflicts actions', () => {
],
[],
() => {
- expect(Cookies.set).toHaveBeenCalledWith('diff_view', payload);
+ expect(Cookies.set).toHaveBeenCalledWith('diff_view', payload, {
+ expires: 365,
+ secure: false,
+ });
done();
},
);
diff --git a/spec/frontend/merge_request_spec.js b/spec/frontend/merge_request_spec.js
index 0b7ed349507..9229b353685 100644
--- a/spec/frontend/merge_request_spec.js
+++ b/spec/frontend/merge_request_spec.js
@@ -1,6 +1,7 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import { TEST_HOST } from 'spec/test_constants';
+import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import MergeRequest from '~/merge_request';
@@ -27,31 +28,31 @@ describe('MergeRequest', () => {
mock.restore();
});
- it('modifies the Markdown field', (done) => {
+ it('modifies the Markdown field', async () => {
jest.spyOn($, 'ajax').mockImplementation();
const changeEvent = document.createEvent('HTMLEvents');
changeEvent.initEvent('change', true, true);
$('input[type=checkbox]').first().attr('checked', true)[0].dispatchEvent(changeEvent);
- setImmediate(() => {
- expect($('.js-task-list-field').val()).toBe(
- '- [x] Task List Item\n- [ ]\n- [ ] Task List Item 2\n',
- );
- done();
- });
+
+ await waitForPromises();
+
+ expect($('.js-task-list-field').val()).toBe(
+ '- [x] Task List Item\n- [ ]\n- [ ] Task List Item 2\n',
+ );
});
- it('ensure that task with only spaces does not get checked incorrectly', (done) => {
+ it('ensure that task with only spaces does not get checked incorrectly', async () => {
// fixed in 'deckar01-task_list', '2.2.1' gem
jest.spyOn($, 'ajax').mockImplementation();
const changeEvent = document.createEvent('HTMLEvents');
changeEvent.initEvent('change', true, true);
$('input[type=checkbox]').last().attr('checked', true)[0].dispatchEvent(changeEvent);
- setImmediate(() => {
- expect($('.js-task-list-field').val()).toBe(
- '- [ ] Task List Item\n- [ ]\n- [x] Task List Item 2\n',
- );
- done();
- });
+
+ await waitForPromises();
+
+ expect($('.js-task-list-field').val()).toBe(
+ '- [ ] Task List Item\n- [ ]\n- [x] Task List Item 2\n',
+ );
});
describe('tasklist', () => {
@@ -60,29 +61,27 @@ describe('MergeRequest', () => {
const index = 3;
const checked = true;
- it('submits an ajax request on tasklist:changed', (done) => {
+ it('submits an ajax request on tasklist:changed', async () => {
$('.js-task-list-field').trigger({
type: 'tasklist:changed',
detail: { lineNumber, lineSource, index, checked },
});
- setImmediate(() => {
- expect(axios.patch).toHaveBeenCalledWith(
- `${TEST_HOST}/frontend-fixtures/merge-requests-project/-/merge_requests/1.json`,
- {
- merge_request: {
- description: '- [ ] Task List Item\n- [ ]\n- [ ] Task List Item 2\n',
- lock_version: 0,
- update_task: { line_number: lineNumber, line_source: lineSource, index, checked },
- },
- },
- );
+ await waitForPromises();
- done();
- });
+ expect(axios.patch).toHaveBeenCalledWith(
+ `${TEST_HOST}/frontend-fixtures/merge-requests-project/-/merge_requests/1.json`,
+ {
+ merge_request: {
+ description: '- [ ] Task List Item\n- [ ]\n- [ ] Task List Item 2\n',
+ lock_version: 0,
+ update_task: { line_number: lineNumber, line_source: lineSource, index, checked },
+ },
+ },
+ );
});
- it('shows an error notification when tasklist update failed', (done) => {
+ it('shows an error notification when tasklist update failed', async () => {
mock
.onPatch(`${TEST_HOST}/frontend-fixtures/merge-requests-project/-/merge_requests/1.json`)
.reply(409, {});
@@ -92,13 +91,11 @@ describe('MergeRequest', () => {
detail: { lineNumber, lineSource, index, checked },
});
- setImmediate(() => {
- expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
- 'Someone edited this merge request at the same time you did. Please refresh the page to see changes.',
- );
+ await waitForPromises();
- done();
- });
+ expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
+ 'Someone edited this merge request at the same time you did. Please refresh the page to see changes.',
+ );
});
});
});
diff --git a/spec/frontend/mocks_spec.js b/spec/frontend/mocks_spec.js
index 110c418e579..0813d2073b9 100644
--- a/spec/frontend/mocks_spec.js
+++ b/spec/frontend/mocks_spec.js
@@ -8,13 +8,10 @@ describe('Mock auto-injection', () => {
failMock = jest.spyOn(global, 'fail').mockImplementation();
});
- it('~/lib/utils/axios_utils', () => {
- return Promise.all([
- expect(axios.get('http://gitlab.com')).rejects.toThrow('Unexpected unmocked request'),
- setImmediate(() => {
- expect(failMock).toHaveBeenCalledTimes(1);
- }),
- ]);
+ it('~/lib/utils/axios_utils', async () => {
+ await expect(axios.get('http://gitlab.com')).rejects.toThrow('Unexpected unmocked request');
+
+ expect(failMock).toHaveBeenCalledTimes(1);
});
it('jQuery.ajax()', () => {
diff --git a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
index 681fb05a6c4..bd2e818df4f 100644
--- a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
+++ b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
@@ -8,6 +8,28 @@ exports[`Dashboard template matches the default snapshot 1`] = `
metricsdashboardbasepath="/monitoring/monitor-project/-/environments/1/metrics"
metricsendpoint="/monitoring/monitor-project/-/environments/1/additional_metrics.json"
>
+ <div>
+ <gl-alert-stub
+ class="mb-3"
+ dismissible="true"
+ dismisslabel="Dismiss"
+ primarybuttonlink=""
+ primarybuttontext=""
+ secondarybuttonlink=""
+ secondarybuttontext=""
+ title="Feature deprecation and removal"
+ variant="danger"
+ >
+ <gl-sprintf-stub
+ message="The metrics, logs and tracing features were deprecated in GitLab 14.7 and are %{epicStart} scheduled for removal %{epicEnd} in GitLab 15.0."
+ />
+
+ <gl-sprintf-stub
+ message="For information on a possible replacement %{epicStart} learn more about Opstrace %{epicEnd}."
+ />
+ </gl-alert-stub>
+ </div>
+
<div
class="prometheus-graphs-header d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 border-bottom bg-gray-light"
>
diff --git a/spec/frontend/monitoring/components/charts/stacked_column_spec.js b/spec/frontend/monitoring/components/charts/stacked_column_spec.js
index f47728313c6..9cab3650f28 100644
--- a/spec/frontend/monitoring/components/charts/stacked_column_spec.js
+++ b/spec/frontend/monitoring/components/charts/stacked_column_spec.js
@@ -2,6 +2,7 @@ import { GlStackedColumnChart, GlChartLegend } from '@gitlab/ui/dist/charts';
import { shallowMount, mount } from '@vue/test-utils';
import { cloneDeep } from 'lodash';
import timezoneMock from 'timezone-mock';
+import { nextTick } from 'vue';
import StackedColumnChart from '~/monitoring/components/charts/stacked_column.vue';
import { stackedColumnGraphData } from '../../graph_data';
@@ -34,9 +35,9 @@ describe('Stacked column chart component', () => {
});
describe('when graphData is present', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createWrapper();
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('chart is rendered', () => {
@@ -116,25 +117,24 @@ describe('Stacked column chart component', () => {
expect(xAxis.axisLabel.formatter('2020-01-30T12:01:00.000Z')).toBe('4:01 AM');
});
- it('date is shown in UTC', () => {
+ it('date is shown in UTC', async () => {
wrapper.setProps({ timezone: 'UTC' });
- return wrapper.vm.$nextTick().then(() => {
- const { xAxis } = findChart().props('option');
- expect(xAxis.axisLabel.formatter('2020-01-30T12:01:00.000Z')).toBe('12:01 PM');
- });
+ await nextTick();
+ const { xAxis } = findChart().props('option');
+ expect(xAxis.axisLabel.formatter('2020-01-30T12:01:00.000Z')).toBe('12:01 PM');
});
});
});
describe('when graphData has results missing', () => {
- beforeEach(() => {
+ beforeEach(async () => {
const graphData = cloneDeep(stackedColumnMockedData);
graphData.metrics[0].result = null;
createWrapper({ graphData });
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('chart is rendered', () => {
@@ -147,7 +147,7 @@ describe('Stacked column chart component', () => {
wrapper = createWrapper({}, mount);
});
- it('allows user to override legend label texts using props', () => {
+ it('allows user to override legend label texts using props', async () => {
const legendRelatedProps = {
legendMinText: 'legendMinText',
legendMaxText: 'legendMaxText',
@@ -158,9 +158,8 @@ describe('Stacked column chart component', () => {
...legendRelatedProps,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findChart().props()).toMatchObject(legendRelatedProps);
- });
+ await nextTick();
+ expect(findChart().props()).toMatchObject(legendRelatedProps);
});
it('should render a tabular legend layout by default', () => {
diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js
index ff6f0b9b0c7..73abd81d889 100644
--- a/spec/frontend/monitoring/components/charts/time_series_spec.js
+++ b/spec/frontend/monitoring/components/charts/time_series_spec.js
@@ -7,6 +7,7 @@ import {
} from '@gitlab/ui/dist/charts';
import { mount, shallowMount } from '@vue/test-utils';
import timezoneMock from 'timezone-mock';
+import { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import { setTestTimeout } from 'helpers/timeout';
import { shallowWrapperContainsSlotText } from 'helpers/vue_test_utils_helper';
@@ -70,12 +71,12 @@ describe('Time series component', () => {
describe('general functions', () => {
const findChart = () => wrapper.find({ ref: 'chart' });
- beforeEach(() => {
+ beforeEach(async () => {
createWrapper({}, mount);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
- it('allows user to override legend label texts using props', () => {
+ it('allows user to override legend label texts using props', async () => {
const legendRelatedProps = {
legendMinText: 'legendMinText',
legendMaxText: 'legendMaxText',
@@ -86,9 +87,8 @@ describe('Time series component', () => {
...legendRelatedProps,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findChart().props()).toMatchObject(legendRelatedProps);
- });
+ await nextTick();
+ expect(findChart().props()).toMatchObject(legendRelatedProps);
});
it('chart sets a default height', () => {
@@ -96,14 +96,13 @@ describe('Time series component', () => {
expect(wrapper.props('height')).toBe(chartHeight);
});
- it('chart has a configurable height', () => {
+ it('chart has a configurable height', async () => {
const mockHeight = 599;
createWrapper();
wrapper.setProps({ height: mockHeight });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.props('height')).toBe(mockHeight);
- });
+ await nextTick();
+ expect(wrapper.props('height')).toBe(mockHeight);
});
describe('events', () => {
@@ -112,7 +111,7 @@ describe('Time series component', () => {
let startValue;
let endValue;
- beforeEach(() => {
+ beforeEach(async () => {
eChartMock = {
handlers: {},
getOption: () => ({
@@ -132,9 +131,8 @@ describe('Time series component', () => {
};
createWrapper({}, mount);
- return wrapper.vm.$nextTick(() => {
- findChart().vm.$emit('created', eChartMock);
- });
+ await nextTick();
+ findChart().vm.$emit('created', eChartMock);
});
it('handles datazoom event from chart', () => {
@@ -203,10 +201,10 @@ describe('Time series component', () => {
});
describe('when series is of line type', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createWrapper({}, mount);
wrapper.vm.formatTooltipText(mockLineSeriesData());
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('formats tooltip title', () => {
@@ -241,34 +239,31 @@ describe('Time series component', () => {
timezoneMock.unregister();
});
- it('formats tooltip title in local timezone by default', () => {
+ it('formats tooltip title in local timezone by default', async () => {
createWrapper();
wrapper.vm.formatTooltipText(mockLineSeriesData());
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.tooltip.title).toBe('16 Jul 2019, 3:14AM (GMT-0700)');
- });
+ await nextTick();
+ expect(wrapper.vm.tooltip.title).toBe('16 Jul 2019, 3:14AM (GMT-0700)');
});
- it('formats tooltip title in local timezone', () => {
+ it('formats tooltip title in local timezone', async () => {
createWrapper({ timezone: 'LOCAL' });
wrapper.vm.formatTooltipText(mockLineSeriesData());
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.tooltip.title).toBe('16 Jul 2019, 3:14AM (GMT-0700)');
- });
+ await nextTick();
+ expect(wrapper.vm.tooltip.title).toBe('16 Jul 2019, 3:14AM (GMT-0700)');
});
- it('formats tooltip title in UTC format', () => {
+ it('formats tooltip title in UTC format', async () => {
createWrapper({ timezone: 'UTC' });
wrapper.vm.formatTooltipText(mockLineSeriesData());
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM (UTC)');
- });
+ await nextTick();
+ expect(wrapper.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM (UTC)');
});
});
});
describe('when series is of scatter type, for deployments', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.vm.formatTooltipText({
...mockAnnotationsSeriesData,
seriesData: mockAnnotationsSeriesData.seriesData.map((data) => ({
@@ -276,7 +271,7 @@ describe('Time series component', () => {
data: annotationsMetadata,
})),
});
- return wrapper.vm.$nextTick;
+ await nextTick();
});
it('set tooltip type to deployments', () => {
@@ -297,9 +292,9 @@ describe('Time series component', () => {
});
describe('when series is of scatter type and deployments data is missing', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.vm.formatTooltipText(mockAnnotationsSeriesData);
- return wrapper.vm.$nextTick;
+ await nextTick();
});
it('formats tooltip title', () => {
@@ -397,14 +392,13 @@ describe('Time series component', () => {
});
});
- it('is not set if time range is not set or incorrectly set', () => {
+ it('is not set if time range is not set or incorrectly set', async () => {
wrapper.setProps({
timeRange: {},
});
- return wrapper.vm.$nextTick(() => {
- expect(getChartOptions().xAxis).not.toHaveProperty('min');
- expect(getChartOptions().xAxis).not.toHaveProperty('max');
- });
+ await nextTick();
+ expect(getChartOptions().xAxis).not.toHaveProperty('min');
+ expect(getChartOptions().xAxis).not.toHaveProperty('max');
});
});
@@ -430,17 +424,16 @@ describe('Time series component', () => {
option2: 'option2',
};
- it('arbitrary options', () => {
+ it('arbitrary options', async () => {
wrapper.setProps({
option: mockOption,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(getChartOptions()).toEqual(expect.objectContaining(mockOption));
- });
+ await nextTick();
+ expect(getChartOptions()).toEqual(expect.objectContaining(mockOption));
});
- it('additional series', () => {
+ it('additional series', async () => {
wrapper.setProps({
option: {
series: [
@@ -453,15 +446,14 @@ describe('Time series component', () => {
},
});
- return wrapper.vm.$nextTick().then(() => {
- const optionSeries = getChartOptions().series;
+ await nextTick();
+ const optionSeries = getChartOptions().series;
- expect(optionSeries.length).toEqual(2);
- expect(optionSeries[0].name).toEqual(mockSeriesName);
- });
+ expect(optionSeries.length).toEqual(2);
+ expect(optionSeries[0].name).toEqual(mockSeriesName);
});
- it('additional y-axis data', () => {
+ it('additional y-axis data', async () => {
const mockCustomYAxisOption = {
name: 'Custom y-axis label',
axisLabel: {
@@ -475,14 +467,13 @@ describe('Time series component', () => {
},
});
- return wrapper.vm.$nextTick().then(() => {
- const { yAxis } = getChartOptions();
+ await nextTick();
+ const { yAxis } = getChartOptions();
- expect(yAxis[0]).toMatchObject(mockCustomYAxisOption);
- });
+ expect(yAxis[0]).toMatchObject(mockCustomYAxisOption);
});
- it('additional x axis data', () => {
+ it('additional x axis data', async () => {
const mockCustomXAxisOption = {
name: 'Custom x axis label',
};
@@ -493,11 +484,10 @@ describe('Time series component', () => {
},
});
- return wrapper.vm.$nextTick().then(() => {
- const { xAxis } = getChartOptions();
+ await nextTick();
+ const { xAxis } = getChartOptions();
- expect(xAxis).toMatchObject(mockCustomXAxisOption);
- });
+ expect(xAxis).toMatchObject(mockCustomXAxisOption);
});
});
@@ -625,12 +615,12 @@ describe('Time series component', () => {
describe(`GitLab UI: ${dynamicComponent.chartType}`, () => {
const findChartComponent = () => wrapper.find(dynamicComponent.component);
- beforeEach(() => {
+ beforeEach(async () => {
createWrapper(
{ graphData: timeSeriesGraphData({ type: dynamicComponent.chartType }) },
mount,
);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('exists', () => {
@@ -645,22 +635,21 @@ describe('Time series component', () => {
expect(props.formatTooltipText).toBe(wrapper.vm.formatTooltipText);
});
- it('receives a tooltip title', () => {
+ it('receives a tooltip title', async () => {
const mockTitle = 'mockTitle';
wrapper.vm.tooltip.title = mockTitle;
- return wrapper.vm.$nextTick(() => {
- expect(
- shallowWrapperContainsSlotText(findChartComponent(), 'tooltip-title', mockTitle),
- ).toBe(true);
- });
+ await nextTick();
+ expect(
+ shallowWrapperContainsSlotText(findChartComponent(), 'tooltip-title', mockTitle),
+ ).toBe(true);
});
describe('when tooltip is showing deployment data', () => {
const mockSha = 'mockSha';
const commitUrl = `${mockProjectDir}/-/commit/${mockSha}`;
- 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({
@@ -668,7 +657,7 @@ describe('Time series component', () => {
type: 'deployments',
},
});
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('uses deployment title', () => {
@@ -677,16 +666,15 @@ describe('Time series component', () => {
).toBe(true);
});
- it('renders clickable commit sha in tooltip content', () => {
+ it('renders clickable commit sha in tooltip content', async () => {
wrapper.vm.tooltip.sha = mockSha;
wrapper.vm.tooltip.commitUrl = commitUrl;
- return wrapper.vm.$nextTick(() => {
- const commitLink = wrapper.find(GlLink);
+ await nextTick();
+ const commitLink = wrapper.find(GlLink);
- expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true);
- expect(commitLink.attributes('href')).toEqual(commitUrl);
- });
+ expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true);
+ expect(commitLink.attributes('href')).toEqual(commitUrl);
});
});
});
@@ -696,11 +684,11 @@ describe('Time series component', () => {
describe('with multiple time series', () => {
describe('General functions', () => {
- beforeEach(() => {
+ beforeEach(async () => {
const graphData = timeSeriesGraphData({ type: panelTypes.AREA_CHART, multiMetric: true });
createWrapper({ graphData }, mount);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
describe('Color match', () => {
@@ -742,9 +730,9 @@ describe('Time series component', () => {
describe('legend layout', () => {
const findLegend = () => wrapper.find(GlChartLegend);
- beforeEach(() => {
+ beforeEach(async () => {
createWrapper({}, mount);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('should render a tabular legend layout by default', () => {
diff --git a/spec/frontend/monitoring/components/create_dashboard_modal_spec.js b/spec/frontend/monitoring/components/create_dashboard_modal_spec.js
index 8202d423ff3..88de3467580 100644
--- a/spec/frontend/monitoring/components/create_dashboard_modal_spec.js
+++ b/spec/frontend/monitoring/components/create_dashboard_modal_spec.js
@@ -1,5 +1,6 @@
import { GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import CreateDashboardModal from '~/monitoring/components/create_dashboard_modal.vue';
describe('Create dashboard modal', () => {
@@ -32,13 +33,12 @@ describe('Create dashboard modal', () => {
wrapper.destroy();
});
- it('has button that links to the project url', () => {
+ it('has button that links to the project url', async () => {
findRepoButton().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(findRepoButton().exists()).toBe(true);
- expect(findRepoButton().attributes('href')).toBe(defaultProps.projectPath);
- });
+ await nextTick();
+ expect(findRepoButton().exists()).toBe(true);
+ expect(findRepoButton().attributes('href')).toBe(defaultProps.projectPath);
});
it('has button that links to the docs', () => {
diff --git a/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js b/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js
index f2116c1f478..d0d0c3071d5 100644
--- a/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js
@@ -1,5 +1,6 @@
import { GlDropdownItem, GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
import { redirectTo } from '~/lib/utils/url_utility';
import ActionsMenu from '~/monitoring/components/dashboard_actions_menu.vue';
@@ -60,22 +61,20 @@ describe('Actions menu', () => {
});
describe('add metric item', () => {
- it('is rendered when custom metrics are available', () => {
+ it('is rendered when custom metrics are available', async () => {
createShallowWrapper();
- return wrapper.vm.$nextTick(() => {
- expect(findAddMetricItem().exists()).toBe(true);
- });
+ await nextTick();
+ expect(findAddMetricItem().exists()).toBe(true);
});
- it('is not rendered when custom metrics are not available', () => {
+ it('is not rendered when custom metrics are not available', async () => {
createShallowWrapper({
addingMetricsAvailable: false,
});
- return wrapper.vm.$nextTick(() => {
- expect(findAddMetricItem().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findAddMetricItem().exists()).toBe(false);
});
describe('when available', () => {
@@ -119,30 +118,23 @@ describe('Actions menu', () => {
origPage = document.body.dataset.page;
document.body.dataset.page = 'projects:environments:metrics';
- wrapper.vm.$nextTick(done);
+ nextTick(done);
});
afterEach(() => {
document.body.dataset.page = origPage;
});
- it('is tracked', (done) => {
+ it('is tracked', async () => {
const submitButton = findAddMetricModalSubmitButton().vm;
- wrapper.vm.$nextTick(() => {
- submitButton.$el.click();
- wrapper.vm.$nextTick(() => {
- expect(Tracking.event).toHaveBeenCalledWith(
- document.body.dataset.page,
- 'click_button',
- {
- label: 'add_new_metric',
- property: 'modal',
- value: undefined,
- },
- );
- done();
- });
+ await nextTick();
+ submitButton.$el.click();
+ await nextTick();
+ expect(Tracking.event).toHaveBeenCalledWith(document.body.dataset.page, 'click_button', {
+ label: 'add_new_metric',
+ property: 'modal',
+ value: undefined,
});
});
});
@@ -172,14 +164,13 @@ describe('Actions menu', () => {
);
});
- it('is disabled for ootb dashboards', () => {
+ it('is disabled for ootb dashboards', async () => {
createShallowWrapper({
isOotbDashboard: true,
});
- return wrapper.vm.$nextTick(() => {
- expect(findAddPanelItemDisabled().exists()).toBe(true);
- });
+ await nextTick();
+ expect(findAddPanelItemDisabled().exists()).toBe(true);
});
it('is visible for custom dashboards', () => {
@@ -256,16 +247,15 @@ describe('Actions menu', () => {
expect(findDuplicateDashboardModal().exists()).toBe(true);
});
- it('clicking on item opens up the duplicate dashboard modal', () => {
+ it('clicking on item opens up the duplicate dashboard modal', async () => {
const modalId = 'duplicateDashboard';
const modalTrigger = findDuplicateDashboardItem();
const rootEmit = jest.spyOn(wrapper.vm.$root, '$emit');
modalTrigger.trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(rootEmit.mock.calls[0]).toContainEqual(modalId);
- });
+ await nextTick();
+ expect(rootEmit.mock.calls[0]).toContainEqual(modalId);
});
});
@@ -300,16 +290,15 @@ describe('Actions menu', () => {
setupAllDashboards(store, dashboardGitResponse[0].path);
});
- it('redirects to the newly created dashboard', () => {
+ it('redirects to the newly created dashboard', async () => {
const newDashboard = dashboardGitResponse[1];
const newDashboardUrl = 'root/sandbox/-/metrics/dashboard.yml';
findDuplicateDashboardModal().vm.$emit('dashboardDuplicated', newDashboard);
- return wrapper.vm.$nextTick().then(() => {
- expect(redirectTo).toHaveBeenCalled();
- expect(redirectTo).toHaveBeenCalledWith(newDashboardUrl);
- });
+ await nextTick();
+ expect(redirectTo).toHaveBeenCalled();
+ expect(redirectTo).toHaveBeenCalledWith(newDashboardUrl);
});
});
});
@@ -330,32 +319,30 @@ describe('Actions menu', () => {
expect(findStarDashboardItem().attributes('disabled')).toBeFalsy();
});
- it('is disabled when starring is taking place', () => {
+ it('is disabled when starring is taking place', async () => {
store.commit(`monitoringDashboard/${types.REQUEST_DASHBOARD_STARRING}`);
- return wrapper.vm.$nextTick(() => {
- expect(findStarDashboardItem().exists()).toBe(true);
- expect(findStarDashboardItem().attributes('disabled')).toBe('true');
- });
+ await nextTick();
+ expect(findStarDashboardItem().exists()).toBe(true);
+ expect(findStarDashboardItem().attributes('disabled')).toBe('true');
});
- it('on click it dispatches a toggle star action', () => {
+ it('on click it dispatches a toggle star action', async () => {
findStarDashboardItem().vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(store.dispatch).toHaveBeenCalledWith(
- 'monitoringDashboard/toggleStarredValue',
- undefined,
- );
- });
+ await nextTick();
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'monitoringDashboard/toggleStarredValue',
+ undefined,
+ );
});
describe('when dashboard is not starred', () => {
- beforeEach(() => {
+ beforeEach(async () => {
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
currentDashboard: dashboardGitResponse[0].path,
});
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('item text shows "Star dashboard"', () => {
@@ -364,11 +351,11 @@ describe('Actions menu', () => {
});
describe('when dashboard is starred', () => {
- beforeEach(() => {
+ beforeEach(async () => {
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
currentDashboard: dashboardGitResponse[1].path,
});
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('item text shows "Unstar dashboard"', () => {
@@ -403,16 +390,15 @@ describe('Actions menu', () => {
expect(findCreateDashboardModal().exists()).toBe(true);
});
- it('clicking opens up the modal', () => {
+ it('clicking opens up the modal', async () => {
const modalId = 'createDashboard';
const modalTrigger = findCreateDashboardItem();
const rootEmit = jest.spyOn(wrapper.vm.$root, '$emit');
modalTrigger.trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(rootEmit.mock.calls[0]).toContainEqual(modalId);
- });
+ await nextTick();
+ expect(rootEmit.mock.calls[0]).toContainEqual(modalId);
});
it('modal gets passed correct props', () => {
diff --git a/spec/frontend/monitoring/components/dashboard_header_spec.js b/spec/frontend/monitoring/components/dashboard_header_spec.js
index 8be7d641953..e28c2913949 100644
--- a/spec/frontend/monitoring/components/dashboard_header_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_header_spec.js
@@ -1,5 +1,6 @@
import { GlDropdownItem, GlSearchBoxByType, GlLoadingIcon, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { redirectTo } from '~/lib/utils/url_utility';
import ActionsMenu from '~/monitoring/components/dashboard_actions_menu.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
@@ -110,9 +111,9 @@ describe('Dashboard header', () => {
});
describe('when environments data is not loaded', () => {
- beforeEach(() => {
+ beforeEach(async () => {
setupStoreWithDashboard(store);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('there are no environments listed', () => {
@@ -124,13 +125,13 @@ describe('Dashboard header', () => {
const currentDashboard = dashboardGitResponse[0].path;
const currentEnvironmentName = environmentData[0].name;
- beforeEach(() => {
+ beforeEach(async () => {
setupStoreWithData(store);
store.state.monitoringDashboard.projectPath = mockProjectPath;
store.state.monitoringDashboard.currentDashboard = currentDashboard;
store.state.monitoringDashboard.currentEnvironmentName = currentEnvironmentName;
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders dropdown items with the environment name', () => {
@@ -159,51 +160,41 @@ describe('Dashboard header', () => {
expect(selectedItems.at(0).text()).toBe(currentEnvironmentName);
});
- it('filters rendered dropdown items', () => {
+ it('filters rendered dropdown items', async () => {
const searchTerm = 'production';
const resultEnvs = environmentData.filter(({ name }) => name.indexOf(searchTerm) !== -1);
setSearchTerm(searchTerm);
- return wrapper.vm.$nextTick().then(() => {
- expect(findEnvsDropdownItems()).toHaveLength(resultEnvs.length);
- });
+ await nextTick();
+ expect(findEnvsDropdownItems()).toHaveLength(resultEnvs.length);
});
- it('does not filter dropdown items if search term is empty string', () => {
+ it('does not filter dropdown items if search term is empty string', async () => {
const searchTerm = '';
setSearchTerm(searchTerm);
- return wrapper.vm.$nextTick(() => {
- expect(findEnvsDropdownItems()).toHaveLength(environmentData.length);
- });
+ await nextTick();
+ expect(findEnvsDropdownItems()).toHaveLength(environmentData.length);
});
- it("shows error message if search term doesn't match", () => {
+ it("shows error message if search term doesn't match", async () => {
const searchTerm = 'does-not-exist';
setSearchTerm(searchTerm);
- return wrapper.vm.$nextTick(() => {
- expect(findEnvsDropdownSearchMsg().isVisible()).toBe(true);
- });
+ await nextTick();
+ expect(findEnvsDropdownSearchMsg().isVisible()).toBe(true);
});
- it('shows loading element when environments fetch is still loading', () => {
+ it('shows loading element when environments fetch is still loading', async () => {
store.commit(`monitoringDashboard/${types.REQUEST_ENVIRONMENTS_DATA}`);
- return wrapper.vm
- .$nextTick()
- .then(() => {
- expect(findEnvsDropdownLoadingIcon().exists()).toBe(true);
- })
- .then(() => {
- store.commit(
- `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
- environmentData,
- );
- })
- .then(() => {
- expect(findEnvsDropdownLoadingIcon().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findEnvsDropdownLoadingIcon().exists()).toBe(true);
+ await store.commit(
+ `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
+ environmentData,
+ );
+ expect(findEnvsDropdownLoadingIcon().exists()).toBe(false);
});
});
});
@@ -262,11 +253,11 @@ describe('Dashboard header', () => {
});
describe('external dashboard link', () => {
- beforeEach(() => {
+ beforeEach(async () => {
store.state.monitoringDashboard.externalDashboardUrl = '/mockUrl';
createShallowWrapper();
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('shows the link', () => {
@@ -295,82 +286,78 @@ describe('Dashboard header', () => {
});
describe('adding metrics prop', () => {
- it.each(ootbDashboards)('gets passed true if current dashboard is OOTB', (dashboardPath) => {
- createShallowWrapper({ customMetricsAvailable: true });
+ it.each(ootbDashboards)(
+ 'gets passed true if current dashboard is OOTB',
+ async (dashboardPath) => {
+ createShallowWrapper({ customMetricsAvailable: true });
- store.state.monitoringDashboard.emptyState = false;
- setupAllDashboards(store, dashboardPath);
+ store.state.monitoringDashboard.emptyState = false;
+ setupAllDashboards(store, dashboardPath);
- return wrapper.vm.$nextTick().then(() => {
+ await nextTick();
expect(findActionsMenu().props('addingMetricsAvailable')).toBe(true);
- });
- });
+ },
+ );
it.each(customDashboards)(
'gets passed false if current dashboard is custom',
- (dashboardPath) => {
+ async (dashboardPath) => {
createShallowWrapper({ customMetricsAvailable: true });
store.state.monitoringDashboard.emptyState = false;
setupAllDashboards(store, dashboardPath);
- return wrapper.vm.$nextTick().then(() => {
- expect(findActionsMenu().props('addingMetricsAvailable')).toBe(false);
- });
+ await nextTick();
+ expect(findActionsMenu().props('addingMetricsAvailable')).toBe(false);
},
);
- it('gets passed false if empty state is shown', () => {
+ it('gets passed false if empty state is shown', async () => {
createShallowWrapper({ customMetricsAvailable: true });
store.state.monitoringDashboard.emptyState = true;
setupAllDashboards(store, ootbDashboards[0]);
- return wrapper.vm.$nextTick().then(() => {
- expect(findActionsMenu().props('addingMetricsAvailable')).toBe(false);
- });
+ await nextTick();
+ expect(findActionsMenu().props('addingMetricsAvailable')).toBe(false);
});
- it('gets passed false if custom metrics are not available', () => {
+ it('gets passed false if custom metrics are not available', async () => {
createShallowWrapper({ customMetricsAvailable: false });
store.state.monitoringDashboard.emptyState = false;
setupAllDashboards(store, ootbDashboards[0]);
- return wrapper.vm.$nextTick().then(() => {
- expect(findActionsMenu().props('addingMetricsAvailable')).toBe(false);
- });
+ await nextTick();
+ expect(findActionsMenu().props('addingMetricsAvailable')).toBe(false);
});
});
- it('custom metrics path gets passed', () => {
+ it('custom metrics path gets passed', async () => {
const path = 'https://path/to/customMetrics';
createShallowWrapper({ customMetricsPath: path });
- return wrapper.vm.$nextTick().then(() => {
- expect(findActionsMenu().props('customMetricsPath')).toBe(path);
- });
+ await nextTick();
+ expect(findActionsMenu().props('customMetricsPath')).toBe(path);
});
- it('validate query path gets passed', () => {
+ it('validate query path gets passed', async () => {
const path = 'https://path/to/validateQuery';
createShallowWrapper({ validateQueryPath: path });
- return wrapper.vm.$nextTick().then(() => {
- expect(findActionsMenu().props('validateQueryPath')).toBe(path);
- });
+ await nextTick();
+ expect(findActionsMenu().props('validateQueryPath')).toBe(path);
});
- it('default branch gets passed', () => {
+ it('default branch gets passed', async () => {
const branch = 'branchName';
createShallowWrapper({ defaultBranch: branch });
- return wrapper.vm.$nextTick().then(() => {
- expect(findActionsMenu().props('defaultBranch')).toBe(branch);
- });
+ await nextTick();
+ expect(findActionsMenu().props('defaultBranch')).toBe(branch);
});
});
@@ -385,40 +372,36 @@ describe('Dashboard header', () => {
store.state.monitoringDashboard.operationsSettingsPath = '';
});
- it('is rendered when the user can access the project settings and path to settings is available', () => {
+ it('is rendered when the user can access the project settings and path to settings is available', async () => {
store.state.monitoringDashboard.canAccessOperationsSettings = true;
store.state.monitoringDashboard.operationsSettingsPath = url;
- return wrapper.vm.$nextTick(() => {
- expect(findSettingsButton().exists()).toBe(true);
- });
+ await nextTick();
+ expect(findSettingsButton().exists()).toBe(true);
});
- it('is not rendered when the user can not access the project settings', () => {
+ it('is not rendered when the user can not access the project settings', async () => {
store.state.monitoringDashboard.canAccessOperationsSettings = false;
store.state.monitoringDashboard.operationsSettingsPath = url;
- return wrapper.vm.$nextTick(() => {
- expect(findSettingsButton().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findSettingsButton().exists()).toBe(false);
});
- it('is not rendered when the path to settings is unavailable', () => {
+ it('is not rendered when the path to settings is unavailable', async () => {
store.state.monitoringDashboard.canAccessOperationsSettings = false;
store.state.monitoringDashboard.operationsSettingsPath = '';
- return wrapper.vm.$nextTick(() => {
- expect(findSettingsButton().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findSettingsButton().exists()).toBe(false);
});
- it('leads to the project settings page', () => {
+ it('leads to the project settings page', async () => {
store.state.monitoringDashboard.canAccessOperationsSettings = true;
store.state.monitoringDashboard.operationsSettingsPath = url;
- return wrapper.vm.$nextTick(() => {
- expect(findSettingsButton().attributes('href')).toBe(url);
- });
+ await nextTick();
+ expect(findSettingsButton().attributes('href')).toBe(url);
});
});
});
diff --git a/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js b/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js
index 400ac2e8f85..f19ef6c6fb7 100644
--- a/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js
@@ -1,5 +1,6 @@
import { GlCard, GlForm, GlFormTextarea, GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
import DashboardPanelBuilder from '~/monitoring/components/dashboard_panel_builder.vue';
import { createStore } from '~/monitoring/stores';
@@ -90,21 +91,20 @@ describe('dashboard invalid url parameters', () => {
expect(mockShowToast).toHaveBeenCalledTimes(1);
});
- it('on submit fetches a panel preview', () => {
+ it('on submit fetches a panel preview', async () => {
findForm().vm.$emit('submit', new Event('submit'));
- return wrapper.vm.$nextTick().then(() => {
- expect(store.dispatch).toHaveBeenCalledWith(
- 'monitoringDashboard/fetchPanelPreview',
- expect.stringContaining('title:'),
- );
- });
+ await nextTick();
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'monitoringDashboard/fetchPanelPreview',
+ expect.stringContaining('title:'),
+ );
});
describe('when form is submitted', () => {
- beforeEach(() => {
+ beforeEach(async () => {
store.commit(`monitoringDashboard/${types.REQUEST_PANEL_PREVIEW}`, 'mock yml content');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('submit button is disabled', () => {
@@ -118,23 +118,21 @@ describe('dashboard invalid url parameters', () => {
expect(findTimeRangePicker().exists()).toBe(true);
});
- it('when changed does not trigger data fetch unless preview panel button is clicked', () => {
+ it('when changed does not trigger data fetch unless preview panel button is clicked', async () => {
// mimic initial state where SET_PANEL_PREVIEW_IS_SHOWN is set to false
store.commit(`monitoringDashboard/${types.SET_PANEL_PREVIEW_IS_SHOWN}`, false);
- return wrapper.vm.$nextTick(() => {
- expect(store.dispatch).not.toHaveBeenCalled();
- });
+ await nextTick();
+ expect(store.dispatch).not.toHaveBeenCalled();
});
- it('when changed triggers data fetch if preview panel button is clicked', () => {
+ it('when changed triggers data fetch if preview panel button is clicked', async () => {
findForm().vm.$emit('submit', new Event('submit'));
store.commit(`monitoringDashboard/${types.SET_PANEL_PREVIEW_TIME_RANGE}`, mockTimeRange);
- return wrapper.vm.$nextTick(() => {
- expect(store.dispatch).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(store.dispatch).toHaveBeenCalled();
});
});
@@ -143,27 +141,25 @@ describe('dashboard invalid url parameters', () => {
expect(findRefreshButton().exists()).toBe(true);
});
- it('when clicked does not trigger data fetch unless preview panel button is clicked', () => {
+ it('when clicked does not trigger data fetch unless preview panel button is clicked', async () => {
// mimic initial state where SET_PANEL_PREVIEW_IS_SHOWN is set to false
store.commit(`monitoringDashboard/${types.SET_PANEL_PREVIEW_IS_SHOWN}`, false);
- return wrapper.vm.$nextTick(() => {
- expect(store.dispatch).not.toHaveBeenCalled();
- });
+ await nextTick();
+ expect(store.dispatch).not.toHaveBeenCalled();
});
- it('when clicked triggers data fetch if preview panel button is clicked', () => {
+ it('when clicked triggers data fetch if preview panel button is clicked', async () => {
// mimic state where preview is visible. SET_PANEL_PREVIEW_IS_SHOWN is set to true
store.commit(`monitoringDashboard/${types.SET_PANEL_PREVIEW_IS_SHOWN}`, true);
findRefreshButton().vm.$emit('click');
- return wrapper.vm.$nextTick(() => {
- expect(store.dispatch).toHaveBeenCalledWith(
- 'monitoringDashboard/fetchPanelPreviewMetrics',
- undefined,
- );
- });
+ await nextTick();
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'monitoringDashboard/fetchPanelPreviewMetrics',
+ undefined,
+ );
});
});
@@ -190,9 +186,9 @@ describe('dashboard invalid url parameters', () => {
describe('when there is an error', () => {
const mockError = 'an error occurred!';
- beforeEach(() => {
+ beforeEach(async () => {
store.commit(`monitoringDashboard/${types.RECEIVE_PANEL_PREVIEW_FAILURE}`, mockError);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays an alert', () => {
@@ -204,19 +200,18 @@ describe('dashboard invalid url parameters', () => {
expect(findPanel().props('graphData')).toBe(null);
});
- it('changing time range should not refetch data', () => {
+ it('changing time range should not refetch data', async () => {
store.commit(`monitoringDashboard/${types.SET_PANEL_PREVIEW_TIME_RANGE}`, mockTimeRange);
- return wrapper.vm.$nextTick(() => {
- expect(store.dispatch).not.toHaveBeenCalled();
- });
+ await nextTick();
+ expect(store.dispatch).not.toHaveBeenCalled();
});
});
describe('when panel data is available', () => {
- beforeEach(() => {
+ beforeEach(async () => {
store.commit(`monitoringDashboard/${types.RECEIVE_PANEL_PREVIEW_SUCCESS}`, mockPanel);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays no alert', () => {
diff --git a/spec/frontend/monitoring/components/dashboard_panel_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js
index 9a73dc820af..7bd062b81f1 100644
--- a/spec/frontend/monitoring/components/dashboard_panel_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js
@@ -2,6 +2,7 @@ import { GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import Vuex from 'vuex';
+import { nextTick } from 'vue';
import { setTestTimeout } from 'helpers/timeout';
import axios from '~/lib/utils/axios_utils';
import invalidUrl from '~/lib/utils/invalid_url';
@@ -186,7 +187,7 @@ describe('Dashboard Panel', () => {
expect(findCopyLink().exists()).toBe(false);
});
- it('should emit `timerange` event when a zooming in/out in a chart occcurs', () => {
+ it('should emit `timerange` event when a zooming in/out in a chart occcurs', async () => {
const timeRange = {
start: '2020-01-01T00:00:00.000Z',
end: '2020-01-01T01:00:00.000Z',
@@ -196,9 +197,8 @@ describe('Dashboard Panel', () => {
findTimeChart().vm.$emit('datazoom', timeRange);
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('timerangezoom', timeRange);
- });
+ await nextTick();
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('timerangezoom', timeRange);
});
it('includes a default group id', () => {
@@ -253,16 +253,15 @@ describe('Dashboard Panel', () => {
describe('computed', () => {
describe('fixedCurrentTimeRange', () => {
- it('returns fixed time for valid time range', () => {
+ it('returns fixed time for valid time range', async () => {
state.timeRange = mockTimeRange;
- return wrapper.vm.$nextTick(() => {
- expect(findTimeChart().props('timeRange')).toEqual(
- expect.objectContaining({
- start: expect.any(String),
- end: expect.any(String),
- }),
- );
- });
+ await nextTick();
+ expect(findTimeChart().props('timeRange')).toEqual(
+ expect.objectContaining({
+ start: expect.any(String),
+ end: expect.any(String),
+ }),
+ );
});
it.each`
@@ -271,11 +270,10 @@ describe('Dashboard Panel', () => {
${undefined} | ${{}}
${null} | ${{}}
${'2020-12-03'} | ${{}}
- `('returns $output for invalid input like $input', ({ input, output }) => {
+ `('returns $output for invalid input like $input', async ({ input, output }) => {
state.timeRange = input;
- return wrapper.vm.$nextTick(() => {
- expect(findTimeChart().props('timeRange')).toEqual(output);
- });
+ await nextTick();
+ expect(findTimeChart().props('timeRange')).toEqual(output);
});
});
});
@@ -285,17 +283,16 @@ describe('Dashboard Panel', () => {
const findEditCustomMetricLink = () => wrapper.find({ ref: 'editMetricLink' });
const mockEditPath = '/root/kubernetes-gke-project/prometheus/metrics/23/edit';
- beforeEach(() => {
+ beforeEach(async () => {
createWrapper();
-
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('is not present if the panel is not a custom metric', () => {
expect(findEditCustomMetricLink().exists()).toBe(false);
});
- it('is present when the panel contains an edit_path property', () => {
+ it('is present when the panel contains an edit_path property', async () => {
wrapper.setProps({
graphData: {
...graphData,
@@ -308,14 +305,13 @@ describe('Dashboard Panel', () => {
},
});
- return wrapper.vm.$nextTick(() => {
- expect(findEditCustomMetricLink().exists()).toBe(true);
- expect(findEditCustomMetricLink().text()).toBe('Edit metric');
- expect(findEditCustomMetricLink().attributes('href')).toBe(mockEditPath);
- });
+ await nextTick();
+ expect(findEditCustomMetricLink().exists()).toBe(true);
+ expect(findEditCustomMetricLink().text()).toBe('Edit metric');
+ expect(findEditCustomMetricLink().attributes('href')).toBe(mockEditPath);
});
- it('shows an "Edit metrics" link pointing to settingsPath for a panel with multiple metrics', () => {
+ it('shows an "Edit metrics" link pointing to settingsPath for a panel with multiple metrics', async () => {
wrapper.setProps({
graphData: {
...graphData,
@@ -332,63 +328,58 @@ describe('Dashboard Panel', () => {
},
});
- return wrapper.vm.$nextTick(() => {
- expect(findEditCustomMetricLink().text()).toBe('Edit metrics');
- expect(findEditCustomMetricLink().attributes('href')).toBe(dashboardProps.settingsPath);
- });
+ await nextTick();
+ expect(findEditCustomMetricLink().text()).toBe('Edit metrics');
+ expect(findEditCustomMetricLink().attributes('href')).toBe(dashboardProps.settingsPath);
});
});
describe('View Logs dropdown item', () => {
const findViewLogsLink = () => wrapper.find({ ref: 'viewLogsLink' });
- beforeEach(() => {
+ beforeEach(async () => {
createWrapper();
- return wrapper.vm.$nextTick();
+ await nextTick();
});
- it('is not present by default', () =>
- wrapper.vm.$nextTick(() => {
- expect(findViewLogsLink().exists()).toBe(false);
- }));
+ it('is not present by default', async () => {
+ await nextTick();
+ expect(findViewLogsLink().exists()).toBe(false);
+ });
- it('is not present if a time range is not set', () => {
+ it('is not present if a time range is not set', async () => {
state.logsPath = mockLogsPath;
state.timeRange = null;
- return wrapper.vm.$nextTick(() => {
- expect(findViewLogsLink().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findViewLogsLink().exists()).toBe(false);
});
- it('is not present if the logs path is default', () => {
+ it('is not present if the logs path is default', async () => {
state.logsPath = invalidUrl;
state.timeRange = mockTimeRange;
- return wrapper.vm.$nextTick(() => {
- expect(findViewLogsLink().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findViewLogsLink().exists()).toBe(false);
});
- it('is not present if the logs path is not set', () => {
+ it('is not present if the logs path is not set', async () => {
state.logsPath = null;
state.timeRange = mockTimeRange;
- return wrapper.vm.$nextTick(() => {
- expect(findViewLogsLink().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findViewLogsLink().exists()).toBe(false);
});
- it('is present when logs path and time a range is present', () => {
+ it('is present when logs path and time a range is present', async () => {
state.logsPath = mockLogsPath;
state.timeRange = mockTimeRange;
- return wrapper.vm.$nextTick(() => {
- expect(findViewLogsLink().attributes('href')).toMatch(mockLogsHref);
- });
+ await nextTick();
+ expect(findViewLogsLink().attributes('href')).toMatch(mockLogsHref);
});
- it('it is overridden when a datazoom event is received', () => {
+ it('it is overridden when a datazoom event is received', async () => {
state.logsPath = mockLogsPath;
state.timeRange = mockTimeRange;
@@ -399,13 +390,12 @@ describe('Dashboard Panel', () => {
findTimeChart().vm.$emit('datazoom', zoomedTimeRange);
- return wrapper.vm.$nextTick(() => {
- const start = encodeURIComponent(zoomedTimeRange.start);
- const end = encodeURIComponent(zoomedTimeRange.end);
- expect(findViewLogsLink().attributes('href')).toMatch(
- `${mockLogsPath}?start=${start}&end=${end}`,
- );
- });
+ await nextTick();
+ const start = encodeURIComponent(zoomedTimeRange.start);
+ const end = encodeURIComponent(zoomedTimeRange.end);
+ expect(findViewLogsLink().attributes('href')).toMatch(
+ `${mockLogsPath}?start=${start}&end=${end}`,
+ );
});
});
@@ -447,7 +437,7 @@ describe('Dashboard Panel', () => {
});
describe('when downloading metrics data as CSV', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
clipboardText: exampleText,
@@ -459,7 +449,7 @@ describe('Dashboard Panel', () => {
},
store,
});
- return wrapper.vm.$nextTick();
+ await nextTick();
});
afterEach(() => {
@@ -509,29 +499,26 @@ describe('Dashboard Panel', () => {
});
});
- it('handles namespaced time range and logs path state', () => {
+ it('handles namespaced time range and logs path state', async () => {
store.state[mockNamespace].timeRange = mockTimeRange;
store.state[mockNamespace].logsPath = mockLogsPath;
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find({ ref: 'viewLogsLink' }).attributes().href).toBe(mockLogsHref);
- });
+ await nextTick();
+ expect(wrapper.find({ ref: 'viewLogsLink' }).attributes().href).toBe(mockLogsHref);
});
- it('handles namespaced deployment data state', () => {
+ it('handles namespaced deployment data state', async () => {
store.state[mockNamespace].deploymentData = mockDeploymentData;
- return wrapper.vm.$nextTick().then(() => {
- expect(findTimeChart().props().deploymentData).toEqual(mockDeploymentData);
- });
+ await nextTick();
+ expect(findTimeChart().props().deploymentData).toEqual(mockDeploymentData);
});
- it('handles namespaced project path state', () => {
+ it('handles namespaced project path state', async () => {
store.state[mockNamespace].projectPath = mockProjectPath;
- return wrapper.vm.$nextTick().then(() => {
- expect(findTimeChart().props().projectPath).toBe(mockProjectPath);
- });
+ await nextTick();
+ expect(findTimeChart().props().projectPath).toBe(mockProjectPath);
});
it('it renders a time series chart with no errors', () => {
diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js
index 7730e7f347f..6c5972e1140 100644
--- a/spec/frontend/monitoring/components/dashboard_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_spec.js
@@ -1,5 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import VueDraggable from 'vuedraggable';
+import { nextTick } from 'vue';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -77,51 +78,47 @@ describe('Dashboard', () => {
});
describe('request information to the server', () => {
- it('calls to set time range and fetch data', () => {
+ it('calls to set time range and fetch data', async () => {
createShallowWrapper({ hasMetrics: true });
- return wrapper.vm.$nextTick().then(() => {
- expect(store.dispatch).toHaveBeenCalledWith(
- 'monitoringDashboard/setTimeRange',
- expect.any(Object),
- );
+ await nextTick();
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'monitoringDashboard/setTimeRange',
+ expect.any(Object),
+ );
- expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
- });
+ expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
});
- it('shows up a loading state', () => {
+ it('shows up a loading state', async () => {
store.state.monitoringDashboard.emptyState = dashboardEmptyStates.LOADING;
createShallowWrapper({ hasMetrics: true });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(EmptyState).exists()).toBe(true);
- expect(wrapper.find(EmptyState).props('selectedState')).toBe(dashboardEmptyStates.LOADING);
- });
+ await nextTick();
+ expect(wrapper.find(EmptyState).exists()).toBe(true);
+ expect(wrapper.find(EmptyState).props('selectedState')).toBe(dashboardEmptyStates.LOADING);
});
- it('hides the group panels when showPanels is false', () => {
+ it('hides the group panels when showPanels is false', async () => {
createMountedWrapper({ hasMetrics: true, showPanels: false });
setupStoreWithData(store);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.emptyState).toBeNull();
- expect(wrapper.findAll('.prometheus-panel')).toHaveLength(0);
- });
+ await nextTick();
+ expect(wrapper.vm.emptyState).toBeNull();
+ expect(wrapper.findAll('.prometheus-panel')).toHaveLength(0);
});
- it('fetches the metrics data with proper time window', () => {
+ it('fetches the metrics data with proper time window', async () => {
createMountedWrapper({ hasMetrics: true });
- return wrapper.vm.$nextTick().then(() => {
- expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
- expect(store.dispatch).toHaveBeenCalledWith(
- 'monitoringDashboard/setTimeRange',
- expect.objectContaining({ duration: { seconds: 28800 } }),
- );
- });
+ await nextTick();
+ expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'monitoringDashboard/setTimeRange',
+ expect.objectContaining({ duration: { seconds: 28800 } }),
+ );
});
});
@@ -133,69 +130,63 @@ describe('Dashboard', () => {
.at(index);
};
- beforeEach(() => {
+ beforeEach(async () => {
createMountedWrapper({ hasMetrics: true });
-
- return wrapper.vm.$nextTick();
+ await nextTick();
});
describe('when the graph group has an even number of panels', () => {
- it('2 panels - all panel wrappers take half width of their parent', () => {
+ it('2 panels - all panel wrappers take half width of their parent', async () => {
setupStoreWithDataForPanelCount(store, 2);
- wrapper.vm.$nextTick(() => {
- expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(true);
- expect(findPanelLayoutWrapperAt(1).classes('col-lg-6')).toBe(true);
- });
+ await nextTick();
+ expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(1).classes('col-lg-6')).toBe(true);
});
- it('4 panels - all panel wrappers take half width of their parent', () => {
+ it('4 panels - all panel wrappers take half width of their parent', async () => {
setupStoreWithDataForPanelCount(store, 4);
- wrapper.vm.$nextTick(() => {
- expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(true);
- expect(findPanelLayoutWrapperAt(1).classes('col-lg-6')).toBe(true);
- expect(findPanelLayoutWrapperAt(2).classes('col-lg-6')).toBe(true);
- expect(findPanelLayoutWrapperAt(3).classes('col-lg-6')).toBe(true);
- });
+ await nextTick();
+ expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(1).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(2).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(3).classes('col-lg-6')).toBe(true);
});
});
describe('when the graph group has an odd number of panels', () => {
- it('1 panel - panel wrapper does not take half width of its parent', () => {
+ it('1 panel - panel wrapper does not take half width of its parent', async () => {
setupStoreWithDataForPanelCount(store, 1);
- wrapper.vm.$nextTick(() => {
- expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(false);
- });
+ await nextTick();
+ expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(false);
});
- it('3 panels - all panels but last take half width of their parents', () => {
+ it('3 panels - all panels but last take half width of their parents', async () => {
setupStoreWithDataForPanelCount(store, 3);
- wrapper.vm.$nextTick(() => {
- expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(true);
- expect(findPanelLayoutWrapperAt(1).classes('col-lg-6')).toBe(true);
- expect(findPanelLayoutWrapperAt(2).classes('col-lg-6')).toBe(false);
- });
+ await nextTick();
+ expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(1).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(2).classes('col-lg-6')).toBe(false);
});
- it('5 panels - all panels but last take half width of their parents', () => {
+ it('5 panels - all panels but last take half width of their parents', async () => {
setupStoreWithDataForPanelCount(store, 5);
- wrapper.vm.$nextTick(() => {
- expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(true);
- expect(findPanelLayoutWrapperAt(1).classes('col-lg-6')).toBe(true);
- expect(findPanelLayoutWrapperAt(2).classes('col-lg-6')).toBe(true);
- expect(findPanelLayoutWrapperAt(3).classes('col-lg-6')).toBe(true);
- expect(findPanelLayoutWrapperAt(4).classes('col-lg-6')).toBe(false);
- });
+ await nextTick();
+ expect(findPanelLayoutWrapperAt(0).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(1).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(2).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(3).classes('col-lg-6')).toBe(true);
+ expect(findPanelLayoutWrapperAt(4).classes('col-lg-6')).toBe(false);
});
});
});
describe('dashboard validation warning', () => {
- it('displays a warning if there are validation warnings', () => {
+ it('displays a warning if there are validation warnings', async () => {
createMountedWrapper({ hasMetrics: true });
store.commit(
@@ -203,12 +194,11 @@ describe('Dashboard', () => {
true,
);
- return wrapper.vm.$nextTick().then(() => {
- expect(createFlash).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(createFlash).toHaveBeenCalled();
});
- it('does not display a warning if there are no validation warnings', () => {
+ it('does not display a warning if there are no validation warnings', async () => {
createMountedWrapper({ hasMetrics: true });
store.commit(
@@ -216,9 +206,8 @@ describe('Dashboard', () => {
false,
);
- return wrapper.vm.$nextTick().then(() => {
- expect(createFlash).not.toHaveBeenCalled();
- });
+ await nextTick();
+ expect(createFlash).not.toHaveBeenCalled();
});
});
@@ -233,7 +222,7 @@ describe('Dashboard', () => {
setWindowLocation(location);
});
- it('when the URL points to a panel it expands', () => {
+ it('when the URL points to a panel it expands', async () => {
const panelGroup = metricsDashboardViewModel.panelGroups[0];
const panel = panelGroup.panels[0];
@@ -246,32 +235,30 @@ describe('Dashboard', () => {
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(store);
- return wrapper.vm.$nextTick().then(() => {
- expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setExpandedPanel', {
- group: panelGroup.group,
- panel: expect.objectContaining({
- title: panel.title,
- y_label: panel.y_label,
- }),
- });
+ await nextTick();
+ expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setExpandedPanel', {
+ group: panelGroup.group,
+ panel: expect.objectContaining({
+ title: panel.title,
+ y_label: panel.y_label,
+ }),
});
});
- it('when the URL does not link to any panel, no panel is expanded', () => {
+ it('when the URL does not link to any panel, no panel is expanded', async () => {
setSearch();
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(store);
- return wrapper.vm.$nextTick().then(() => {
- expect(store.dispatch).not.toHaveBeenCalledWith(
- 'monitoringDashboard/setExpandedPanel',
- expect.anything(),
- );
- });
+ await nextTick();
+ expect(store.dispatch).not.toHaveBeenCalledWith(
+ 'monitoringDashboard/setExpandedPanel',
+ expect.anything(),
+ );
});
- it('when the URL points to an incorrect panel it shows an error', () => {
+ it('when the URL points to an incorrect panel it shows an error', async () => {
const panelGroup = metricsDashboardViewModel.panelGroups[0];
const panel = panelGroup.panels[0];
@@ -284,13 +271,12 @@ describe('Dashboard', () => {
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(store);
- return wrapper.vm.$nextTick().then(() => {
- expect(createFlash).toHaveBeenCalled();
- expect(store.dispatch).not.toHaveBeenCalledWith(
- 'monitoringDashboard/setExpandedPanel',
- expect.anything(),
- );
- });
+ await nextTick();
+ expect(createFlash).toHaveBeenCalled();
+ expect(store.dispatch).not.toHaveBeenCalledWith(
+ 'monitoringDashboard/setExpandedPanel',
+ expect.anything(),
+ );
});
});
@@ -319,7 +305,7 @@ describe('Dashboard', () => {
window.history.pushState.mockRestore();
});
- it('URL is updated with panel parameters', () => {
+ it('URL is updated with panel parameters', async () => {
createMountedWrapper({ hasMetrics: true });
expandPanel(group, panel);
@@ -329,17 +315,16 @@ describe('Dashboard', () => {
y_label: panel.y_label,
});
- return wrapper.vm.$nextTick(() => {
- expect(window.history.pushState).toHaveBeenCalledTimes(1);
- expect(window.history.pushState).toHaveBeenCalledWith(
- expect.anything(), // state
- expect.any(String), // document title
- expect.stringContaining(`${expectedSearch}`),
- );
- });
+ await nextTick();
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ expect.anything(), // state
+ expect.any(String), // document title
+ expect.stringContaining(`${expectedSearch}`),
+ );
});
- it('URL is updated with panel parameters and custom dashboard', () => {
+ it('URL is updated with panel parameters and custom dashboard', async () => {
const dashboard = 'dashboard.yml';
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
@@ -355,36 +340,34 @@ describe('Dashboard', () => {
y_label: panel.y_label,
});
- return wrapper.vm.$nextTick(() => {
- expect(window.history.pushState).toHaveBeenCalledTimes(1);
- expect(window.history.pushState).toHaveBeenCalledWith(
- expect.anything(), // state
- expect.any(String), // document title
- expect.stringContaining(`${expectedSearch}`),
- );
- });
+ await nextTick();
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ expect.anything(), // state
+ expect.any(String), // document title
+ expect.stringContaining(`${expectedSearch}`),
+ );
});
- it('URL is updated with no parameters', () => {
+ it('URL is updated with no parameters', async () => {
expandPanel(group, panel);
createMountedWrapper({ hasMetrics: true });
expandPanel(null, null);
- return wrapper.vm.$nextTick(() => {
- expect(window.history.pushState).toHaveBeenCalledTimes(1);
- expect(window.history.pushState).toHaveBeenCalledWith(
- expect.anything(), // state
- expect.any(String), // document title
- expect.not.stringMatching(/group|title|y_label/), // no panel params
- );
- });
+ await nextTick();
+ expect(window.history.pushState).toHaveBeenCalledTimes(1);
+ expect(window.history.pushState).toHaveBeenCalledWith(
+ expect.anything(), // state
+ expect.any(String), // document title
+ expect.not.stringMatching(/group|title|y_label/), // no panel params
+ );
});
});
describe('when all panels in the first group are loading', () => {
const findGroupAt = (i) => wrapper.findAll(GraphGroup).at(i);
- beforeEach(() => {
+ beforeEach(async () => {
setupStoreWithDashboard(store);
const { panels } = store.state.monitoringDashboard.dashboard.panelGroups[0];
@@ -396,7 +379,7 @@ describe('Dashboard', () => {
createShallowWrapper();
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('a loading icon appears in the first group', () => {
@@ -409,7 +392,7 @@ describe('Dashboard', () => {
});
describe('when all requests have been committed by the store', () => {
- beforeEach(() => {
+ beforeEach(async () => {
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
currentEnvironmentName: 'production',
currentDashboard: dashboardGitResponse[0].path,
@@ -418,26 +401,25 @@ describe('Dashboard', () => {
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(store);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
- it('it does not show loading icons in any group', () => {
+ it('it does not show loading icons in any group', async () => {
setupStoreWithData(store);
- wrapper.vm.$nextTick(() => {
- wrapper.findAll(GraphGroup).wrappers.forEach((groupWrapper) => {
- expect(groupWrapper.props('isLoading')).toBe(false);
- });
+ await nextTick();
+ wrapper.findAll(GraphGroup).wrappers.forEach((groupWrapper) => {
+ expect(groupWrapper.props('isLoading')).toBe(false);
});
});
});
describe('variables section', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createShallowWrapper({ hasMetrics: true });
setupStoreWithData(store);
store.state.monitoringDashboard.variables = storeVariables;
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('shows the variables section', () => {
@@ -446,12 +428,11 @@ describe('Dashboard', () => {
});
describe('links section', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createShallowWrapper({ hasMetrics: true });
setupStoreWithData(store);
setupStoreWithLinks(store);
-
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('shows the links section', () => {
@@ -464,10 +445,10 @@ describe('Dashboard', () => {
const findExpandedPanel = () => wrapper.find({ ref: 'expandedPanel' });
describe('when the panel is not expanded', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createShallowWrapper({ hasMetrics: true });
setupStoreWithData(store);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('expanded panel is not visible', () => {
@@ -502,7 +483,7 @@ describe('Dashboard', () => {
template: `<div><slot name="top-left"/></div>`,
};
- beforeEach(() => {
+ beforeEach(async () => {
createShallowWrapper({ hasMetrics: true }, { stubs: { DashboardPanel: MockPanel } });
setupStoreWithData(store);
@@ -517,8 +498,7 @@ describe('Dashboard', () => {
});
jest.spyOn(store, 'dispatch');
-
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays a single panel and others are hidden', () => {
@@ -561,13 +541,12 @@ describe('Dashboard', () => {
});
describe('when one of the metrics is missing', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createShallowWrapper({ hasMetrics: true });
setupStoreWithDashboard(store);
setMetricResult({ store, result: [], panel: 2 });
-
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('shows a group empty area', () => {
@@ -590,14 +569,13 @@ describe('Dashboard', () => {
const findDraggablePanels = () => wrapper.findAll('.js-draggable-panel');
const findRearrangeButton = () => wrapper.find('.js-rearrange-button');
- beforeEach(() => {
+ beforeEach(async () => {
// call original dispatch
store.dispatch.mockRestore();
createShallowWrapper({ hasMetrics: true });
setupStoreWithData(store);
-
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('wraps vuedraggable', () => {
@@ -611,9 +589,9 @@ describe('Dashboard', () => {
});
describe('when rearrange is enabled', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.setProps({ rearrangePanelsAvailable: true });
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays rearrange button', () => {
@@ -624,9 +602,9 @@ describe('Dashboard', () => {
const findFirstDraggableRemoveButton = () =>
findDraggablePanels().at(0).find('.js-draggable-remove');
- beforeEach(() => {
+ beforeEach(async () => {
findRearrangeButton().vm.$emit('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('it enables draggables', () => {
@@ -634,7 +612,7 @@ describe('Dashboard', () => {
expect(findEnabledDraggables().wrappers).toEqual(findDraggables().wrappers);
});
- it('metrics can be swapped', () => {
+ it('metrics can be swapped', async () => {
const firstDraggable = findDraggables().at(0);
const mockMetrics = [...metricsDashboardViewModel.panelGroups[0].panels];
@@ -645,43 +623,40 @@ describe('Dashboard', () => {
[mockMetrics[0], mockMetrics[1]] = [mockMetrics[1], mockMetrics[0]];
firstDraggable.vm.$emit('input', mockMetrics);
- return wrapper.vm.$nextTick(() => {
- const { panels } = wrapper.vm.dashboard.panelGroups[0];
+ await nextTick();
+ const { panels } = wrapper.vm.dashboard.panelGroups[0];
- expect(panels[1].title).toEqual(firstTitle);
- expect(panels[0].title).toEqual(secondTitle);
- });
+ expect(panels[1].title).toEqual(firstTitle);
+ expect(panels[0].title).toEqual(secondTitle);
});
- it('shows a remove button, which removes a panel', () => {
+ it('shows a remove button, which removes a panel', async () => {
expect(findFirstDraggableRemoveButton().find('a').exists()).toBe(true);
expect(findDraggablePanels().length).toEqual(metricsDashboardPanelCount);
findFirstDraggableRemoveButton().trigger('click');
- return wrapper.vm.$nextTick(() => {
- expect(findDraggablePanels().length).toEqual(metricsDashboardPanelCount - 1);
- });
+ await nextTick();
+ expect(findDraggablePanels().length).toEqual(metricsDashboardPanelCount - 1);
});
- it('it disables draggables when clicked again', () => {
+ it('it disables draggables when clicked again', async () => {
findRearrangeButton().vm.$emit('click');
- return wrapper.vm.$nextTick(() => {
- expect(findRearrangeButton().attributes('pressed')).toBeFalsy();
- expect(findEnabledDraggables().length).toBe(0);
- });
+ await nextTick();
+ expect(findRearrangeButton().attributes('pressed')).toBeFalsy();
+ expect(findEnabledDraggables().length).toBe(0);
});
});
});
});
describe('cluster health', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createShallowWrapper({ hasMetrics: true, showHeader: false });
// all_dashboards is not defined in health dashboards
store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, undefined);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('hides dashboard header by default', () => {
@@ -706,34 +681,31 @@ describe('Dashboard', () => {
document.title = '';
});
- it('is prepended with the overview dashboard name by default', () => {
+ it('is prepended with the overview dashboard name by default', async () => {
setupAllDashboards(store);
- return wrapper.vm.$nextTick().then(() => {
- expect(document.title.startsWith(`${overviewDashboardName} · `)).toBe(true);
- });
+ await nextTick();
+ expect(document.title.startsWith(`${overviewDashboardName} · `)).toBe(true);
});
- it('is prepended with dashboard name if path is known', () => {
+ it('is prepended with dashboard name if path is known', async () => {
const dashboard = dashboardGitResponse[1];
const currentDashboard = dashboard.path;
setupAllDashboards(store, currentDashboard);
- return wrapper.vm.$nextTick().then(() => {
- expect(document.title.startsWith(`${dashboard.display_name} · `)).toBe(true);
- });
+ await nextTick();
+ expect(document.title.startsWith(`${dashboard.display_name} · `)).toBe(true);
});
- it('is prepended with the overview dashboard name if path is not known', () => {
+ it('is prepended with the overview dashboard name if path is not known', async () => {
setupAllDashboards(store, 'unknown/path');
- return wrapper.vm.$nextTick().then(() => {
- expect(document.title.startsWith(`${overviewDashboardName} · `)).toBe(true);
- });
+ await nextTick();
+ expect(document.title.startsWith(`${overviewDashboardName} · `)).toBe(true);
});
- it('is not modified when dashboard name is not provided', () => {
+ it('is not modified when dashboard name is not provided', async () => {
const dashboard = { ...dashboardGitResponse[1], display_name: null };
const currentDashboard = dashboard.path;
@@ -743,9 +715,8 @@ describe('Dashboard', () => {
currentDashboard,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(document.title).toBe(originalTitle);
- });
+ await nextTick();
+ expect(document.title).toBe(originalTitle);
});
});
@@ -756,14 +727,13 @@ describe('Dashboard', () => {
const getClipboardTextFirstPanel = () =>
wrapper.findAll(DashboardPanel).at(panelIndex).props('clipboardText');
- beforeEach(() => {
+ beforeEach(async () => {
setupStoreWithData(store);
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
currentDashboard,
});
createShallowWrapper({ hasMetrics: true });
-
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('contains a link to the dashboard', () => {
@@ -785,7 +755,7 @@ describe('Dashboard', () => {
// the dashboard panels have a ref attribute set.
const getDashboardPanel = () => wrapper.find({ ref: panelRef });
- beforeEach(() => {
+ beforeEach(async () => {
setupStoreWithData(store);
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
currentDashboard,
@@ -795,8 +765,7 @@ describe('Dashboard', () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ hoveredPanel: panelRef });
-
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('contains a ref attribute inside a DashboardPanel component', () => {
diff --git a/spec/frontend/monitoring/components/dashboard_url_time_spec.js b/spec/frontend/monitoring/components/dashboard_url_time_spec.js
index e6785f34597..246dd598d19 100644
--- a/spec/frontend/monitoring/components/dashboard_url_time_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_url_time_spec.js
@@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import {
@@ -51,23 +52,22 @@ describe('dashboard invalid url parameters', () => {
queryToObject.mockReset();
});
- it('passes default url parameters to the time range picker', () => {
+ it('passes default url parameters to the time range picker', async () => {
queryToObject.mockReturnValue({});
createMountedWrapper();
- return wrapper.vm.$nextTick().then(() => {
- expect(findDateTimePicker().props('value')).toEqual(defaultTimeRange);
+ await nextTick();
+ expect(findDateTimePicker().props('value')).toEqual(defaultTimeRange);
- expect(store.dispatch).toHaveBeenCalledWith(
- 'monitoringDashboard/setTimeRange',
- expect.any(Object),
- );
- expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
- });
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'monitoringDashboard/setTimeRange',
+ expect.any(Object),
+ );
+ expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
});
- it('passes a fixed time range in the URL to the time range picker', () => {
+ it('passes a fixed time range in the URL to the time range picker', async () => {
const params = {
start: '2019-01-01T00:00:00.000Z',
end: '2019-01-10T00:00:00.000Z',
@@ -77,37 +77,35 @@ describe('dashboard invalid url parameters', () => {
createMountedWrapper();
- return wrapper.vm.$nextTick().then(() => {
- expect(findDateTimePicker().props('value')).toEqual(params);
+ await nextTick();
+ expect(findDateTimePicker().props('value')).toEqual(params);
- expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setTimeRange', params);
- expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
- });
+ expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setTimeRange', params);
+ expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
});
- it('passes a rolling time range in the URL to the time range picker', () => {
+ it('passes a rolling time range in the URL to the time range picker', async () => {
queryToObject.mockReturnValue({
duration_seconds: '120',
});
createMountedWrapper();
- return wrapper.vm.$nextTick().then(() => {
- const expectedTimeRange = {
- duration: { seconds: 60 * 2 },
- };
+ await nextTick();
+ const expectedTimeRange = {
+ duration: { seconds: 60 * 2 },
+ };
- expect(findDateTimePicker().props('value')).toMatchObject(expectedTimeRange);
+ expect(findDateTimePicker().props('value')).toMatchObject(expectedTimeRange);
- expect(store.dispatch).toHaveBeenCalledWith(
- 'monitoringDashboard/setTimeRange',
- expectedTimeRange,
- );
- expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
- });
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'monitoringDashboard/setTimeRange',
+ expectedTimeRange,
+ );
+ expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
});
- it('shows an error message and loads a default time range if invalid url parameters are passed', () => {
+ it('shows an error message and loads a default time range if invalid url parameters are passed', async () => {
queryToObject.mockReturnValue({
start: '<script>alert("XSS")</script>',
end: '<script>alert("XSS")</script>',
@@ -115,37 +113,35 @@ describe('dashboard invalid url parameters', () => {
createMountedWrapper();
- return wrapper.vm.$nextTick().then(() => {
- expect(createFlash).toHaveBeenCalled();
+ await nextTick();
+ expect(createFlash).toHaveBeenCalled();
- expect(findDateTimePicker().props('value')).toEqual(defaultTimeRange);
+ expect(findDateTimePicker().props('value')).toEqual(defaultTimeRange);
- expect(store.dispatch).toHaveBeenCalledWith(
- 'monitoringDashboard/setTimeRange',
- defaultTimeRange,
- );
- expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
- });
+ expect(store.dispatch).toHaveBeenCalledWith(
+ 'monitoringDashboard/setTimeRange',
+ defaultTimeRange,
+ );
+ expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
});
- it('redirects to different time range', () => {
+ it('redirects to different time range', async () => {
const toUrl = `${mockProjectDir}/-/environments/1/metrics`;
removeParams.mockReturnValueOnce(toUrl);
createMountedWrapper();
- return wrapper.vm.$nextTick().then(() => {
- findDateTimePicker().vm.$emit('input', {
- duration: { seconds: 120 },
- });
-
- // redirect to with new parameters
- expect(mergeUrlParams).toHaveBeenCalledWith({ duration_seconds: '120' }, toUrl);
- expect(redirectTo).toHaveBeenCalledTimes(1);
+ await nextTick();
+ findDateTimePicker().vm.$emit('input', {
+ duration: { seconds: 120 },
});
+
+ // redirect to with new parameters
+ expect(mergeUrlParams).toHaveBeenCalledWith({ duration_seconds: '120' }, toUrl);
+ expect(redirectTo).toHaveBeenCalledTimes(1);
});
- it('changes the url when a panel moves the time slider', () => {
+ it('changes the url when a panel moves the time slider', async () => {
const timeRange = {
start: '2020-01-01T00:00:00.000Z',
end: '2020-01-01T01:00:00.000Z',
@@ -155,12 +151,11 @@ describe('dashboard invalid url parameters', () => {
createMountedWrapper();
- return wrapper.vm.$nextTick().then(() => {
- wrapper.vm.onTimeRangeZoom(timeRange);
+ await nextTick();
+ wrapper.vm.onTimeRangeZoom(timeRange);
- expect(updateHistory).toHaveBeenCalled();
- expect(wrapper.vm.selectedTimeRange.start.toString()).toBe(timeRange.start);
- expect(wrapper.vm.selectedTimeRange.end.toString()).toBe(timeRange.end);
- });
+ expect(updateHistory).toHaveBeenCalled();
+ expect(wrapper.vm.selectedTimeRange.start.toString()).toBe(timeRange.start);
+ expect(wrapper.vm.selectedTimeRange.end.toString()).toBe(timeRange.end);
});
});
diff --git a/spec/frontend/monitoring/components/embeds/embed_group_spec.js b/spec/frontend/monitoring/components/embeds/embed_group_spec.js
index 79b223d96e4..47366b345a8 100644
--- a/spec/frontend/monitoring/components/embeds/embed_group_spec.js
+++ b/spec/frontend/monitoring/components/embeds/embed_group_spec.js
@@ -1,5 +1,6 @@
import { GlButton, GlCard } from '@gitlab/ui';
-import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
+import { mount, shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { TEST_HOST } from 'helpers/test_constants';
import EmbedGroup from '~/monitoring/components/embeds/embed_group.vue';
@@ -12,8 +13,7 @@ import {
multipleEmbedProps,
} from './mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Embed Group', () => {
let wrapper;
@@ -23,7 +23,6 @@ describe('Embed Group', () => {
function mountComponent({ urls = [TEST_HOST], shallow = true, stubs } = {}) {
const mountMethod = shallow ? shallowMount : mount;
wrapper = mountMethod(EmbedGroup, {
- localVue,
store,
propsData: {
urls,
@@ -76,16 +75,14 @@ describe('Embed Group', () => {
expect(wrapper.find('.gl-card-body').classes()).not.toContain('d-none');
});
- it('collapses when clicked', (done) => {
+ it('collapses when clicked', async () => {
metricsWithDataGetter.mockReturnValue([1]);
mountComponent({ shallow: false, stubs: { MetricEmbed: true } });
wrapper.find(GlButton).trigger('click');
- wrapper.vm.$nextTick(() => {
- expect(wrapper.find('.gl-card-body').classes()).toContain('d-none');
- done();
- });
+ await nextTick();
+ expect(wrapper.find('.gl-card-body').classes()).toContain('d-none');
});
});
diff --git a/spec/frontend/monitoring/components/embeds/metric_embed_spec.js b/spec/frontend/monitoring/components/embeds/metric_embed_spec.js
index 90647f50b14..f9f1be4f277 100644
--- a/spec/frontend/monitoring/components/embeds/metric_embed_spec.js
+++ b/spec/frontend/monitoring/components/embeds/metric_embed_spec.js
@@ -1,4 +1,5 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import { setHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
@@ -6,8 +7,7 @@ import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue';
import { groups, initialState, metricsData, metricsWithData } from './mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('MetricEmbed', () => {
let wrapper;
@@ -17,7 +17,6 @@ describe('MetricEmbed', () => {
function mountComponent() {
wrapper = shallowMount(MetricEmbed, {
- localVue,
store,
propsData: {
dashboardUrl: TEST_HOST,
diff --git a/spec/frontend/monitoring/components/graph_group_spec.js b/spec/frontend/monitoring/components/graph_group_spec.js
index 625dd3f0b33..c5b45564089 100644
--- a/spec/frontend/monitoring/components/graph_group_spec.js
+++ b/spec/frontend/monitoring/components/graph_group_spec.js
@@ -1,5 +1,6 @@
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import GraphGroup from '~/monitoring/components/graph_group.vue';
describe('Graph group component', () => {
@@ -38,13 +39,12 @@ describe('Graph group component', () => {
expect(findCaretIcon().props('name')).toBe('angle-down');
});
- it('should show the angle-right caret icon when the user collapses the group', () => {
+ it('should show the angle-right caret icon when the user collapses the group', async () => {
findToggleButton().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(findContent().isVisible()).toBe(false);
- expect(findCaretIcon().props('name')).toBe('angle-right');
- });
+ await nextTick();
+ expect(findContent().isVisible()).toBe(false);
+ expect(findCaretIcon().props('name')).toBe('angle-right');
});
it('should contain a tab index for the collapse button', () => {
@@ -53,15 +53,14 @@ describe('Graph group component', () => {
expect(groupToggle.attributes('tabindex')).toBeDefined();
});
- it('should show the open the group when collapseGroup is set to true', () => {
+ it('should show the open the group when collapseGroup is set to true', async () => {
wrapper.setProps({
collapseGroup: true,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findContent().isVisible()).toBe(true);
- expect(findCaretIcon().props('name')).toBe('angle-down');
- });
+ await nextTick();
+ expect(findContent().isVisible()).toBe(true);
+ expect(findCaretIcon().props('name')).toBe('angle-down');
});
});
@@ -77,12 +76,11 @@ describe('Graph group component', () => {
expect(findCaretIcon().props('name')).toBe('angle-right');
});
- it('should show the angle-right caret icon when collapseGroup is false', () => {
+ it('should show the angle-right caret icon when collapseGroup is false', async () => {
findToggleButton().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(findCaretIcon().props('name')).toBe('angle-down');
- });
+ await nextTick();
+ expect(findCaretIcon().props('name')).toBe('angle-down');
});
it('should call collapse the graph group content when enter is pressed on the caret icon', () => {
@@ -137,15 +135,14 @@ describe('Graph group component', () => {
expect(findCaretIcon().exists()).toBe(false);
});
- it('should show the panel content when collapse is set to false', () => {
+ it('should show the panel content when collapse is set to false', async () => {
wrapper.setProps({
collapseGroup: false,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findContent().isVisible()).toBe(true);
- expect(findCaretIcon().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findContent().isVisible()).toBe(true);
+ expect(findCaretIcon().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/monitoring/components/links_section_spec.js b/spec/frontend/monitoring/components/links_section_spec.js
index e37abf6722a..c9b5aeeecb8 100644
--- a/spec/frontend/monitoring/components/links_section_spec.js
+++ b/spec/frontend/monitoring/components/links_section_spec.js
@@ -36,7 +36,7 @@ describe('Links Section component', () => {
expect(findLinks().length).toBe(0);
});
- it('renders a link inside a section', () => {
+ it('renders a link inside a section', async () => {
setState([
{
title: 'GitLab Website',
@@ -44,23 +44,21 @@ describe('Links Section component', () => {
},
]);
- return wrapper.vm.$nextTick(() => {
- expect(findLinks()).toHaveLength(1);
- const firstLink = findLinks().at(0);
+ await nextTick();
+ expect(findLinks()).toHaveLength(1);
+ const firstLink = findLinks().at(0);
- expect(firstLink.attributes('href')).toBe('https://gitlab.com');
- expect(firstLink.text()).toBe('GitLab Website');
- });
+ expect(firstLink.attributes('href')).toBe('https://gitlab.com');
+ expect(firstLink.text()).toBe('GitLab Website');
});
- it('renders multiple links inside a section', () => {
+ it('renders multiple links inside a section', async () => {
const links = new Array(10)
.fill(null)
.map((_, i) => ({ title: `Title ${i}`, url: `https://gitlab.com/projects/${i}` }));
setState(links);
- return wrapper.vm.$nextTick(() => {
- expect(findLinks()).toHaveLength(10);
- });
+ await nextTick();
+ expect(findLinks()).toHaveLength(10);
});
});
diff --git a/spec/frontend/monitoring/components/refresh_button_spec.js b/spec/frontend/monitoring/components/refresh_button_spec.js
index 248cf32d54b..0e45cc021c5 100644
--- a/spec/frontend/monitoring/components/refresh_button_spec.js
+++ b/spec/frontend/monitoring/components/refresh_button_spec.js
@@ -1,6 +1,7 @@
import { GlDropdown, GlDropdownItem, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Visibility from 'visibilityjs';
+import { nextTick } from 'vue';
import RefreshButton from '~/monitoring/components/refresh_button.vue';
import { createStore } from '~/monitoring/stores';
@@ -79,9 +80,9 @@ describe('RefreshButton', () => {
describe('when a refresh rate is chosen', () => {
const optIndex = 2; // Other option than "Off"
- beforeEach(() => {
+ beforeEach(async () => {
findOptionAt(optIndex).vm.$emit('click');
- return wrapper.vm.$nextTick;
+ await nextTick();
});
it('refresh rate appears in the dropdown', () => {
@@ -101,7 +102,7 @@ describe('RefreshButton', () => {
jest.runOnlyPendingTimers();
expectFetchDataToHaveBeenCalledTimes(2);
- await wrapper.vm.$nextTick();
+ await nextTick();
jest.runOnlyPendingTimers();
expectFetchDataToHaveBeenCalledTimes(3);
@@ -113,7 +114,7 @@ describe('RefreshButton', () => {
jest.runOnlyPendingTimers();
expectFetchDataToHaveBeenCalledTimes(1);
- await wrapper.vm.$nextTick();
+ await nextTick();
jest.runOnlyPendingTimers();
expectFetchDataToHaveBeenCalledTimes(1);
@@ -128,9 +129,9 @@ describe('RefreshButton', () => {
});
describe('when "Off" refresh rate is chosen', () => {
- beforeEach(() => {
+ beforeEach(async () => {
findOptionAt(0).vm.$emit('click');
- return wrapper.vm.$nextTick;
+ await nextTick();
});
it('refresh rate is "Off" in the dropdown', () => {
diff --git a/spec/frontend/monitoring/components/variables/dropdown_field_spec.js b/spec/frontend/monitoring/components/variables/dropdown_field_spec.js
index f5ee32e78e6..643bbb39f04 100644
--- a/spec/frontend/monitoring/components/variables/dropdown_field_spec.js
+++ b/spec/frontend/monitoring/components/variables/dropdown_field_spec.js
@@ -1,5 +1,6 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DropdownField from '~/monitoring/components/variables/dropdown_field.vue';
describe('Custom variable component', () => {
@@ -53,14 +54,13 @@ describe('Custom variable component', () => {
expect(findDropdown().exists()).toBe(true);
});
- it('changing dropdown items triggers update', () => {
+ it('changing dropdown items triggers update', async () => {
createShallowWrapper();
jest.spyOn(wrapper.vm, '$emit');
findDropdownItems().at(1).vm.$emit('click');
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'canary');
- });
+ await nextTick();
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'canary');
});
});
diff --git a/spec/frontend/monitoring/components/variables/text_field_spec.js b/spec/frontend/monitoring/components/variables/text_field_spec.js
index c879803fddd..3073b3968aa 100644
--- a/spec/frontend/monitoring/components/variables/text_field_spec.js
+++ b/spec/frontend/monitoring/components/variables/text_field_spec.js
@@ -1,5 +1,6 @@
import { GlFormInput } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import TextField from '~/monitoring/components/variables/text_field.vue';
describe('Text variable component', () => {
@@ -23,15 +24,14 @@ describe('Text variable component', () => {
expect(findInput().exists()).toBe(true);
});
- it('always has a default value', () => {
+ it('always has a default value', async () => {
createShallowWrapper();
- return wrapper.vm.$nextTick(() => {
- expect(findInput().attributes('value')).toBe(propsData.value);
- });
+ await nextTick();
+ expect(findInput().attributes('value')).toBe(propsData.value);
});
- it('triggers keyup enter', () => {
+ it('triggers keyup enter', async () => {
createShallowWrapper();
jest.spyOn(wrapper.vm, '$emit');
@@ -39,12 +39,11 @@ describe('Text variable component', () => {
findInput().trigger('input');
findInput().trigger('keyup.enter');
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'prod-pod');
- });
+ await nextTick();
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'prod-pod');
});
- it('triggers blur enter', () => {
+ it('triggers blur enter', async () => {
createShallowWrapper();
jest.spyOn(wrapper.vm, '$emit');
@@ -52,8 +51,7 @@ describe('Text variable component', () => {
findInput().trigger('input');
findInput().trigger('blur');
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'canary-pod');
- });
+ await nextTick();
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'canary-pod');
});
});
diff --git a/spec/frontend/monitoring/components/variables_section_spec.js b/spec/frontend/monitoring/components/variables_section_spec.js
index 6157de0dafe..64b93bd3027 100644
--- a/spec/frontend/monitoring/components/variables_section_spec.js
+++ b/spec/frontend/monitoring/components/variables_section_spec.js
@@ -1,5 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
+import { nextTick } from 'vue';
import { updateHistory, mergeUrlParams } from '~/lib/utils/url_utility';
import DropdownField from '~/monitoring/components/variables/dropdown_field.vue';
import TextField from '~/monitoring/components/variables/text_field.vue';
@@ -40,11 +41,11 @@ describe('Metrics dashboard/variables section component', () => {
});
describe('when variables are set', () => {
- beforeEach(() => {
+ beforeEach(async () => {
store.state.monitoringDashboard.variables = storeVariables;
createShallowWrapper();
- return wrapper.vm.$nextTick;
+ await nextTick();
});
it('shows the variables section', () => {
@@ -83,34 +84,32 @@ describe('Metrics dashboard/variables section component', () => {
createShallowWrapper();
});
- it('merges the url params and refreshes the dashboard when a text-based variables inputs are updated', () => {
+ it('merges the url params and refreshes the dashboard when a text-based variables inputs are updated', async () => {
const firstInput = findTextInputs().at(0);
firstInput.vm.$emit('input', 'test');
- return wrapper.vm.$nextTick(() => {
- expect(updateVariablesAndFetchData).toHaveBeenCalled();
- expect(mergeUrlParams).toHaveBeenCalledWith(
- convertVariablesForURL(storeVariables),
- window.location.href,
- );
- expect(updateHistory).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(updateVariablesAndFetchData).toHaveBeenCalled();
+ expect(mergeUrlParams).toHaveBeenCalledWith(
+ convertVariablesForURL(storeVariables),
+ window.location.href,
+ );
+ expect(updateHistory).toHaveBeenCalled();
});
- it('merges the url params and refreshes the dashboard when a custom-based variables inputs are updated', () => {
+ it('merges the url params and refreshes the dashboard when a custom-based variables inputs are updated', async () => {
const firstInput = findCustomInputs().at(0);
firstInput.vm.$emit('input', 'test');
- return wrapper.vm.$nextTick(() => {
- expect(updateVariablesAndFetchData).toHaveBeenCalled();
- expect(mergeUrlParams).toHaveBeenCalledWith(
- convertVariablesForURL(storeVariables),
- window.location.href,
- );
- expect(updateHistory).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(updateVariablesAndFetchData).toHaveBeenCalled();
+ expect(mergeUrlParams).toHaveBeenCalledWith(
+ convertVariablesForURL(storeVariables),
+ window.location.href,
+ );
+ expect(updateHistory).toHaveBeenCalled();
});
it('does not merge the url params and refreshes the dashboard if the value entered is not different that is what currently stored', () => {
diff --git a/spec/frontend/monitoring/router_spec.js b/spec/frontend/monitoring/router_spec.js
index b027d60f61e..7758dd351b7 100644
--- a/spec/frontend/monitoring/router_spec.js
+++ b/spec/frontend/monitoring/router_spec.js
@@ -1,4 +1,5 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import VueRouter from 'vue-router';
import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardPage from '~/monitoring/pages/dashboard_page.vue';
@@ -25,8 +26,7 @@ describe('Monitoring router', () => {
let store;
const createWrapper = (basePath, routeArg) => {
- const localVue = createLocalVue();
- localVue.use(VueRouter);
+ Vue.use(VueRouter);
router = createRouter(basePath);
if (routeArg !== undefined) {
@@ -34,7 +34,6 @@ describe('Monitoring router', () => {
}
return mount(MockApp, {
- localVue,
store,
router,
});
diff --git a/spec/frontend/mr_popover/mr_popover_spec.js b/spec/frontend/mr_popover/mr_popover_spec.js
index 36ad82e93a5..23f97073e9e 100644
--- a/spec/frontend/mr_popover/mr_popover_spec.js
+++ b/spec/frontend/mr_popover/mr_popover_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import MRPopover from '~/mr_popover/components/mr_popover.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
@@ -25,16 +26,15 @@ describe('MR Popover', () => {
});
});
- it('shows skeleton-loader while apollo is loading', () => {
+ it('shows skeleton-loader while apollo is loading', async () => {
wrapper.vm.$apollo.queries.mergeRequest.loading = true;
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(wrapper.element).toMatchSnapshot();
});
describe('loaded state', () => {
- it('matches the snapshot', () => {
+ it('matches the snapshot', 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('MR Popover', () => {
},
});
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.element).toMatchSnapshot();
- });
+ await nextTick();
+ expect(wrapper.element).toMatchSnapshot();
});
- it('does not show CI Icon if there is no pipeline data', () => {
+ it('does not show CI Icon if there is no pipeline data', 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({
@@ -69,15 +68,13 @@ describe('MR Popover', () => {
},
});
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(CiIcon).exists()).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.find(CiIcon).exists()).toBe(false);
});
- it('falls back to cached MR title when request fails', () => {
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.text()).toContain('MR Title');
- });
+ it('falls back to cached MR title when request fails', async () => {
+ await nextTick();
+ expect(wrapper.text()).toContain('MR Title');
});
});
});
diff --git a/spec/frontend/nav/components/responsive_app_spec.js b/spec/frontend/nav/components/responsive_app_spec.js
index 4af8c6020bc..76b8ebdc92f 100644
--- a/spec/frontend/nav/components/responsive_app_spec.js
+++ b/spec/frontend/nav/components/responsive_app_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import ResponsiveApp from '~/nav/components/responsive_app.vue';
import ResponsiveHeader from '~/nav/components/responsive_header.vue';
import ResponsiveHome from '~/nav/components/responsive_home.vue';
@@ -62,7 +63,7 @@ describe('~/nav/components/responsive_app.vue', () => {
wrapper.vm.$root.$emit(evt);
- await wrapper.vm.$nextTick();
+ await nextTick();
}, Promise.resolve());
expect(hasMobileOverlayVisible()).toBe(expectation);
@@ -97,7 +98,7 @@ describe('~/nav/components/responsive_app.vue', () => {
findHome().vm.$emit('menu-item-click', { view });
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('shows header', () => {
diff --git a/spec/frontend/nav/components/top_nav_menu_item_spec.js b/spec/frontend/nav/components/top_nav_menu_item_spec.js
index 71154e18915..a7430d8c73f 100644
--- a/spec/frontend/nav/components/top_nav_menu_item_spec.js
+++ b/spec/frontend/nav/components/top_nav_menu_item_spec.js
@@ -137,6 +137,7 @@ describe('~/nav/components/top_nav_menu_item.vue', () => {
expect(wrapper.classes()).toStrictEqual([
'top-nav-menu-item',
'gl-display-block',
+ 'gl-pr-3!',
...expectedClasses,
]);
});
diff --git a/spec/frontend/notebook/cells/code_spec.js b/spec/frontend/notebook/cells/code_spec.js
index 669bdc2f89a..9a2db061278 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';
@@ -25,12 +25,10 @@ describe('Code component', () => {
};
describe('without output', () => {
- beforeEach((done) => {
+ beforeEach(() => {
vm = setupComponent(json.cells[0]);
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('does not render output prompt', () => {
@@ -39,12 +37,10 @@ describe('Code component', () => {
});
describe('with output', () => {
- beforeEach((done) => {
+ beforeEach(() => {
vm = setupComponent(json.cells[2]);
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('does not render output prompt', () => {
@@ -58,12 +54,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 +72,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/notebook/cells/output/index_spec.js b/spec/frontend/notebook/cells/output/index_spec.js
index 7ece73d375c..8e04e4c146c 100644
--- a/spec/frontend/notebook/cells/output/index_spec.js
+++ b/spec/frontend/notebook/cells/output/index_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import json from 'test_fixtures/blob/notebook/basic.json';
import CodeComponent from '~/notebook/cells/output/index.vue';
@@ -18,13 +18,11 @@ describe('Output component', () => {
};
describe('text output', () => {
- beforeEach((done) => {
+ beforeEach(() => {
const textType = json.cells[2];
createComponent(textType.outputs[0]);
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('renders as plain text', () => {
@@ -37,13 +35,11 @@ describe('Output component', () => {
});
describe('image output', () => {
- beforeEach((done) => {
+ beforeEach(() => {
const imageType = json.cells[3];
createComponent(imageType.outputs[0]);
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('renders as an image', () => {
@@ -86,13 +82,11 @@ describe('Output component', () => {
});
describe('svg output', () => {
- beforeEach((done) => {
+ beforeEach(() => {
const svgType = json.cells[5];
createComponent(svgType.outputs[0]);
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('renders as an svg', () => {
@@ -101,13 +95,11 @@ describe('Output component', () => {
});
describe('default to plain text', () => {
- beforeEach((done) => {
+ beforeEach(() => {
const unknownType = json.cells[6];
createComponent(unknownType.outputs[0]);
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('renders as plain text', () => {
@@ -119,16 +111,14 @@ describe('Output component', () => {
expect(vm.$el.querySelector('.prompt span')).not.toBeNull();
});
- it("renders as plain text when doesn't recognise other types", (done) => {
+ it("renders as plain text when doesn't recognise other types", async () => {
const unknownType = json.cells[7];
createComponent(unknownType.outputs[0]);
- setImmediate(() => {
- expect(vm.$el.querySelector('pre')).not.toBeNull();
- expect(vm.$el.textContent.trim()).toContain('testing');
+ await nextTick();
- done();
- });
+ expect(vm.$el.querySelector('pre')).not.toBeNull();
+ expect(vm.$el.textContent.trim()).toContain('testing');
});
});
});
diff --git a/spec/frontend/notebook/cells/prompt_spec.js b/spec/frontend/notebook/cells/prompt_spec.js
index 8cdcd1f84de..89b2d7b2b90 100644
--- a/spec/frontend/notebook/cells/prompt_spec.js
+++ b/spec/frontend/notebook/cells/prompt_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import PromptComponent from '~/notebook/cells/prompt.vue';
const Component = Vue.extend(PromptComponent);
@@ -7,7 +7,7 @@ describe('Prompt component', () => {
let vm;
describe('input', () => {
- beforeEach((done) => {
+ beforeEach(() => {
vm = new Component({
propsData: {
type: 'In',
@@ -16,9 +16,7 @@ describe('Prompt component', () => {
});
vm.$mount();
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('renders in label', () => {
@@ -31,7 +29,7 @@ describe('Prompt component', () => {
});
describe('output', () => {
- beforeEach((done) => {
+ beforeEach(() => {
vm = new Component({
propsData: {
type: 'Out',
@@ -40,9 +38,7 @@ describe('Prompt component', () => {
});
vm.$mount();
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('renders in label', () => {
diff --git a/spec/frontend/notebook/index_spec.js b/spec/frontend/notebook/index_spec.js
index cd531d628b3..475c41a72f6 100644
--- a/spec/frontend/notebook/index_spec.js
+++ b/spec/frontend/notebook/index_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import json from 'test_fixtures/blob/notebook/basic.json';
import jsonWithWorksheet from 'test_fixtures/blob/notebook/worksheets.json';
import Notebook from '~/notebook/index.vue';
@@ -17,12 +17,10 @@ describe('Notebook component', () => {
}
describe('without JSON', () => {
- beforeEach((done) => {
+ beforeEach(() => {
vm = buildComponent({});
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('does not render', () => {
@@ -31,12 +29,10 @@ describe('Notebook component', () => {
});
describe('with JSON', () => {
- beforeEach((done) => {
+ beforeEach(() => {
vm = buildComponent(json);
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('renders cells', () => {
@@ -57,12 +53,10 @@ describe('Notebook component', () => {
});
describe('with worksheets', () => {
- beforeEach((done) => {
+ beforeEach(() => {
vm = buildComponent(jsonWithWorksheet);
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
it('renders cells', () => {
diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js
index 16dbf60cef4..a605edc4357 100644
--- a/spec/frontend/notes/components/comment_form_spec.js
+++ b/spec/frontend/notes/components/comment_form_spec.js
@@ -20,7 +20,6 @@ import { loggedOutnoteableData, notesDataMock, userDataMock, noteableDataMock }
jest.mock('autosize');
jest.mock('~/commons/nav/user_merge_requests');
jest.mock('~/flash');
-jest.mock('~/gl_form');
Vue.use(Vuex);
@@ -466,8 +465,8 @@ describe('issue_comment_form component', () => {
await findCloseReopenButton().trigger('click');
- await wrapper.vm.$nextTick;
- await wrapper.vm.$nextTick;
+ await nextTick;
+ await nextTick;
expect(createFlash).toHaveBeenCalledWith({
message: `Something went wrong while closing the ${type}. Please try again later.`,
@@ -502,8 +501,8 @@ describe('issue_comment_form component', () => {
await findCloseReopenButton().trigger('click');
- await wrapper.vm.$nextTick;
- await wrapper.vm.$nextTick;
+ await nextTick;
+ await nextTick;
expect(createFlash).toHaveBeenCalledWith({
message: `Something went wrong while reopening the ${type}. Please try again later.`,
@@ -521,7 +520,7 @@ describe('issue_comment_form component', () => {
await findCloseReopenButton().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(refreshUserMergeRequestCounts).toHaveBeenCalled();
});
@@ -581,7 +580,7 @@ describe('issue_comment_form component', () => {
// check checkbox
checkbox.element.checked = shouldCheckboxBeChecked;
checkbox.trigger('change');
- await wrapper.vm.$nextTick();
+ await nextTick();
// submit comment
findCommentButton().trigger('click');
diff --git a/spec/frontend/notes/components/diff_discussion_header_spec.js b/spec/frontend/notes/components/diff_discussion_header_spec.js
index fa34a5e8d39..9f94dd693cb 100644
--- a/spec/frontend/notes/components/diff_discussion_header_spec.js
+++ b/spec/frontend/notes/components/diff_discussion_header_spec.js
@@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import diffDiscussionHeader from '~/notes/components/diff_discussion_header.vue';
import createStore from '~/notes/stores';
@@ -24,16 +25,15 @@ describe('diff_discussion_header component', () => {
wrapper.destroy();
});
- it('should render user avatar', () => {
+ it('should render user avatar', async () => {
const discussion = { ...discussionMock };
discussion.diff_file = mockDiffFile;
discussion.diff_discussion = true;
wrapper.setProps({ discussion });
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find('.user-avatar-link').exists()).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find('.user-avatar-link').exists()).toBe(true);
});
describe('action text', () => {
@@ -41,7 +41,7 @@ describe('diff_discussion_header component', () => {
const truncatedCommitId = commitId.substr(0, 8);
let commitElement;
- beforeEach((done) => {
+ beforeEach(async () => {
store.state.diffs = {
projectPath: 'something',
};
@@ -58,41 +58,30 @@ describe('diff_discussion_header component', () => {
},
});
- wrapper.vm
- .$nextTick()
- .then(() => {
- commitElement = wrapper.find('.commit-sha');
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ commitElement = wrapper.find('.commit-sha');
});
describe('for diff threads without a commit id', () => {
- it('should show started a thread on the diff text', (done) => {
+ it('should show started a thread on the diff text', async () => {
Object.assign(wrapper.vm.discussion, {
for_commit: false,
commit_id: null,
});
- wrapper.vm.$nextTick(() => {
- expect(wrapper.text()).toContain('started a thread on the diff');
-
- done();
- });
+ await nextTick();
+ expect(wrapper.text()).toContain('started a thread on the diff');
});
- it('should show thread on older version text', (done) => {
+ it('should show thread on older version text', async () => {
Object.assign(wrapper.vm.discussion, {
for_commit: false,
commit_id: null,
active: false,
});
- wrapper.vm.$nextTick(() => {
- expect(wrapper.text()).toContain('started a thread on an old version of the diff');
-
- done();
- });
+ await nextTick();
+ expect(wrapper.text()).toContain('started a thread on an old version of the diff');
});
});
@@ -105,31 +94,25 @@ describe('diff_discussion_header component', () => {
});
describe('for diff thread with a commit id', () => {
- it('should display started thread on commit header', (done) => {
+ it('should display started thread on commit header', async () => {
wrapper.vm.discussion.for_commit = false;
- wrapper.vm.$nextTick(() => {
- expect(wrapper.text()).toContain(`started a thread on commit ${truncatedCommitId}`);
-
- expect(commitElement).not.toBe(null);
+ await nextTick();
+ expect(wrapper.text()).toContain(`started a thread on commit ${truncatedCommitId}`);
- done();
- });
+ expect(commitElement).not.toBe(null);
});
- it('should display outdated change on commit header', (done) => {
+ it('should display outdated change on commit header', async () => {
wrapper.vm.discussion.for_commit = false;
wrapper.vm.discussion.active = false;
- wrapper.vm.$nextTick(() => {
- expect(wrapper.text()).toContain(
- `started a thread on an outdated change in commit ${truncatedCommitId}`,
- );
+ await nextTick();
+ expect(wrapper.text()).toContain(
+ `started a thread on an outdated change in commit ${truncatedCommitId}`,
+ );
- expect(commitElement).not.toBe(null);
-
- done();
- });
+ expect(commitElement).not.toBe(null);
});
});
});
diff --git a/spec/frontend/notes/components/discussion_counter_spec.js b/spec/frontend/notes/components/discussion_counter_spec.js
index c454d502beb..a856d002d2e 100644
--- a/spec/frontend/notes/components/discussion_counter_spec.js
+++ b/spec/frontend/notes/components/discussion_counter_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import DiscussionCounter from '~/notes/components/discussion_counter.vue';
import notesModule from '~/notes/stores/modules';
@@ -10,9 +11,8 @@ describe('DiscussionCounter component', () => {
let store;
let wrapper;
let setExpandDiscussionsFn;
- const localVue = createLocalVue();
- localVue.use(Vuex);
+ Vue.use(Vuex);
beforeEach(() => {
window.mrTabs = {};
@@ -45,7 +45,7 @@ describe('DiscussionCounter component', () => {
describe('has no discussions', () => {
it('does not render', () => {
- wrapper = shallowMount(DiscussionCounter, { store, localVue });
+ wrapper = shallowMount(DiscussionCounter, { store });
expect(wrapper.find({ ref: 'discussionCounter' }).exists()).toBe(false);
});
@@ -55,7 +55,7 @@ describe('DiscussionCounter component', () => {
it('does not render', () => {
store.commit(types.ADD_OR_UPDATE_DISCUSSIONS, [{ ...discussionMock, resolvable: false }]);
store.dispatch('updateResolvableDiscussionsCounts');
- wrapper = shallowMount(DiscussionCounter, { store, localVue });
+ wrapper = shallowMount(DiscussionCounter, { store });
expect(wrapper.find({ ref: 'discussionCounter' }).exists()).toBe(false);
});
@@ -75,7 +75,7 @@ describe('DiscussionCounter component', () => {
it('renders', () => {
updateStore();
- wrapper = shallowMount(DiscussionCounter, { store, localVue });
+ wrapper = shallowMount(DiscussionCounter, { store });
expect(wrapper.find({ ref: 'discussionCounter' }).exists()).toBe(true);
});
@@ -86,7 +86,7 @@ describe('DiscussionCounter component', () => {
${'allResolved'} | ${true} | ${true} | ${1}
`('renders correctly if $title', ({ resolved, isActive, groupLength }) => {
updateStore({ resolvable: true, resolved });
- wrapper = shallowMount(DiscussionCounter, { store, localVue });
+ wrapper = shallowMount(DiscussionCounter, { store });
expect(wrapper.find(`.is-active`).exists()).toBe(isActive);
expect(wrapper.findAll(GlButton)).toHaveLength(groupLength);
@@ -99,7 +99,7 @@ describe('DiscussionCounter component', () => {
const discussion = { ...discussionMock, expanded };
store.commit(types.ADD_OR_UPDATE_DISCUSSIONS, [discussion]);
store.dispatch('updateResolvableDiscussionsCounts');
- wrapper = shallowMount(DiscussionCounter, { store, localVue });
+ wrapper = shallowMount(DiscussionCounter, { store });
toggleAllButton = wrapper.find('.toggle-all-discussions-btn');
};
@@ -113,7 +113,7 @@ describe('DiscussionCounter component', () => {
expect(setExpandDiscussionsFn).toHaveBeenCalledTimes(1);
});
- it('collapses all discussions if expanded', () => {
+ it('collapses all discussions if expanded', async () => {
updateStoreWithExpanded(true);
expect(wrapper.vm.allExpanded).toBe(true);
@@ -121,13 +121,12 @@ describe('DiscussionCounter component', () => {
toggleAllButton.vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.allExpanded).toBe(false);
- expect(toggleAllButton.props('icon')).toBe('angle-down');
- });
+ await nextTick();
+ expect(wrapper.vm.allExpanded).toBe(false);
+ expect(toggleAllButton.props('icon')).toBe('angle-down');
});
- it('expands all discussions if collapsed', () => {
+ it('expands all discussions if collapsed', async () => {
updateStoreWithExpanded(false);
expect(wrapper.vm.allExpanded).toBe(false);
@@ -135,10 +134,9 @@ describe('DiscussionCounter component', () => {
toggleAllButton.vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.allExpanded).toBe(true);
- expect(toggleAllButton.props('icon')).toBe('angle-up');
- });
+ await nextTick();
+ expect(wrapper.vm.allExpanded).toBe(true);
+ expect(toggleAllButton.props('icon')).toBe('angle-up');
});
});
});
diff --git a/spec/frontend/notes/components/discussion_filter_spec.js b/spec/frontend/notes/components/discussion_filter_spec.js
index 17998dfc9d5..27206bddbfc 100644
--- a/spec/frontend/notes/components/discussion_filter_spec.js
+++ b/spec/frontend/notes/components/discussion_filter_spec.js
@@ -1,5 +1,6 @@
import { GlDropdown } from '@gitlab/ui';
-import { createLocalVue, mount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import AxiosMockAdapter from 'axios-mock-adapter';
import Vuex from 'vuex';
import { TEST_HOST } from 'helpers/test_constants';
@@ -12,9 +13,7 @@ import notesModule from '~/notes/stores/modules';
import { discussionFiltersMock, discussionMock } from '../mock_data';
-const localVue = createLocalVue();
-
-localVue.use(Vuex);
+Vue.use(Vuex);
const DISCUSSION_PATH = `${TEST_HOST}/example`;
@@ -58,7 +57,6 @@ describe('DiscussionFilter component', () => {
filters: discussionFiltersMock,
selectedValue: DISCUSSION_FILTERS_DEFAULT_VALUE,
},
- localVue,
});
};
@@ -153,13 +151,11 @@ describe('DiscussionFilter component', () => {
window.mrTabs = undefined;
});
- it('only renders when discussion tab is active', (done) => {
+ it('only renders when discussion tab is active', async () => {
eventHub.$emit('MergeRequestTabChange', 'commit');
- wrapper.vm.$nextTick(() => {
- expect(wrapper.html()).toBe('');
- done();
- });
+ await nextTick();
+ expect(wrapper.html()).toBe('');
});
});
@@ -168,58 +164,48 @@ describe('DiscussionFilter component', () => {
window.location.hash = '';
});
- it('updates the filter when the URL links to a note', (done) => {
+ it('updates the filter when the URL links to a note', async () => {
window.location.hash = `note_${discussionMock.notes[0].id}`;
wrapper.vm.currentValue = discussionFiltersMock[2].value;
wrapper.vm.handleLocationHash();
- wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.currentValue).toBe(DISCUSSION_FILTERS_DEFAULT_VALUE);
- done();
- });
+ await nextTick();
+ expect(wrapper.vm.currentValue).toBe(DISCUSSION_FILTERS_DEFAULT_VALUE);
});
- it('does not update the filter when the current filter is "Show all activity"', (done) => {
+ it('does not update the filter when the current filter is "Show all activity"', async () => {
window.location.hash = `note_${discussionMock.notes[0].id}`;
wrapper.vm.handleLocationHash();
- wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.currentValue).toBe(DISCUSSION_FILTERS_DEFAULT_VALUE);
- done();
- });
+ await nextTick();
+ expect(wrapper.vm.currentValue).toBe(DISCUSSION_FILTERS_DEFAULT_VALUE);
});
- it('only updates filter when the URL links to a note', (done) => {
+ it('only updates filter when the URL links to a note', async () => {
window.location.hash = `testing123`;
wrapper.vm.handleLocationHash();
- wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.currentValue).toBe(DISCUSSION_FILTERS_DEFAULT_VALUE);
- done();
- });
+ await nextTick();
+ expect(wrapper.vm.currentValue).toBe(DISCUSSION_FILTERS_DEFAULT_VALUE);
});
- it('fetches discussions when there is a hash', (done) => {
+ it('fetches discussions when there is a hash', async () => {
window.location.hash = `note_${discussionMock.notes[0].id}`;
wrapper.vm.currentValue = discussionFiltersMock[2].value;
jest.spyOn(wrapper.vm, 'selectFilter').mockImplementation(() => {});
wrapper.vm.handleLocationHash();
- wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.selectFilter).toHaveBeenCalled();
- done();
- });
+ await nextTick();
+ expect(wrapper.vm.selectFilter).toHaveBeenCalled();
});
- it('does not fetch discussions when there is no hash', (done) => {
+ it('does not fetch discussions when there is no hash', async () => {
window.location.hash = '';
jest.spyOn(wrapper.vm, 'selectFilter').mockImplementation(() => {});
wrapper.vm.handleLocationHash();
- wrapper.vm.$nextTick(() => {
- expect(wrapper.vm.selectFilter).not.toHaveBeenCalled();
- done();
- });
+ await nextTick();
+ expect(wrapper.vm.selectFilter).not.toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/notes/components/discussion_navigator_spec.js b/spec/frontend/notes/components/discussion_navigator_spec.js
index e430e18b76a..77ae7b2c3b5 100644
--- a/spec/frontend/notes/components/discussion_navigator_spec.js
+++ b/spec/frontend/notes/components/discussion_navigator_spec.js
@@ -1,6 +1,6 @@
/* global Mousetrap */
import 'mousetrap';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import {
keysFor,
@@ -11,8 +11,6 @@ import DiscussionNavigator from '~/notes/components/discussion_navigator.vue';
import eventHub from '~/notes/event_hub';
describe('notes/components/discussion_navigator', () => {
- const localVue = createLocalVue();
-
let wrapper;
let jumpToNextDiscussion;
let jumpToPreviousDiscussion;
@@ -20,12 +18,12 @@ describe('notes/components/discussion_navigator', () => {
const createComponent = () => {
wrapper = shallowMount(DiscussionNavigator, {
mixins: [
- localVue.extend({
+ {
methods: {
jumpToNextDiscussion,
jumpToPreviousDiscussion,
},
- }),
+ },
],
});
};
@@ -48,7 +46,7 @@ describe('notes/components/discussion_navigator', () => {
beforeEach(() => {
onSpy = jest.spyOn(eventHub, '$on');
- vm = new (Vue.extend(DiscussionNavigator))();
+ vm = new Vue(DiscussionNavigator);
});
it('listens for jumpToFirstUnresolvedDiscussion events', () => {
diff --git a/spec/frontend/notes/components/discussion_notes_spec.js b/spec/frontend/notes/components/discussion_notes_spec.js
index 59ac75f00e6..3506b6ac9f3 100644
--- a/spec/frontend/notes/components/discussion_notes_spec.js
+++ b/spec/frontend/notes/components/discussion_notes_spec.js
@@ -1,6 +1,7 @@
import { getByRole } from '@testing-library/dom';
import { shallowMount, mount } from '@vue/test-utils';
import '~/behaviors/markdown/render_gfm';
+import { nextTick } from 'vue';
import DiscussionNotes from '~/notes/components/discussion_notes.vue';
import NoteableNote from '~/notes/components/noteable_note.vue';
import { SYSTEM_NOTE } from '~/notes/constants';
@@ -135,28 +136,25 @@ describe('DiscussionNotes', () => {
createComponent({ shouldGroupReplies: true, isExpanded: true });
});
- it('emits deleteNote when first note emits handleDeleteNote', () => {
+ it('emits deleteNote when first note emits handleDeleteNote', async () => {
findNoteAtIndex(0).vm.$emit('handleDeleteNote');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().deleteNote).toBeTruthy();
- });
+ await nextTick();
+ expect(wrapper.emitted().deleteNote).toBeTruthy();
});
- it('emits startReplying when first note emits startReplying', () => {
+ it('emits startReplying when first note emits startReplying', async () => {
findNoteAtIndex(0).vm.$emit('startReplying');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().startReplying).toBeTruthy();
- });
+ await nextTick();
+ expect(wrapper.emitted().startReplying).toBeTruthy();
});
- it('emits deleteNote when second note emits handleDeleteNote', () => {
+ it('emits deleteNote when second note emits handleDeleteNote', async () => {
findNoteAtIndex(1).vm.$emit('handleDeleteNote');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().deleteNote).toBeTruthy();
- });
+ await nextTick();
+ expect(wrapper.emitted().deleteNote).toBeTruthy();
});
});
@@ -167,12 +165,11 @@ describe('DiscussionNotes', () => {
note = wrapper.find('.notes > *');
});
- it('emits deleteNote when first note emits handleDeleteNote', () => {
+ it('emits deleteNote when first note emits handleDeleteNote', async () => {
note.vm.$emit('handleDeleteNote');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().deleteNote).toBeTruthy();
- });
+ await nextTick();
+ expect(wrapper.emitted().deleteNote).toBeTruthy();
});
});
});
diff --git a/spec/frontend/notes/components/discussion_resolve_button_spec.js b/spec/frontend/notes/components/discussion_resolve_button_spec.js
index 64e061830b9..ca0c0ca6de8 100644
--- a/spec/frontend/notes/components/discussion_resolve_button_spec.js
+++ b/spec/frontend/notes/components/discussion_resolve_button_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import resolveDiscussionButton from '~/notes/components/discussion_resolve_button.vue';
const buttonTitle = 'Resolve discussion';
@@ -26,15 +27,14 @@ describe('resolveDiscussionButton', () => {
wrapper.destroy();
});
- it('should emit a onClick event on button click', () => {
+ it('should emit a onClick event on button click', async () => {
const button = wrapper.find(GlButton);
button.vm.$emit('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted()).toEqual({
- onClick: [[]],
- });
+ await nextTick();
+ expect(wrapper.emitted()).toEqual({
+ onClick: [[]],
});
});
@@ -57,7 +57,7 @@ describe('resolveDiscussionButton', () => {
expect(button.props('loading')).toEqual(true);
});
- it('should only show a loading spinner while resolving', () => {
+ it('should only show a loading spinner while resolving', async () => {
factory({
propsData: {
isResolving: false,
@@ -67,8 +67,7 @@ describe('resolveDiscussionButton', () => {
const button = wrapper.find(GlButton);
- wrapper.vm.$nextTick(() => {
- expect(button.props('loading')).toEqual(false);
- });
+ await nextTick();
+ expect(button.props('loading')).toEqual(false);
});
});
diff --git a/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js b/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js
index 4348445f7ca..5bc6282db03 100644
--- a/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js
+++ b/spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js
@@ -1,17 +1,14 @@
import { GlButton } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'spec/test_constants';
import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue';
-const localVue = createLocalVue();
-
describe('ResolveWithIssueButton', () => {
let wrapper;
const url = `${TEST_HOST}/hello-world/`;
beforeEach(() => {
wrapper = shallowMount(ResolveWithIssueButton, {
- localVue,
propsData: {
url,
},
diff --git a/spec/frontend/notes/components/note_actions_spec.js b/spec/frontend/notes/components/note_actions_spec.js
index ecce854b00a..780f24b3aa8 100644
--- a/spec/frontend/notes/components/note_actions_spec.js
+++ b/spec/frontend/notes/components/note_actions_spec.js
@@ -1,6 +1,6 @@
-import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
+import { mount, createWrapper } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
-import Vue from 'vue';
+import { nextTick } from 'vue';
import { TEST_HOST } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
@@ -20,11 +20,9 @@ describe('noteActions', () => {
const findUserAccessRoleBadgeText = (idx) => findUserAccessRoleBadge(idx).text().trim();
const mountNoteActions = (propsData, computed) => {
- const localVue = createLocalVue();
- return mount(localVue.extend(noteActions), {
+ return mount(noteActions, {
store,
propsData,
- localVue,
computed,
});
};
@@ -78,15 +76,14 @@ describe('noteActions', () => {
expect(findUserAccessRoleBadgeText(1)).toBe(props.accessLevel);
});
- it('should render contributor badge', () => {
+ it('should render contributor badge', async () => {
wrapper.setProps({
accessLevel: null,
isContributor: true,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findUserAccessRoleBadgeText(1)).toBe('Contributor');
- });
+ await nextTick();
+ expect(findUserAccessRoleBadgeText(1)).toBe('Contributor');
});
it('should render emoji link', () => {
@@ -107,7 +104,7 @@ describe('noteActions', () => {
expect(wrapper.find('.js-btn-copy-note-link').exists()).toBe(true);
});
- it('should not show copy link action when `noteUrl` prop is empty', (done) => {
+ it('should not show copy link action when `noteUrl` prop is empty', async () => {
wrapper.setProps({
...props,
author: {
@@ -121,30 +118,23 @@ describe('noteActions', () => {
noteUrl: '',
});
- Vue.nextTick()
- .then(() => {
- expect(wrapper.find('.js-btn-copy-note-link').exists()).toBe(false);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(wrapper.find('.js-btn-copy-note-link').exists()).toBe(false);
});
it('should be possible to delete comment', () => {
expect(wrapper.find('.js-note-delete').exists()).toBe(true);
});
- it('closes tooltip when dropdown opens', (done) => {
+ it('closes tooltip when dropdown opens', async () => {
wrapper.find('.more-actions-toggle').trigger('click');
const rootWrapper = createWrapper(wrapper.vm.$root);
- Vue.nextTick()
- .then(() => {
- const emitted = Object.keys(rootWrapper.emitted());
-
- expect(emitted).toEqual([BV_HIDE_TOOLTIP]);
- done();
- })
- .catch(done.fail);
+
+ await nextTick();
+ const emitted = Object.keys(rootWrapper.emitted());
+
+ expect(emitted).toEqual([BV_HIDE_TOOLTIP]);
});
it('should not be possible to assign or unassign the comment author in a merge request', () => {
diff --git a/spec/frontend/notes/components/note_body_spec.js b/spec/frontend/notes/components/note_body_spec.js
index 4e345c9ac8d..63f3cd865d5 100644
--- a/spec/frontend/notes/components/note_body_spec.js
+++ b/spec/frontend/notes/components/note_body_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { suggestionCommitMessage } from '~/diffs/store/getters';
@@ -46,9 +46,9 @@ describe('issue_note_body component', () => {
});
describe('isEditing', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
vm.isEditing = true;
- Vue.nextTick(done);
+ await nextTick();
});
it('renders edit form', () => {
diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js
index d3b5ab02f24..3e80b24f128 100644
--- a/spec/frontend/notes/components/note_form_spec.js
+++ b/spec/frontend/notes/components/note_form_spec.js
@@ -45,6 +45,8 @@ describe('issue_note_form component', () => {
noteBody: 'Magni suscipit eius consectetur enim et ex et commodi.',
noteId: '545',
};
+
+ gon.features = { markdownContinueLists: true };
});
afterEach(() => {
diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js
index 774d5aaa7d3..8d82cf3d2c7 100644
--- a/spec/frontend/notes/components/note_header_spec.js
+++ b/spec/frontend/notes/components/note_header_spec.js
@@ -1,13 +1,12 @@
import { GlSprintf } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import NoteHeader from '~/notes/components/note_header.vue';
import { AVAILABILITY_STATUS } from '~/set_status_modal/utils';
import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
const actions = {
setTargetNoteHash: jest.fn(),
@@ -42,7 +41,6 @@ describe('NoteHeader component', () => {
const createComponent = (props) => {
wrapper = shallowMount(NoteHeader, {
- localVue,
store: new Vuex.Store({
actions,
}),
diff --git a/spec/frontend/notes/components/noteable_note_spec.js b/spec/frontend/notes/components/noteable_note_spec.js
index 038aff3be04..c7115a5911b 100644
--- a/spec/frontend/notes/components/noteable_note_spec.js
+++ b/spec/frontend/notes/components/noteable_note_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 waitForPromises from 'helpers/wait_for_promises';
@@ -107,11 +107,11 @@ describe('issue_note', () => {
line,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findMultilineComment().text()).toBe('Comment on lines 1 to 2');
});
- it('should only render if it has everything it needs', () => {
+ it('should only render if it has everything it needs', async () => {
const position = {
line_range: {
start: {
@@ -140,12 +140,11 @@ describe('issue_note', () => {
line,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findMultilineComment().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findMultilineComment().exists()).toBe(false);
});
- it('should not render if has single line comment', () => {
+ it('should not render if has single line comment', async () => {
const position = {
line_range: {
start: {
@@ -174,9 +173,8 @@ describe('issue_note', () => {
line,
});
- return wrapper.vm.$nextTick().then(() => {
- expect(findMultilineComment().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findMultilineComment().exists()).toBe(false);
});
it('should not render if `line_range` is unavailable', () => {
@@ -204,7 +202,7 @@ describe('issue_note', () => {
line,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.findComponent(UserAvatarLink).props('imgSize')).toBe(24);
});
@@ -318,13 +316,13 @@ describe('issue_note', () => {
callback: () => {},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
let noteBodyProps = noteBody.props();
expect(noteBodyProps.note.note_html).toBe(`<p>${updatedText}</p>\n`);
noteBody.vm.$emit('cancelForm', {});
- await wrapper.vm.$nextTick();
+ await nextTick();
noteBodyProps = noteBody.props();
diff --git a/spec/frontend/notes/components/notes_app_spec.js b/spec/frontend/notes/components/notes_app_spec.js
index 84d94857fe5..bf36d6cb7a2 100644
--- a/spec/frontend/notes/components/notes_app_spec.js
+++ b/spec/frontend/notes/components/notes_app_spec.js
@@ -1,7 +1,7 @@
import { mount, shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
-import Vue from 'vue';
+import { nextTick } from 'vue';
import setWindowLocation from 'helpers/set_window_location_helper';
import { setTestTimeout } from 'helpers/timeout';
import waitForPromises from 'helpers/wait_for_promises';
@@ -294,24 +294,22 @@ describe('note_app', () => {
return waitForDiscussionsRequest();
});
- it('should render markdown docs url', () => {
+ it('should render markdown docs url', async () => {
wrapper.find('.js-note-edit').trigger('click');
const { markdownDocsPath } = mockData.notesDataMock;
- return Vue.nextTick().then(() => {
- expect(wrapper.find(`.edit-note a[href="${markdownDocsPath}"]`).text().trim()).toEqual(
- 'Markdown is supported',
- );
- });
+ await nextTick();
+ expect(wrapper.find(`.edit-note a[href="${markdownDocsPath}"]`).text().trim()).toEqual(
+ 'Markdown is supported',
+ );
});
- it('should not render quick actions docs url', () => {
+ it('should not render quick actions docs url', async () => {
wrapper.find('.js-note-edit').trigger('click');
const { quickActionsDocsPath } = mockData.notesDataMock;
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(`.edit-note a[href="${quickActionsDocsPath}"]`).exists()).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.find(`.edit-note a[href="${quickActionsDocsPath}"]`).exists()).toBe(false);
});
});
diff --git a/spec/frontend/notes/components/sort_discussion_spec.js b/spec/frontend/notes/components/sort_discussion_spec.js
index 60f03a0f5b5..a279dfd1ef3 100644
--- a/spec/frontend/notes/components/sort_discussion_spec.js
+++ b/spec/frontend/notes/components/sort_discussion_spec.js
@@ -1,4 +1,5 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import SortDiscussion from '~/notes/components/sort_discussion.vue';
import { ASC, DESC } from '~/notes/constants';
@@ -6,8 +7,7 @@ import createStore from '~/notes/stores';
import Tracking from '~/tracking';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Sort Discussion component', () => {
let wrapper;
@@ -17,7 +17,6 @@ describe('Sort Discussion component', () => {
jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = shallowMount(SortDiscussion, {
- localVue,
store,
});
};
diff --git a/spec/frontend/notes/components/timeline_toggle_spec.js b/spec/frontend/notes/components/timeline_toggle_spec.js
index 73fb2079e31..84fa3008835 100644
--- a/spec/frontend/notes/components/timeline_toggle_spec.js
+++ b/spec/frontend/notes/components/timeline_toggle_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import TimelineToggle, {
timelineEnabledTooltip,
@@ -10,8 +11,7 @@ import createStore from '~/notes/stores';
import { trackToggleTimelineView } from '~/notes/utils';
import Tracking from '~/tracking';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Timeline toggle', () => {
let wrapper;
@@ -23,7 +23,6 @@ describe('Timeline toggle', () => {
jest.spyOn(Tracking, 'event').mockImplementation();
wrapper = shallowMount(TimelineToggle, {
- localVue,
store,
});
};
@@ -65,7 +64,7 @@ describe('Timeline toggle', () => {
it('should set correct UI state', async () => {
store.state.isTimelineEnabled = true;
findGlButton().vm.$emit('click', mockEvent);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findGlButton().attributes('title')).toBe(timelineEnabledTooltip);
expect(findGlButton().attributes('selected')).toBe('true');
expect(mockEvent.currentTarget.blur).toHaveBeenCalled();
@@ -73,7 +72,7 @@ describe('Timeline toggle', () => {
it('should track Snowplow event', async () => {
store.state.isTimelineEnabled = true;
- await wrapper.vm.$nextTick();
+ await nextTick();
findGlButton().trigger('click');
@@ -98,7 +97,7 @@ describe('Timeline toggle', () => {
it('should set correct UI state', async () => {
store.state.isTimelineEnabled = false;
findGlButton().vm.$emit('click', mockEvent);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findGlButton().attributes('title')).toBe(timelineDisabledTooltip);
expect(findGlButton().attributes('selected')).toBe(undefined);
expect(mockEvent.currentTarget.blur).toHaveBeenCalled();
@@ -106,7 +105,7 @@ describe('Timeline toggle', () => {
it('should track Snowplow event', async () => {
store.state.isTimelineEnabled = false;
- await wrapper.vm.$nextTick();
+ await nextTick();
findGlButton().trigger('click');
diff --git a/spec/frontend/notes/deprecated_notes_spec.js b/spec/frontend/notes/deprecated_notes_spec.js
index 34623f8aa13..7c52920da90 100644
--- a/spec/frontend/notes/deprecated_notes_spec.js
+++ b/spec/frontend/notes/deprecated_notes_spec.js
@@ -5,6 +5,7 @@ import $ from 'jquery';
import '~/behaviors/markdown/render_gfm';
import { createSpyObj } from 'helpers/jest_helpers';
import { TEST_HOST } from 'helpers/test_constants';
+import waitForPromises from 'helpers/wait_for_promises';
import { setTestTimeoutOnce } from 'helpers/timeout';
import axios from '~/lib/utils/axios_utils';
import * as urlUtility from '~/lib/utils/url_utility';
@@ -549,15 +550,14 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => {
expect($notesContainer.find('.note.being-posted').length).toBeGreaterThan(0);
});
- it('should remove placeholder note when new comment is done posting', (done) => {
+ it('should remove placeholder note when new comment is done posting', async () => {
mockNotesPost();
$('.js-comment-button').click();
- setImmediate(() => {
- expect($notesContainer.find('.note.being-posted').length).toEqual(0);
- done();
- });
+ await waitForPromises();
+
+ expect($notesContainer.find('.note.being-posted').length).toEqual(0);
});
describe('postComment', () => {
@@ -584,40 +584,37 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => {
});
});
- it('should show actual note element when new comment is done posting', (done) => {
+ it('should show actual note element when new comment is done posting', async () => {
mockNotesPost();
$('.js-comment-button').click();
- setImmediate(() => {
- expect($notesContainer.find(`#note_${note.id}`).length).toBeGreaterThan(0);
- done();
- });
+ await waitForPromises();
+
+ expect($notesContainer.find(`#note_${note.id}`).length).toBeGreaterThan(0);
});
- it('should reset Form when new comment is done posting', (done) => {
+ it('should reset Form when new comment is done posting', async () => {
mockNotesPost();
$('.js-comment-button').click();
- setImmediate(() => {
- expect($form.find('textarea.js-note-text').val()).toEqual('');
- done();
- });
+ await waitForPromises();
+
+ expect($form.find('textarea.js-note-text').val()).toEqual('');
});
- it('should show flash error message when new comment failed to be posted', (done) => {
+ it('should show flash error message when new comment failed to be posted', async () => {
mockNotesPostError();
jest.spyOn(notes, 'addFlash');
$('.js-comment-button').click();
- setImmediate(() => {
- expect(notes.addFlash).toHaveBeenCalled();
- // JSDom doesn't support the :visible selector yet
- expect(notes.flashContainer.style.display).not.toBe('none');
- done();
- });
+ await waitForPromises();
+
+ expect(notes.addFlash).toHaveBeenCalled();
+ // JSDom doesn't support the :visible selector yet
+ expect(notes.flashContainer.style.display).not.toBe('none');
});
});
@@ -657,16 +654,15 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => {
$form.find('textarea.js-note-text').val(sampleComment);
});
- it('should remove quick action placeholder when comment with quick actions is done posting', (done) => {
+ it('should remove quick action placeholder when comment with quick actions is done posting', async () => {
jest.spyOn(gl.awardsHandler, 'addAwardToEmojiBar');
$('.js-comment-button').click();
expect($notesContainer.find('.note.being-posted').length).toEqual(1); // Placeholder shown
- setImmediate(() => {
- expect($notesContainer.find('.note.being-posted').length).toEqual(0); // Placeholder removed
- done();
- });
+ await waitForPromises();
+
+ expect($notesContainer.find('.note.being-posted').length).toEqual(0); // Placeholder removed
});
});
@@ -692,16 +688,15 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => {
$form.find('textarea.js-note-text').val(sampleComment);
});
- it('should show message placeholder including lines starting with slash', (done) => {
+ it('should show message placeholder including lines starting with slash', async () => {
$('.js-comment-button').click();
expect($notesContainer.find('.note.being-posted').length).toEqual(1); // Placeholder shown
expect($notesContainer.find('.note-body p').text()).toEqual(sampleComment); // No quick action processing
- setImmediate(() => {
- expect($notesContainer.find('.note.being-posted').length).toEqual(0); // Placeholder removed
- done();
- });
+ await waitForPromises();
+
+ expect($notesContainer.find('.note.being-posted').length).toEqual(0); // Placeholder removed
});
});
@@ -730,23 +725,21 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => {
$form.find('textarea.js-note-text').html(sampleComment);
});
- it('should not render a script tag', (done) => {
+ it('should not render a script tag', async () => {
$('.js-comment-button').click();
- setImmediate(() => {
- const $noteEl = $notesContainer.find(`#note_${note.id}`);
- $noteEl.find('.js-note-edit').click();
- $noteEl.find('textarea.js-note-text').html(updatedComment);
- $noteEl.find('.js-comment-save-button').click();
+ await waitForPromises();
- const $updatedNoteEl = $notesContainer
- .find(`#note_${note.id}`)
- .find('.js-task-list-container');
+ const $noteEl = $notesContainer.find(`#note_${note.id}`);
+ $noteEl.find('.js-note-edit').click();
+ $noteEl.find('textarea.js-note-text').html(updatedComment);
+ $noteEl.find('.js-comment-save-button').click();
- expect($updatedNoteEl.find('.note-text').text().trim()).toEqual('');
+ const $updatedNoteEl = $notesContainer
+ .find(`#note_${note.id}`)
+ .find('.js-task-list-container');
- done();
- });
+ expect($updatedNoteEl.find('.note-text').text().trim()).toEqual('');
});
});
diff --git a/spec/frontend/notes/mixins/discussion_navigation_spec.js b/spec/frontend/notes/mixins/discussion_navigation_spec.js
index 26a072b82f8..aba80789a01 100644
--- a/spec/frontend/notes/mixins/discussion_navigation_spec.js
+++ b/spec/frontend/notes/mixins/discussion_navigation_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { setHTMLFixture } from 'helpers/fixtures';
import createEventHub from '~/helpers/event_hub_factory';
@@ -27,8 +27,7 @@ const createComponent = () => ({
});
describe('Discussion navigation mixin', () => {
- const localVue = createLocalVue();
- localVue.use(Vuex);
+ Vue.use(Vuex);
let wrapper;
let store;
@@ -65,7 +64,7 @@ describe('Discussion navigation mixin', () => {
});
store.state.notes.discussions = createDiscussions();
- wrapper = shallowMount(createComponent(), { store, localVue });
+ wrapper = shallowMount(createComponent(), { store });
});
afterEach(() => {
@@ -94,14 +93,13 @@ describe('Discussion navigation mixin', () => {
expect(store.dispatch).toHaveBeenCalledWith('setCurrentDiscussionId', null);
});
- it('triggers the jumpToNextDiscussion action when the previous store action succeeds', () => {
+ it('triggers the jumpToNextDiscussion action when the previous store action succeeds', async () => {
store.dispatch.mockResolvedValue();
vm.jumpToFirstUnresolvedDiscussion();
- return vm.$nextTick().then(() => {
- expect(vm.jumpToNextDiscussion).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(vm.jumpToNextDiscussion).toHaveBeenCalled();
});
});
@@ -127,11 +125,11 @@ describe('Discussion navigation mixin', () => {
});
describe('on `show` active tab', () => {
- beforeEach(() => {
+ beforeEach(async () => {
window.mrTabs.currentAction = 'show';
wrapper.vm[fn](...args);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('sets current discussion', () => {
@@ -145,17 +143,17 @@ describe('Discussion navigation mixin', () => {
it('scrolls to element', () => {
expect(utils.scrollToElement).toHaveBeenCalledWith(
findDiscussion('div.discussion', expected),
- { behavior: 'smooth' },
+ { behavior: 'auto' },
);
});
});
describe('on `diffs` active tab', () => {
- beforeEach(() => {
+ beforeEach(async () => {
window.mrTabs.currentAction = 'diffs';
wrapper.vm[fn](...args);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('sets current discussion', () => {
@@ -173,17 +171,17 @@ describe('Discussion navigation mixin', () => {
expect(utils.scrollToElementWithContext).toHaveBeenCalledWith(
findDiscussion('ul.notes', expected),
- { behavior: 'smooth' },
+ { behavior: 'auto' },
);
});
});
describe('on `other` active tab', () => {
- beforeEach(() => {
+ beforeEach(async () => {
window.mrTabs.currentAction = 'other';
wrapper.vm[fn](...args);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('sets current discussion', () => {
@@ -214,21 +212,15 @@ describe('Discussion navigation mixin', () => {
it('scrolls to discussion', () => {
expect(utils.scrollToElement).toHaveBeenCalledWith(
findDiscussion('div.discussion', expected),
- { behavior: 'smooth' },
+ { behavior: 'auto' },
);
});
});
});
});
- describe.each`
- diffsVirtualScrolling
- ${false}
- ${true}
- `('virtual scrolling feature is $diffsVirtualScrolling', ({ diffsVirtualScrolling }) => {
+ describe('virtual scrolling feature', () => {
beforeEach(() => {
- window.gon = { features: { diffsVirtualScrolling } };
-
jest.spyOn(store, 'dispatch');
store.state.notes.currentDiscussionId = 'a';
@@ -240,22 +232,22 @@ describe('Discussion navigation mixin', () => {
window.location.hash = '';
});
- it('resets location hash if diffsVirtualScrolling flag is true', async () => {
+ it('resets location hash', async () => {
wrapper.vm.jumpToNextDiscussion();
await nextTick();
- expect(window.location.hash).toBe(diffsVirtualScrolling ? '' : '#test');
+ expect(window.location.hash).toBe('');
});
it.each`
- tabValue | hashValue
- ${'diffs'} | ${false}
- ${'show'} | ${!diffsVirtualScrolling}
- ${'other'} | ${!diffsVirtualScrolling}
+ tabValue
+ ${'diffs'}
+ ${'show'}
+ ${'other'}
`(
'calls scrollToFile with setHash as $hashValue when the tab is $tabValue',
- async ({ hashValue, tabValue }) => {
+ async ({ tabValue }) => {
window.mrTabs.currentAction = tabValue;
wrapper.vm.jumpToNextDiscussion();
@@ -264,7 +256,6 @@ describe('Discussion navigation mixin', () => {
expect(store.dispatch).toHaveBeenCalledWith('diffs/scrollToFile', {
path: 'test.js',
- setHash: hashValue,
});
},
);
diff --git a/spec/frontend/notifications/components/custom_notifications_modal_spec.js b/spec/frontend/notifications/components/custom_notifications_modal_spec.js
index 7a036d25559..c5d201c3aec 100644
--- a/spec/frontend/notifications/components/custom_notifications_modal_spec.js
+++ b/spec/frontend/notifications/components/custom_notifications_modal_spec.js
@@ -2,6 +2,7 @@ import { GlSprintf, GlModal, GlFormGroup, GlFormCheckbox, GlLoadingIcon } from '
import { 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 httpStatus from '~/lib/utils/http_status';
@@ -97,7 +98,7 @@ describe('CustomNotificationsModal', () => {
],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it.each`
@@ -222,7 +223,7 @@ describe('CustomNotificationsModal', () => {
],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
findCheckboxAt(1).vm.$emit('change', true);
@@ -252,7 +253,7 @@ describe('CustomNotificationsModal', () => {
],
});
- await wrapper.vm.$nextTick();
+ await nextTick();
findCheckboxAt(1).vm.$emit('change', true);
diff --git a/spec/frontend/notifications/components/notifications_dropdown_spec.js b/spec/frontend/notifications/components/notifications_dropdown_spec.js
index e12251ce6d9..7ca6c2052ae 100644
--- a/spec/frontend/notifications/components/notifications_dropdown_spec.js
+++ b/spec/frontend/notifications/components/notifications_dropdown_spec.js
@@ -195,6 +195,14 @@ describe('NotificationsDropdown', () => {
);
});
});
+
+ it('passes provided `noFlip` value to `GlDropdown`', () => {
+ wrapper = createComponent({
+ noFlip: true,
+ });
+
+ expect(findDropdown().attributes('no-flip')).toBe('true');
+ });
});
describe('when selecting an item', () => {
diff --git a/spec/frontend/operation_settings/components/metrics_settings_spec.js b/spec/frontend/operation_settings/components/metrics_settings_spec.js
index 258c6eae692..c1fa1d24a82 100644
--- a/spec/frontend/operation_settings/components/metrics_settings_spec.js
+++ b/spec/frontend/operation_settings/components/metrics_settings_spec.js
@@ -1,5 +1,6 @@
import { GlButton, GlLink, GlFormGroup, GlFormInput, GlFormSelect } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
@@ -181,17 +182,18 @@ describe('operation settings external dashboard component', () => {
expect(submit.text()).toBe('Save Changes');
});
- it('submits form on click', () => {
+ it('submits form on click', async () => {
mountComponent(false);
axios.patch.mockResolvedValue();
findSubmitButton().trigger('click');
expect(axios.patch).toHaveBeenCalledWith(...endpointRequest);
- return wrapper.vm.$nextTick().then(() => expect(refreshCurrentPage).toHaveBeenCalled());
+ await nextTick();
+ expect(refreshCurrentPage).toHaveBeenCalled();
});
- it('creates flash banner on error', () => {
+ it('creates flash banner on error', async () => {
mountComponent(false);
const message = 'mockErrorMessage';
axios.patch.mockRejectedValue({ response: { data: { message } } });
@@ -199,14 +201,11 @@ describe('operation settings external dashboard component', () => {
expect(axios.patch).toHaveBeenCalledWith(...endpointRequest);
- return wrapper.vm
- .$nextTick()
- .then(jest.runAllTicks)
- .then(() =>
- expect(createFlash).toHaveBeenCalledWith({
- message: `There was an error saving your changes. ${message}`,
- }),
- );
+ await nextTick();
+ await jest.runAllTicks();
+ expect(createFlash).toHaveBeenCalledWith({
+ message: `There was an error saving your changes. ${message}`,
+ });
});
});
});
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js
index 5278e730ec9..f4c22d9bfa7 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js
@@ -1,6 +1,7 @@
import { GlDropdownItem, GlIcon, GlDropdown } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
+import { nextTick } from 'vue';
import { useFakeDate } from 'helpers/fake_date';
import createMockApollo from 'helpers/mock_apollo_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
@@ -54,8 +55,8 @@ describe('Details Header', () => {
const waitForMetadataItems = async () => {
// Metadata items are printed by a loop in the title-area and it takes two ticks for them to be available
- await wrapper.vm.$nextTick();
- await wrapper.vm.$nextTick();
+ await nextTick();
+ await nextTick();
};
const mountComponent = ({
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js
index 0dcf988c814..ef6c4a1fa32 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import { GlEmptyState } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -8,7 +8,7 @@ import { stripTypenames } from 'helpers/graphql_helpers';
import component from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue';
import TagsListRow from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue';
-import TagsLoader from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_loader.vue';
+import TagsLoader from '~/packages_and_registries/shared/components/tags_loader.vue';
import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue';
import getContainerRepositoryTagsQuery from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql';
@@ -22,8 +22,6 @@ import {
import { FILTERED_SEARCH_TERM } from '~/packages_and_registries/shared/constants';
import { tagsMock, imageTagsMock, tagsPageInfo } from '../../mock_data';
-const localVue = createLocalVue();
-
describe('Tags List', () => {
let wrapper;
let apolloProvider;
@@ -50,13 +48,12 @@ describe('Tags List', () => {
};
const mountComponent = ({ propsData = { isMobile: false, id: 1 } } = {}) => {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const requestHandlers = [[getContainerRepositoryTagsQuery, resolver]];
apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMount(component, {
- localVue,
apolloProvider,
propsData,
stubs: { RegistryList },
@@ -108,6 +105,7 @@ describe('Tags List', () => {
describe('events', () => {
it('prev-page fetch the previous page', async () => {
findRegistryList().vm.$emit('prev-page');
+ await waitForPromises();
expect(resolver).toHaveBeenCalledWith({
first: null,
@@ -119,8 +117,9 @@ describe('Tags List', () => {
});
});
- it('next-page fetch the previous page', () => {
+ it('next-page fetch the previous page', async () => {
findRegistryList().vm.$emit('next-page');
+ await waitForPromises();
expect(resolver).toHaveBeenCalledWith({
after: tagsPageInfo.endCursor,
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js
index 060dc9dc5f3..e5df260a260 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import component from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_loader.vue';
+import component from '~/packages_and_registries/shared/components/tags_loader.vue';
import { GlSkeletonLoader } from '../../stubs';
describe('TagsLoader component', () => {
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cli_commands_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cli_commands_spec.js
index 4039fba869b..7727bf167fe 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cli_commands_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cli_commands_spec.js
@@ -1,7 +1,8 @@
import { GlDropdown } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
-import QuickstartDropdown from '~/packages_and_registries/container_registry/explorer/components/list_page/cli_commands.vue';
+import QuickstartDropdown from '~/packages_and_registries/shared/components/cli_commands.vue';
import {
QUICK_START,
LOGIN_COMMAND_LABEL,
@@ -16,28 +17,18 @@ import CodeInstruction from '~/vue_shared/components/registry/code_instruction.v
import { dockerCommands } from '../../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('cli_commands', () => {
let wrapper;
- const config = {
- repositoryUrl: 'foo',
- registryHostUrlWithPort: 'bar',
- };
-
const findDropdownButton = () => wrapper.find(GlDropdown);
const findCodeInstruction = () => wrapper.findAll(CodeInstruction);
const mountComponent = () => {
wrapper = mount(QuickstartDropdown, {
- localVue,
- provide() {
- return {
- config,
- ...dockerCommands,
- };
+ propsData: {
+ ...dockerCommands,
},
});
};
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state_spec.js
index 027cdf732bc..d2086943e4f 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state_spec.js
@@ -1,11 +1,11 @@
import { GlSprintf } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import groupEmptyState from '~/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state.vue';
import { GlEmptyState } from '../../stubs';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Registry Group Empty state', () => {
let wrapper;
@@ -16,7 +16,6 @@ describe('Registry Group Empty state', () => {
beforeEach(() => {
wrapper = shallowMount(groupEmptyState, {
- localVue,
stubs: {
GlEmptyState,
GlSprintf,
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state_spec.js
index 21748ae2813..8cfa8128021 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state_spec.js
@@ -1,12 +1,12 @@
import { GlSprintf } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import projectEmptyState from '~/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state.vue';
import { dockerCommands } from '../../mock_data';
import { GlEmptyState } from '../../stubs';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Registry Project Empty state', () => {
let wrapper;
@@ -21,7 +21,6 @@ describe('Registry Project Empty state', () => {
beforeEach(() => {
wrapper = shallowMount(projectEmptyState, {
- localVue,
stubs: {
GlEmptyState,
GlSprintf,
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js
index 92cfeb7633e..c91a9c0f0fb 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/registry_header_spec.js
@@ -1,5 +1,6 @@
import { GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import Component from '~/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue';
import {
CONTAINER_REGISTRY_TITLE,
@@ -21,7 +22,7 @@ describe('registry_header', () => {
const findImagesCountSubHeader = () => wrapper.find('[data-testid="images-count"]');
const findExpirationPolicySubHeader = () => wrapper.find('[data-testid="expiration-policy"]');
- const mountComponent = (propsData, slots) => {
+ const mountComponent = async (propsData, slots) => {
wrapper = shallowMount(Component, {
stubs: {
GlSprintf,
@@ -30,7 +31,7 @@ describe('registry_header', () => {
propsData,
slots,
});
- return wrapper.vm.$nextTick();
+ await nextTick();
};
afterEach(() => {
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js
index 7992bead60a..c602b37c3b5 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js
@@ -1,7 +1,8 @@
import { GlKeysetPagination, GlEmptyState } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-import { nextTick } from 'vue';
+
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
@@ -11,7 +12,7 @@ import DetailsHeader from '~/packages_and_registries/container_registry/explorer
import PartialCleanupAlert from '~/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert.vue';
import StatusAlert from '~/packages_and_registries/container_registry/explorer/components/details_page/status_alert.vue';
import TagsList from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue';
-import TagsLoader from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_loader.vue';
+import TagsLoader from '~/packages_and_registries/shared/components/tags_loader.vue';
import {
UNFINISHED_STATUS,
@@ -39,8 +40,6 @@ import {
} from '../mock_data';
import { DeleteModal } from '../stubs';
-const localVue = createLocalVue();
-
describe('Details Page', () => {
let wrapper;
let apolloProvider;
@@ -85,7 +84,7 @@ describe('Details Page', () => {
options,
config = defaultConfig,
} = {}) => {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const requestHandlers = [
[getContainerRepositoryDetailsQuery, resolver],
@@ -96,7 +95,6 @@ describe('Details Page', () => {
apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMount(component, {
- localVue,
apolloProvider,
stubs: {
DeleteModal,
@@ -522,7 +520,7 @@ describe('Details Page', () => {
findDeleteImage().vm.$emit('start');
- await nextTick();
+ await waitForPromises();
expect(findTagsLoader().exists()).toBe(true);
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js
index 051d1e2a169..bd126fe532d 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js
@@ -1,6 +1,7 @@
import { GlSkeletonLoader, GlSprintf, GlAlert } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -8,7 +9,7 @@ import getContainerRepositoriesQuery from 'shared_queries/container_registry/get
import CleanupPolicyEnabledAlert from '~/packages_and_registries/shared/components/cleanup_policy_enabled_alert.vue';
import { FILTERED_SEARCH_TERM } from '~/packages_and_registries/shared/constants';
import DeleteImage from '~/packages_and_registries/container_registry/explorer/components/delete_image.vue';
-import CliCommands from '~/packages_and_registries/container_registry/explorer/components/list_page/cli_commands.vue';
+import CliCommands from '~/packages_and_registries/shared/components/cli_commands.vue';
import GroupEmptyState from '~/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state.vue';
import ImageList from '~/packages_and_registries/container_registry/explorer/components/list_page/image_list.vue';
import ProjectEmptyState from '~/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state.vue';
@@ -38,8 +39,6 @@ import {
} from '../mock_data';
import { GlModal, GlEmptyState } from '../stubs';
-const localVue = createLocalVue();
-
describe('List Page', () => {
let wrapper;
let apolloProvider;
@@ -75,7 +74,7 @@ describe('List Page', () => {
config = { isGroupPage: false },
query = {},
} = {}) => {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const requestHandlers = [
[getContainerRepositoriesQuery, resolver],
@@ -86,7 +85,6 @@ describe('List Page', () => {
apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMount(component, {
- localVue,
apolloProvider,
stubs: {
GlModal,
@@ -307,15 +305,8 @@ describe('List Page', () => {
await selectImageForDeletion();
findDeleteModal().vm.$emit('primary');
- await waitForApolloRequestRender();
-
- expect(wrapper.vm.itemToDelete).toEqual(deletedContainerRepository);
-
- const updatedImage = findImageList()
- .props('images')
- .find((i) => i.id === deletedContainerRepository.id);
- expect(updatedImage.status).toBe(deletedContainerRepository.status);
+ expect(mutationResolver).toHaveBeenCalledWith({ id: deletedContainerRepository.id });
});
it('should show a success alert when delete request is successful', async () => {
@@ -361,7 +352,7 @@ describe('List Page', () => {
findRegistrySearch().vm.$emit('filter:submit');
- await nextTick();
+ await waitForPromises();
};
it('has a search box element', async () => {
@@ -429,7 +420,7 @@ describe('List Page', () => {
await waitForApolloRequestRender();
findImageList().vm.$emit('prev-page');
- await nextTick();
+ await waitForPromises();
expect(resolver).toHaveBeenCalledWith(
expect.objectContaining({ before: pageInfo.startCursor }),
@@ -449,7 +440,7 @@ describe('List Page', () => {
await waitForApolloRequestRender();
findImageList().vm.$emit('next-page');
- await nextTick();
+ await waitForPromises();
expect(resolver).toHaveBeenCalledWith(
expect.objectContaining({ after: pageInfo.endCursor }),
@@ -471,8 +462,9 @@ describe('List Page', () => {
});
it('contains a description with the path of the item to delete', async () => {
+ await waitForPromises();
findImageList().vm.$emit('delete', { path: 'foo' });
- await nextTick();
+ await waitForPromises();
expect(findDeleteModal().html()).toContain('foo');
});
});
diff --git a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
index 44a7186904d..79894e25889 100644
--- a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
+++ b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
@@ -5,7 +5,7 @@ import {
GlSprintf,
GlEmptyState,
} from '@gitlab/ui';
-import { createLocalVue } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -21,8 +21,6 @@ import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency
import { proxyDetailsQuery, proxyData, pagination, proxyManifests } from './mock_data';
-const localVue = createLocalVue();
-
describe('DependencyProxyApp', () => {
let wrapper;
let apolloProvider;
@@ -35,14 +33,13 @@ describe('DependencyProxyApp', () => {
};
function createComponent({ provide = provideDefaults } = {}) {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const requestHandlers = [[getDependencyProxyDetailsQuery, resolver]];
apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMountExtended(DependencyProxyApp, {
- localVue,
apolloProvider,
provide,
stubs: {
@@ -195,8 +192,9 @@ describe('DependencyProxyApp', () => {
});
});
- it('prev-page event on list fetches the previous page', () => {
+ it('prev-page event on list fetches the previous page', async () => {
findManifestList().vm.$emit('prev-page');
+ await waitForPromises();
expect(resolver).toHaveBeenCalledWith({
before: pagination().startCursor,
@@ -206,8 +204,9 @@ describe('DependencyProxyApp', () => {
});
});
- it('next-page event on list fetches the next page', () => {
+ it('next-page event on list fetches the next page', async () => {
findManifestList().vm.$emit('next-page');
+ await waitForPromises();
expect(resolver).toHaveBeenCalledWith({
after: pagination().endCursor,
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js
index 2868af84181..69c78e64e22 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js
@@ -1,6 +1,6 @@
import { GlEmptyState } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import stubChildren from 'helpers/stub_children';
@@ -19,8 +19,7 @@ import Tracking from '~/tracking';
import { mavenPackage, mavenFiles, npmPackage } from '../../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
useMockLocationHelper();
@@ -60,7 +59,6 @@ describe('PackagesApp', () => {
});
wrapper = mount(PackagesApp, {
- localVue,
store,
stubs: {
...stubChildren(PackagesApp),
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js
index 24bd80ba80c..b504f7489ab 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js
@@ -1,17 +1,20 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import component from '~/packages_and_registries/infrastructure_registry/details/components/details_title.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import { terraformModule, mavenFiles, npmPackage } from '../../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('PackageTitle', () => {
let wrapper;
let store;
- function createComponent({ packageFiles = mavenFiles, packageEntity = terraformModule } = {}) {
+ async function createComponent({
+ packageFiles = mavenFiles,
+ packageEntity = terraformModule,
+ } = {}) {
store = new Vuex.Store({
state: {
packageEntity,
@@ -23,13 +26,12 @@ describe('PackageTitle', () => {
});
wrapper = shallowMount(component, {
- localVue,
store,
stubs: {
TitleArea,
},
});
- return wrapper.vm.$nextTick();
+ await nextTick();
}
const findTitleArea = () => wrapper.findComponent(TitleArea);
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js
index 6ff4a4c51ef..78c1b840dbc 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js
@@ -1,11 +1,11 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/details/components/terraform_installation.vue';
import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
import { terraformModule as packageEntity } from '../../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('TerraformInstallation', () => {
let wrapper;
@@ -22,7 +22,6 @@ describe('TerraformInstallation', () => {
function createComponent() {
wrapper = shallowMount(TerraformInstallation, {
- localVue,
store,
});
}
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap
index 7cdf21dde46..d82af8f9e63 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap
@@ -3,6 +3,7 @@
exports[`packages_list_app renders 1`] = `
<div>
<infrastructure-title-stub
+ count="1"
helpurl="foo"
/>
@@ -37,8 +38,8 @@ exports[`packages_list_app renders 1`] = `
class="gl-font-size-h-display gl-line-height-36 h4"
>
- There are no packages yet
-
+ There are no packages yet
+
</h1>
<p
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js
index b519ab00d06..e5230417c78 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js
@@ -1,11 +1,11 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import component from '~/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue';
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
import UrlSync from '~/vue_shared/components/url_sync.vue';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Infrastructure Search', () => {
let wrapper;
@@ -48,7 +48,6 @@ describe('Infrastructure Search', () => {
createStore(isGroupPage);
wrapper = shallowMount(component, {
- localVue,
store,
stubs: {
UrlSync,
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js
index b0e586f189a..72d08d5683b 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js
@@ -10,7 +10,9 @@ describe('Infrastructure Title', () => {
const findTitleArea = () => wrapper.find(TitleArea);
const findMetadataItem = () => wrapper.find(MetadataItem);
- const mountComponent = (propsData = { helpUrl: 'foo' }) => {
+ const exampleProps = { helpUrl: 'http://example.gitlab.com/help' };
+
+ const mountComponent = (propsData = exampleProps) => {
wrapper = shallowMount(component, {
store,
propsData,
@@ -26,23 +28,36 @@ describe('Infrastructure Title', () => {
});
describe('title area', () => {
- it('exists', () => {
+ beforeEach(() => {
mountComponent();
+ });
+ it('exists', () => {
expect(findTitleArea().exists()).toBe(true);
});
- it('has the correct props', () => {
- mountComponent();
+ it('has the correct title', () => {
+ expect(findTitleArea().props('title')).toBe('Infrastructure Registry');
+ });
+
+ describe('with no modules', () => {
+ it('has no info message', () => {
+ expect(findTitleArea().props('infoMessages')).toStrictEqual([]);
+ });
+ });
+
+ describe('with at least one module', () => {
+ beforeEach(() => {
+ mountComponent({ ...exampleProps, count: 1 });
+ });
- expect(findTitleArea().props()).toMatchObject({
- title: 'Infrastructure Registry',
- infoMessages: [
+ it('has an info message', () => {
+ expect(findTitleArea().props('infoMessages')).toStrictEqual([
{
text: 'Publish and share your modules. %{docLinkStart}More information%{docLinkEnd}',
- link: 'foo',
+ link: exampleProps.helpUrl,
},
- ],
+ ]);
});
});
});
@@ -51,15 +66,15 @@ describe('Infrastructure Title', () => {
count | exist | text
${null} | ${false} | ${''}
${undefined} | ${false} | ${''}
- ${0} | ${true} | ${'0 Modules'}
+ ${0} | ${false} | ${''}
${1} | ${true} | ${'1 Module'}
${2} | ${true} | ${'2 Modules'}
`('when count is $count metadata item', ({ count, exist, text }) => {
beforeEach(() => {
- mountComponent({ count, helpUrl: 'foo' });
+ mountComponent({ ...exampleProps, count });
});
- it(`is ${exist} that it exists`, () => {
+ it(exist ? 'exists' : 'does not exist', () => {
expect(findMetadataItem().exists()).toBe(exist);
});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js
index cad75d2a858..31616e0b2f5 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js
@@ -1,5 +1,6 @@
import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import setWindowLocation from 'helpers/set_window_location_helper';
import createFlash from '~/flash';
@@ -17,8 +18,7 @@ import InfrastructureSearch from '~/packages_and_registries/infrastructure_regis
jest.mock('~/lib/utils/common_utils');
jest.mock('~/flash');
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('packages_list_app', () => {
let wrapper;
@@ -35,7 +35,7 @@ describe('packages_list_app', () => {
const findListComponent = () => wrapper.find(PackageList);
const findInfrastructureSearch = () => wrapper.find(InfrastructureSearch);
- const createStore = (filter = []) => {
+ const createStore = ({ filter = [], packageCount = 0 } = {}) => {
store = new Vuex.Store({
state: {
isLoading: false,
@@ -46,6 +46,9 @@ describe('packages_list_app', () => {
packageHelpUrl: 'foo',
},
filter,
+ pagination: {
+ total: packageCount,
+ },
},
});
store.dispatch = jest.fn();
@@ -53,7 +56,6 @@ describe('packages_list_app', () => {
const mountComponent = (provide) => {
wrapper = shallowMount(PackageListApp, {
- localVue,
store,
stubs: {
GlEmptyState,
@@ -69,6 +71,7 @@ describe('packages_list_app', () => {
beforeEach(() => {
createStore();
jest.spyOn(packageUtils, 'getQueryParams').mockReturnValue({});
+ mountComponent();
});
afterEach(() => {
@@ -76,30 +79,26 @@ describe('packages_list_app', () => {
});
it('renders', () => {
+ createStore({ packageCount: 1 });
mountComponent();
+
expect(wrapper.element).toMatchSnapshot();
});
- it('call requestPackagesList on page:changed', () => {
- mountComponent();
- store.dispatch.mockClear();
-
+ it('calls requestPackagesList on page:changed', () => {
const list = findListComponent();
list.vm.$emit('page:changed', 1);
expect(store.dispatch).toHaveBeenCalledWith('requestPackagesList', { page: 1 });
});
- it('call requestDeletePackage on package:delete', () => {
- mountComponent();
-
+ it('calls requestDeletePackage on package:delete', () => {
const list = findListComponent();
list.vm.$emit('package:delete', 'foo');
+
expect(store.dispatch).toHaveBeenCalledWith('requestDeletePackage', 'foo');
});
- it('does call requestPackagesList only one time on render', () => {
- mountComponent();
-
+ it('calls requestPackagesList only once on render', () => {
expect(store.dispatch).toHaveBeenCalledTimes(3);
expect(store.dispatch).toHaveBeenNthCalledWith(1, 'setSorting', expect.any(Object));
expect(store.dispatch).toHaveBeenNthCalledWith(2, 'setFilter', expect.any(Array));
@@ -114,9 +113,12 @@ describe('packages_list_app', () => {
orderBy: 'created',
};
- it('calls setSorting with the query string based sorting', () => {
+ beforeEach(() => {
+ createStore();
jest.spyOn(packageUtils, 'getQueryParams').mockReturnValue(defaultQueryParamsMock);
+ });
+ it('calls setSorting with the query string based sorting', () => {
mountComponent();
expect(store.dispatch).toHaveBeenNthCalledWith(1, 'setSorting', {
@@ -126,8 +128,6 @@ describe('packages_list_app', () => {
});
it('calls setFilter with the query string based filters', () => {
- jest.spyOn(packageUtils, 'getQueryParams').mockReturnValue(defaultQueryParamsMock);
-
mountComponent();
expect(store.dispatch).toHaveBeenNthCalledWith(2, 'setFilter', [
@@ -151,8 +151,6 @@ describe('packages_list_app', () => {
describe('empty state', () => {
it('generate the correct empty list link', () => {
- mountComponent();
-
const link = findListComponent().find(GlLink);
expect(link.attributes('href')).toBe(emptyListHelpUrl);
@@ -160,8 +158,6 @@ describe('packages_list_app', () => {
});
it('includes the right content on the default tab', () => {
- mountComponent();
-
const heading = findEmptyState().find('h1');
expect(heading.text()).toBe('There are no packages yet');
@@ -170,7 +166,7 @@ describe('packages_list_app', () => {
describe('filter without results', () => {
beforeEach(() => {
- createStore([{ type: 'something' }]);
+ createStore({ filter: [{ type: 'something' }] });
mountComponent();
});
@@ -182,20 +178,30 @@ describe('packages_list_app', () => {
});
});
- describe('Search', () => {
- it('exists', () => {
- mountComponent();
-
- expect(findInfrastructureSearch().exists()).toBe(true);
+ describe('search', () => {
+ describe('with no packages', () => {
+ it('does not exist', () => {
+ expect(findInfrastructureSearch().exists()).toBe(false);
+ });
});
- it('on update fetches data from the store', () => {
- mountComponent();
- store.dispatch.mockClear();
+ describe('with packages', () => {
+ beforeEach(() => {
+ createStore({ packageCount: 1 });
+ mountComponent();
+ });
- findInfrastructureSearch().vm.$emit('update');
+ it('exists', () => {
+ expect(findInfrastructureSearch().exists()).toBe(true);
+ });
- expect(store.dispatch).toHaveBeenCalledWith('requestPackagesList');
+ it('on update fetches data from the store', () => {
+ store.dispatch.mockClear();
+
+ findInfrastructureSearch().vm.$emit('update');
+
+ expect(store.dispatch).toHaveBeenCalledWith('requestPackagesList');
+ });
});
});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js
index 26569f20e94..fed82653016 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js
@@ -1,5 +1,6 @@
import { GlTable, GlPagination, GlModal } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import { last } from 'lodash';
import Vuex from 'vuex';
import stubChildren from 'helpers/stub_children';
@@ -11,8 +12,7 @@ import { TRACK_CATEGORY } from '~/packages_and_registries/infrastructure_registr
import Tracking from '~/tracking';
import { packageList } from '../../mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('packages_list', () => {
let wrapper;
@@ -61,7 +61,6 @@ describe('packages_list', () => {
createStore(isGroupPage, packages, isLoading);
wrapper = mount(PackagesList, {
- localVue,
store,
stubs: {
...stubChildren(PackagesList),
@@ -121,16 +120,15 @@ describe('packages_list', () => {
mountComponent();
});
- it('setItemToBeDeleted sets itemToBeDeleted and open the modal', () => {
+ it('setItemToBeDeleted sets itemToBeDeleted and open the modal', async () => {
const mockModalShow = jest.spyOn(wrapper.vm.$refs.packageListDeleteModal, 'show');
const item = last(wrapper.vm.list);
findPackagesListRow().vm.$emit('packageToDelete', item);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.vm.itemToBeDeleted).toEqual(item);
- expect(mockModalShow).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(wrapper.vm.itemToBeDeleted).toEqual(item);
+ expect(mockModalShow).toHaveBeenCalled();
});
it('deleteItemConfirmation resets itemToBeDeleted', () => {
@@ -141,15 +139,14 @@ describe('packages_list', () => {
expect(wrapper.vm.itemToBeDeleted).toEqual(null);
});
- it('deleteItemConfirmation emit package:delete', () => {
+ it('deleteItemConfirmation emit package:delete', async () => {
const itemToBeDeleted = { id: 2 };
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ itemToBeDeleted });
wrapper.vm.deleteItemConfirmation();
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.emitted('package:delete')[0]).toEqual([itemToBeDeleted]);
- });
+ await nextTick();
+ expect(wrapper.emitted('package:delete')[0]).toEqual([itemToBeDeleted]);
});
it('deleteItemCanceled resets itemToBeDeleted', () => {
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js
index 1052fdd1dda..79c1b18c9f9 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js
@@ -1,4 +1,5 @@
import { GlLink } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
@@ -126,7 +127,7 @@ describe('packages_list_row', () => {
findDeleteButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('packageToDelete')).toBeTruthy();
expect(wrapper.emitted('packageToDelete')[0]).toEqual([packageWithoutTags]);
});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap
index 7aa42a1f1e5..bdd0fe3ad9e 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/version_row_spec.js.snap
@@ -72,12 +72,14 @@ exports[`VersionRow renders 1`] = `
<div
class="gl-display-flex gl-align-items-center gl-min-h-6"
>
- Created
- <time-ago-tooltip-stub
- cssclass=""
- time="2021-08-10T09:33:54Z"
- tooltipplacement="top"
- />
+ <span>
+ Created
+ <time-ago-tooltip-stub
+ cssclass=""
+ time="2021-08-10T09:33:54Z"
+ tooltipplacement="top"
+ />
+ </span>
</div>
</div>
</div>
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_title_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_title_spec.js
index 6ad6007c9da..5da9cfffaae 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/package_title_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_title_spec.js
@@ -1,5 +1,6 @@
import { GlIcon, GlSprintf } from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
+import { nextTick } from 'vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import PackageTags from '~/packages_and_registries/shared/components/package_tags.vue';
@@ -24,7 +25,7 @@ const packageWithTags = {
describe('PackageTitle', () => {
let wrapper;
- function createComponent(packageEntity = packageWithTags) {
+ async function createComponent(packageEntity = packageWithTags) {
wrapper = shallowMountExtended(PackageTitle, {
propsData: { packageEntity },
stubs: {
@@ -35,7 +36,7 @@ describe('PackageTitle', () => {
GlResizeObserver: createMockDirective(),
},
});
- return wrapper.vm.$nextTick();
+ await nextTick();
}
const findTitleArea = () => wrapper.findComponent(TitleArea);
@@ -71,7 +72,7 @@ describe('PackageTitle', () => {
await createComponent();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findPackageBadges()).toHaveLength(packageTags().length);
});
@@ -85,7 +86,7 @@ describe('PackageTitle', () => {
const { value } = getBinding(wrapper.element, 'gl-resize-observer');
value();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findPackageBadges()).toHaveLength(packageTags().length);
});
});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js b/spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js
index 5de30829fa5..14a70def7d0 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/functional/delete_package_spec.js
@@ -1,4 +1,4 @@
-import { createLocalVue } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import waitForPromises from 'helpers/wait_for_promises';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -16,8 +16,6 @@ import {
jest.mock('~/flash');
-const localVue = createLocalVue();
-
describe('DeletePackage', () => {
let wrapper;
let apolloProvider;
@@ -27,7 +25,7 @@ describe('DeletePackage', () => {
const eventPayload = { id: '1' };
function createComponent(propsData = {}) {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const requestHandlers = [
[getPackagesQuery, resolver],
@@ -37,7 +35,6 @@ describe('DeletePackage', () => {
wrapper = shallowMountExtended(DeletePackage, {
propsData,
- localVue,
apolloProvider,
scopedSlots: {
default(props) {
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js
index 9467a613b2a..12a3eaa3873 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js
@@ -1,5 +1,5 @@
import { GlSprintf } from '@gitlab/ui';
-import { createLocalVue } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import VueRouter from 'vue-router';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
@@ -17,8 +17,7 @@ import { PACKAGE_ERROR_STATUS } from '~/packages_and_registries/package_registry
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import { packageData, packagePipelines, packageProject, packageTags } from '../../mock_data';
-const localVue = createLocalVue();
-localVue.use(VueRouter);
+Vue.use(VueRouter);
describe('packages_list_row', () => {
let wrapper;
@@ -47,7 +46,6 @@ describe('packages_list_row', () => {
provide = defaultProvide,
} = {}) => {
wrapper = shallowMountExtended(PackagesListRow, {
- localVue,
provide,
stubs: {
ListItem,
@@ -121,7 +119,7 @@ describe('packages_list_row', () => {
findDeleteButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('packageToDelete')).toBeTruthy();
expect(wrapper.emitted('packageToDelete')[0]).toEqual([packageWithoutTags]);
});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js
index bed7a07ff36..9e91b15bc6e 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js
@@ -5,7 +5,10 @@ import component from '~/packages_and_registries/package_registry/components/lis
import PackageTypeToken from '~/packages_and_registries/package_registry/components/list/tokens/package_type_token.vue';
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
import UrlSync from '~/vue_shared/components/url_sync.vue';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
+import { LIST_KEY_CREATED_AT } from '~/packages_and_registries/package_registry/constants';
+
import { getQueryParams, extractFilterAndSorting } from '~/packages_and_registries/shared/utils';
jest.mock('~/packages_and_registries/shared/utils');
@@ -22,6 +25,7 @@ describe('Package Search', () => {
const findRegistrySearch = () => wrapper.findComponent(RegistrySearch);
const findUrlSync = () => wrapper.findComponent(UrlSync);
+ const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
const mountComponent = (isGroupPage = false) => {
wrapper = shallowMountExtended(component, {
@@ -32,6 +36,7 @@ describe('Package Search', () => {
},
stubs: {
UrlSync,
+ LocalStorageSync,
},
});
};
@@ -64,6 +69,19 @@ describe('Package Search', () => {
expect(findUrlSync().exists()).toBe(true);
});
+ it('has a LocalStorageSync component', () => {
+ mountComponent();
+
+ expect(findLocalStorageSync().props()).toMatchObject({
+ asJson: true,
+ storageKey: 'package_registry_list_sorting',
+ value: {
+ orderBy: LIST_KEY_CREATED_AT,
+ sort: 'desc',
+ },
+ });
+ });
+
it.each`
isGroupPage | page
${false} | ${'project'}
@@ -92,7 +110,7 @@ describe('Package Search', () => {
await nextTick();
- expect(findRegistrySearch().props('sorting')).toEqual({ sort: 'foo', orderBy: 'name' });
+ expect(findRegistrySearch().props('sorting')).toEqual({ sort: 'foo', orderBy: 'created_at' });
// there is always a first call on mounted that emits up default values
expect(wrapper.emitted('update')[1]).toEqual([
@@ -101,7 +119,7 @@ describe('Package Search', () => {
packageName: '',
packageType: undefined,
},
- sort: 'NAME_FOO',
+ sort: 'CREATED_FOO',
},
]);
});
@@ -133,7 +151,7 @@ describe('Package Search', () => {
packageName: '',
packageType: undefined,
},
- sort: 'NAME_DESC',
+ sort: 'CREATED_DESC',
},
]);
});
diff --git a/spec/frontend/packages_and_registries/package_registry/mock_data.js b/spec/frontend/packages_and_registries/package_registry/mock_data.js
index c6a59f20998..0a4747fc9ec 100644
--- a/spec/frontend/packages_and_registries/package_registry/mock_data.js
+++ b/spec/frontend/packages_and_registries/package_registry/mock_data.js
@@ -119,6 +119,7 @@ export const packageVersions = () => [
];
export const packageData = (extend) => ({
+ __typename: 'Package',
id: 'gid://gitlab/Packages::Package/111',
canDestroy: true,
name: '@gitlab-org/package-15',
diff --git a/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap
index ed96abe24b1..0154486e224 100644
--- a/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap
+++ b/spec/frontend/packages_and_registries/package_registry/pages/__snapshots__/list_spec.js.snap
@@ -38,8 +38,8 @@ exports[`PackagesListApp renders 1`] = `
class="gl-font-size-h-display gl-line-height-36 h4"
>
- There are no packages yet
-
+ There are no packages yet
+
</h1>
<p
diff --git a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
index 637e2edf3be..a7e31d42c9e 100644
--- a/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/pages/details_spec.js
@@ -1,6 +1,6 @@
import { GlEmptyState, GlBadge, GlTabs, GlTab } from '@gitlab/ui';
-import { createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
+
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
@@ -41,8 +41,6 @@ import {
jest.mock('~/flash');
useMockLocationHelper();
-const localVue = createLocalVue();
-
describe('PackagesApp', () => {
let wrapper;
let apolloProvider;
@@ -59,12 +57,14 @@ describe('PackagesApp', () => {
breadCrumbState,
};
+ const { __typename, ...packageWithoutTypename } = packageData();
+
function createComponent({
resolver = jest.fn().mockResolvedValue(packageDetailsQuery()),
fileDeleteMutationResolver = jest.fn().mockResolvedValue(packageDestroyFileMutation()),
routeId = '1',
} = {}) {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const requestHandlers = [
[getPackageDetails, resolver],
@@ -73,7 +73,6 @@ describe('PackagesApp', () => {
apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMountExtended(PackagesApp, {
- localVue,
apolloProvider,
provide,
stubs: {
@@ -133,7 +132,7 @@ describe('PackagesApp', () => {
expect(findPackageTitle().exists()).toBe(true);
expect(findPackageTitle().props()).toMatchObject({
- packageEntity: expect.objectContaining(packageData()),
+ packageEntity: expect.objectContaining(packageWithoutTypename),
});
});
@@ -156,7 +155,7 @@ describe('PackagesApp', () => {
expect(findPackageHistory().exists()).toBe(true);
expect(findPackageHistory().props()).toMatchObject({
- packageEntity: expect.objectContaining(packageData()),
+ packageEntity: expect.objectContaining(packageWithoutTypename),
projectName: packageDetailsQuery().data.package.project.name,
});
});
@@ -168,7 +167,7 @@ describe('PackagesApp', () => {
expect(findAdditionalMetadata().exists()).toBe(true);
expect(findAdditionalMetadata().props()).toMatchObject({
- packageEntity: expect.objectContaining(packageData()),
+ packageEntity: expect.objectContaining(packageWithoutTypename),
});
});
@@ -179,7 +178,7 @@ describe('PackagesApp', () => {
expect(findInstallationCommands().exists()).toBe(true);
expect(findInstallationCommands().props()).toMatchObject({
- packageEntity: expect.objectContaining(packageData()),
+ packageEntity: expect.objectContaining(packageWithoutTypename),
});
});
diff --git a/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js
index 2ac2a6455ef..0e74fbbc6d9 100644
--- a/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js
@@ -1,8 +1,8 @@
import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
-import { createLocalVue } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-import { nextTick } from 'vue';
+
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -27,8 +27,6 @@ import { packagesListQuery, packageData, pagination } from '../mock_data';
jest.mock('~/lib/utils/common_utils');
jest.mock('~/flash');
-const localVue = createLocalVue();
-
describe('PackagesListApp', () => {
let wrapper;
let apolloProvider;
@@ -61,13 +59,12 @@ describe('PackagesListApp', () => {
resolver = jest.fn().mockResolvedValue(packagesListQuery()),
provide = defaultProvide,
} = {}) => {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const requestHandlers = [[getPackagesQuery, resolver]];
apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMountExtended(ListPage, {
- localVue,
apolloProvider,
provide,
stubs: {
@@ -85,7 +82,7 @@ describe('PackagesListApp', () => {
wrapper.destroy();
});
- const waitForFirstRequest = () => {
+ const waitForFirstRequest = async () => {
// emit a search update so the query is executed
findSearch().vm.$emit('update', { sort: 'NAME_DESC', filters: [] });
return waitForPromises();
@@ -149,11 +146,10 @@ describe('PackagesListApp', () => {
beforeEach(() => {
resolver = jest.fn().mockResolvedValue(packagesListQuery());
mountComponent({ resolver });
-
- return waitForFirstRequest();
});
- it('exists and has the right props', () => {
+ it('exists and has the right props', async () => {
+ await waitForFirstRequest();
expect(findListComponent().props()).toMatchObject({
list: expect.arrayContaining([expect.objectContaining({ id: packageData().id })]),
isLoading: false,
@@ -161,16 +157,20 @@ describe('PackagesListApp', () => {
});
});
- it('when list emits next-page fetches the next set of records', () => {
+ it('when list emits next-page fetches the next set of records', async () => {
+ await waitForFirstRequest();
findListComponent().vm.$emit('next-page');
+ await waitForPromises();
expect(resolver).toHaveBeenCalledWith(
expect.objectContaining({ after: pagination().endCursor, first: GRAPHQL_PAGE_SIZE }),
);
});
- it('when list emits prev-page fetches the prev set of records', () => {
+ it('when list emits prev-page fetches the prev set of records', async () => {
+ await waitForFirstRequest();
findListComponent().vm.$emit('prev-page');
+ await waitForPromises();
expect(resolver).toHaveBeenCalledWith(
expect.objectContaining({ before: pagination().startCursor, last: GRAPHQL_PAGE_SIZE }),
diff --git a/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js
index f6c1d212b51..94f56e5c979 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js
@@ -1,5 +1,5 @@
import { GlSprintf, GlToggle } from '@gitlab/ui';
-import { createLocalVue } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -33,8 +33,6 @@ import {
jest.mock('~/flash');
jest.mock('~/packages_and_registries/settings/group/graphql/utils/optimistic_responses');
-const localVue = createLocalVue();
-
describe('DependencyProxySettings', () => {
let wrapper;
let apolloProvider;
@@ -47,7 +45,7 @@ describe('DependencyProxySettings', () => {
groupDependencyProxyPath: 'group_dependency_proxy_path',
};
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const mountComponent = ({
provide = defaultProvide,
@@ -63,7 +61,6 @@ describe('DependencyProxySettings', () => {
apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMountExtended(component, {
- localVue,
apolloProvider,
provide,
propsData: {
diff --git a/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js b/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js
index 933dac7f5a8..5c30074a6af 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js
@@ -1,7 +1,8 @@
import { GlAlert } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-import { nextTick } from 'vue';
+
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import PackagesSettings from '~/packages_and_registries/settings/group/components/packages_settings.vue';
@@ -19,8 +20,6 @@ import {
jest.mock('~/flash');
-const localVue = createLocalVue();
-
describe('Group Settings App', () => {
let wrapper;
let apolloProvider;
@@ -36,14 +35,13 @@ describe('Group Settings App', () => {
resolver = jest.fn().mockResolvedValue(groupPackageSettingsMock),
provide = defaultProvide,
} = {}) => {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const requestHandlers = [[getGroupPackagesSettingsQuery, resolver]];
apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMount(component, {
- localVue,
apolloProvider,
provide,
mocks: {
diff --git a/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js
index 693af21e24a..d92d42e7834 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js
@@ -1,5 +1,5 @@
import { GlSprintf, GlLink } from '@gitlab/ui';
-import { createLocalVue } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -28,8 +28,6 @@ import {
jest.mock('~/flash');
jest.mock('~/packages_and_registries/settings/group/graphql/utils/optimistic_responses');
-const localVue = createLocalVue();
-
describe('Packages Settings', () => {
let wrapper;
let apolloProvider;
@@ -42,14 +40,13 @@ describe('Packages Settings', () => {
const mountComponent = ({
mutationResolver = jest.fn().mockResolvedValue(groupPackageSettingsMutationMock()),
} = {}) => {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const requestHandlers = [[updateNamespacePackageSettings, mutationResolver]];
apolloProvider = createMockApollo(requestHandlers);
wrapper = shallowMountExtended(component, {
- localVue,
apolloProvider,
provide: defaultProvide,
propsData: {
@@ -252,7 +249,7 @@ describe('Packages Settings', () => {
emitMavenSettingsUpdate();
- await wrapper.vm.$nextTick();
+ await nextTick();
// errors are reset on mutation call
expect(findMavenDuplicatedSettings().props('duplicateExceptionRegexError')).toBe('');
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js
index 8266f9bee89..a6c929844b1 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js
@@ -1,7 +1,9 @@
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import component from '~/packages_and_registries/settings/project/components/registry_settings_app.vue';
import SettingsForm from '~/packages_and_registries/settings/project/components/settings_form.vue';
import {
@@ -19,8 +21,6 @@ import {
containerExpirationPolicyData,
} from '../mock_data';
-const localVue = createLocalVue();
-
describe('Registry Settings App', () => {
let wrapper;
let fakeApollo;
@@ -55,17 +55,14 @@ describe('Registry Settings App', () => {
};
const mountComponentWithApollo = ({ provide = defaultProvidedValues, resolver } = {}) => {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const requestHandlers = [[expirationPolicyQuery, resolver]];
fakeApollo = createMockApollo(requestHandlers);
mountComponent(provide, {
- localVue,
apolloProvider: fakeApollo,
});
-
- return requestHandlers.map((request) => request[1]);
};
afterEach(() => {
@@ -101,25 +98,25 @@ describe('Registry Settings App', () => {
${'response and changes'} | ${expirationPolicyPayload()} | ${{ ...containerExpirationPolicyData(), nameRegex: '12345' }} | ${true}
${'response and empty'} | ${expirationPolicyPayload()} | ${{}} | ${true}
`('$description', async ({ apiResponse, workingCopy, result }) => {
- const requests = mountComponentWithApollo({
+ mountComponentWithApollo({
provide: { ...defaultProvidedValues, enableHistoricEntries: true },
resolver: jest.fn().mockResolvedValue(apiResponse),
});
- await Promise.all(requests);
+ await waitForPromises();
findSettingsComponent().vm.$emit('input', workingCopy);
- await wrapper.vm.$nextTick();
+ await waitForPromises();
expect(findSettingsComponent().props('isEdited')).toBe(result);
});
});
it('renders the setting form', async () => {
- const requests = mountComponentWithApollo({
+ mountComponentWithApollo({
resolver: jest.fn().mockResolvedValue(expirationPolicyPayload()),
});
- await Promise.all(requests);
+ await waitForPromises();
expect(findSettingsComponent().exists()).toBe(true);
});
@@ -153,11 +150,11 @@ describe('Registry Settings App', () => {
});
describe('fetchSettingsError', () => {
- beforeEach(() => {
- const requests = mountComponentWithApollo({
+ beforeEach(async () => {
+ mountComponentWithApollo({
resolver: jest.fn().mockRejectedValue(new Error('GraphQL error')),
});
- return Promise.all(requests);
+ await waitForPromises();
});
it('the form is hidden', () => {
@@ -175,14 +172,14 @@ describe('Registry Settings App', () => {
${true} | ${true}
${false} | ${false}
`('is $isShown that the form is shown', async ({ enableHistoricEntries, isShown }) => {
- const requests = mountComponentWithApollo({
+ mountComponentWithApollo({
provide: {
...defaultProvidedValues,
enableHistoricEntries,
},
resolver: jest.fn().mockResolvedValue(emptyExpirationPolicyPayload()),
});
- await Promise.all(requests);
+ await waitForPromises();
expect(findSettingsComponent().exists()).toBe(isShown);
});
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/settings_form_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/settings_form_spec.js
index bc104a25ef9..625aa37fc0f 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/settings_form_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/settings_form_spec.js
@@ -1,5 +1,6 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
+import { nextTick } from 'vue';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { GlCard, GlLoadingIcon } from 'jest/packages_and_registries/shared/stubs';
@@ -201,7 +202,7 @@ describe('Settings Form', () => {
finder().vm.$emit('input', 'foo');
expect(finder().props('error')).toEqual('bar');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(finder().props('error')).toEqual('');
});
@@ -213,7 +214,7 @@ describe('Settings Form', () => {
finder().vm.$emit('validation', false);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findSaveButton().props('disabled')).toBe(true);
});
@@ -252,7 +253,7 @@ describe('Settings Form', () => {
findForm().trigger('reset');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findKeepRegexInput().props('error')).toBe('');
expect(findRemoveRegexInput().props('error')).toBe('');
@@ -319,7 +320,7 @@ describe('Settings Form', () => {
findForm().trigger('submit');
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_SUCCESS_MESSAGE);
});
@@ -335,7 +336,7 @@ describe('Settings Form', () => {
findForm().trigger('submit');
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith('foo');
});
@@ -349,7 +350,7 @@ describe('Settings Form', () => {
findForm().trigger('submit');
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(UPDATE_SETTINGS_ERROR_MESSAGE);
});
@@ -368,7 +369,7 @@ describe('Settings Form', () => {
findForm().trigger('submit');
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findKeepRegexInput().props('error')).toEqual('baz');
});
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js b/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js
index a56bb75f8ed..33406c98f4b 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/mock_data.js
@@ -13,6 +13,7 @@ export const expirationPolicyPayload = (override) => ({
project: {
id: '1',
containerExpirationPolicy: {
+ __typename: 'ContainerExpirationPolicy',
...containerExpirationPolicyData(),
...override,
},
diff --git a/spec/frontend/pager_spec.js b/spec/frontend/pager_spec.js
index ff352303143..043ea470436 100644
--- a/spec/frontend/pager_spec.js
+++ b/spec/frontend/pager_spec.js
@@ -1,6 +1,7 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import { TEST_HOST } from 'helpers/test_constants';
+import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import { removeParams } from '~/lib/utils/url_utility';
import Pager from '~/pager';
@@ -64,67 +65,59 @@ describe('pager', () => {
Pager.init();
});
- it('shows loader while loading next page', (done) => {
+ it('shows loader while loading next page', async () => {
mockSuccess();
jest.spyOn(Pager.loading, 'show').mockImplementation(() => {});
Pager.getOld();
- setImmediate(() => {
- expect(Pager.loading.show).toHaveBeenCalled();
+ await waitForPromises();
- done();
- });
+ expect(Pager.loading.show).toHaveBeenCalled();
});
- it('hides loader on success', (done) => {
+ it('hides loader on success', async () => {
mockSuccess();
jest.spyOn(Pager.loading, 'hide').mockImplementation(() => {});
Pager.getOld();
- setImmediate(() => {
- expect(Pager.loading.hide).toHaveBeenCalled();
+ await waitForPromises();
- done();
- });
+ expect(Pager.loading.hide).toHaveBeenCalled();
});
- it('hides loader on error', (done) => {
+ it('hides loader on error', async () => {
mockError();
jest.spyOn(Pager.loading, 'hide').mockImplementation(() => {});
Pager.getOld();
- setImmediate(() => {
- expect(Pager.loading.hide).toHaveBeenCalled();
+ await waitForPromises();
- done();
- });
+ expect(Pager.loading.hide).toHaveBeenCalled();
});
- it('sends request to url with offset and limit params', (done) => {
+ it('sends request to url with offset and limit params', async () => {
Pager.offset = 100;
Pager.limit = 20;
Pager.getOld();
- setImmediate(() => {
- const [url, params] = axios.get.mock.calls[0];
+ await waitForPromises();
- expect(params).toEqual({
- params: {
- limit: 20,
- offset: 100,
- },
- });
+ const [url, params] = axios.get.mock.calls[0];
- expect(url).toBe('/some_list');
-
- done();
+ expect(params).toEqual({
+ params: {
+ limit: 20,
+ offset: 100,
+ },
});
+
+ expect(url).toBe('/some_list');
});
- it('disables if return count is less than limit', (done) => {
+ it('disables if return count is less than limit', async () => {
Pager.offset = 0;
Pager.limit = 20;
@@ -132,12 +125,10 @@ describe('pager', () => {
jest.spyOn(Pager.loading, 'hide').mockImplementation(() => {});
Pager.getOld();
- setImmediate(() => {
- expect(Pager.loading.hide).toHaveBeenCalled();
- expect(Pager.disable).toBe(true);
+ await waitForPromises();
- done();
- });
+ expect(Pager.loading.hide).toHaveBeenCalled();
+ expect(Pager.disable).toBe(true);
});
describe('has data-href attribute from list element', () => {
diff --git a/spec/frontend/pages/admin/projects/components/namespace_select_spec.js b/spec/frontend/pages/admin/projects/components/namespace_select_spec.js
index 1fcc00489e3..f10b202f4d7 100644
--- a/spec/frontend/pages/admin/projects/components/namespace_select_spec.js
+++ b/spec/frontend/pages/admin/projects/components/namespace_select_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import Api from '~/api';
import NamespaceSelect from '~/pages/admin/projects/components/namespace_select.vue';
@@ -55,14 +56,14 @@ describe('Dropdown select component', () => {
mountDropdown({ fieldName: 'namespace-input' });
// wait for dropdown options to populate
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findDropdownOption('user: Administrator').exists()).toBe(true);
expect(findDropdownOption('group: GitLab Org').exists()).toBe(true);
expect(findDropdownOption('group: Foobar').exists()).toBe(false);
findDropdownOption('user: Administrator').trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findNamespaceInput().attributes('value')).toBe('10');
expect(findDropdownToggle().text()).toBe('user: Administrator');
@@ -72,7 +73,7 @@ describe('Dropdown select component', () => {
mountDropdown();
// wait for dropdown options to populate
- await wrapper.vm.$nextTick();
+ await nextTick();
findDropdownOption('group: GitLab Org').trigger('click');
diff --git a/spec/frontend/pages/dashboard/todos/index/todos_spec.js b/spec/frontend/pages/dashboard/todos/index/todos_spec.js
index 6a7ce80ec5a..ef295e7d1ba 100644
--- a/spec/frontend/pages/dashboard/todos/index/todos_spec.js
+++ b/spec/frontend/pages/dashboard/todos/index/todos_spec.js
@@ -1,5 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
+import waitForPromises from 'helpers/wait_for_promises';
import '~/lib/utils/common_utils';
import axios from '~/lib/utils/axios_utils';
import { addDelimiter } from '~/lib/utils/text_utility';
@@ -71,7 +72,7 @@ describe('Todos', () => {
describe('on done todo click', () => {
let onToggleSpy;
- beforeEach((done) => {
+ beforeEach(() => {
const el = document.querySelector('.js-done-todo');
const path = el.dataset.href;
@@ -86,7 +87,7 @@ describe('Todos', () => {
el.click();
// Wait for axios and HTML to udpate
- setImmediate(done);
+ return waitForPromises();
});
it('dispatches todo:toggle', () => {
diff --git a/spec/frontend/pages/profiles/password_prompt/password_prompt_modal_spec.js b/spec/frontend/pages/profiles/password_prompt/password_prompt_modal_spec.js
index b722ac1e97b..c30b996437d 100644
--- a/spec/frontend/pages/profiles/password_prompt/password_prompt_modal_spec.js
+++ b/spec/frontend/pages/profiles/password_prompt/password_prompt_modal_spec.js
@@ -1,4 +1,5 @@
import { GlModal } from '@gitlab/ui';
+import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import {
I18N_PASSWORD_PROMPT_CANCEL_BUTTON,
@@ -62,7 +63,7 @@ describe('Password prompt modal', () => {
setPassword(mockPassword);
submitModal();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(handleConfirmPasswordSpy).toHaveBeenCalledTimes(1);
expect(handleConfirmPasswordSpy).toHaveBeenCalledWith(mockPassword);
@@ -73,7 +74,7 @@ describe('Password prompt modal', () => {
expect(findConfirmBtnDisabledState()).toBe(true);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findConfirmBtnDisabledState()).toBe(false);
});
@@ -84,7 +85,7 @@ describe('Password prompt modal', () => {
expect(findConfirmBtnDisabledState()).toBe(true);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findConfirmBtnDisabledState()).toBe(true);
});
diff --git a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js
index dd617b1ffc2..dc5f1cb9e61 100644
--- a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js
+++ b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js
@@ -4,6 +4,7 @@ import { mount, shallowMount } from '@vue/test-utils';
import axios from 'axios';
import AxiosMockAdapter from 'axios-mock-adapter';
import { kebabCase } from 'lodash';
+import { nextTick } from 'vue';
import createFlash from '~/flash';
import httpStatus from '~/lib/utils/http_status';
import * as urlUtility from '~/lib/utils/url_utility';
@@ -217,7 +218,7 @@ describe('ForkForm component', () => {
it('changes to kebab case when project name changes', async () => {
const newInput = `${projectPath}1`;
findForkNameInput().vm.$emit('input', newInput);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findForkSlugInput().attributes('value')).toBe(kebabCase(newInput));
});
@@ -225,7 +226,7 @@ describe('ForkForm component', () => {
it('does not change to kebab case when project slug is changed manually', async () => {
const newInput = `${projectPath}1`;
findForkSlugInput().vm.$emit('input', newInput);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findForkSlugInput().attributes('value')).toBe(newInput);
});
@@ -273,7 +274,7 @@ describe('ForkForm component', () => {
expect(wrapper.vm.form.fields.visibility.value).toBe('public');
await findFormSelectOptions().at(1).setSelected();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(getByRole(wrapper.element, 'radio', { name: /private/i }).checked).toBe(true);
});
@@ -283,7 +284,7 @@ describe('ForkForm component', () => {
await findFormSelectOptions().at(1).setSelected();
- await wrapper.vm.$nextTick();
+ await nextTick();
const container = getByRole(wrapper.element, 'radiogroup', { name: /visibility/i });
const visibilityRadios = getAllByRole(container, 'radio');
@@ -419,7 +420,7 @@ describe('ForkForm component', () => {
const form = wrapper.find(GlForm);
await form.trigger('submit');
- await wrapper.vm.$nextTick();
+ await nextTick();
};
describe('with invalid form', () => {
diff --git a/spec/frontend/pages/projects/graphs/code_coverage_spec.js b/spec/frontend/pages/projects/graphs/code_coverage_spec.js
index 1f9029b40c7..0f763e3220a 100644
--- a/spec/frontend/pages/projects/graphs/code_coverage_spec.js
+++ b/spec/frontend/pages/projects/graphs/code_coverage_spec.js
@@ -3,6 +3,7 @@ import { GlAreaChart } from '@gitlab/ui/dist/charts';
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 httpStatusCodes from '~/lib/utils/http_status';
@@ -143,7 +144,7 @@ describe('Code Coverage', () => {
it('updates the selected dropdown option with an icon', async () => {
findSecondDropdownItem().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findFirstDropdownItem().attributes('ischecked')).toBeFalsy();
expect(findSecondDropdownItem().attributes('ischecked')).toBeTruthy();
@@ -155,7 +156,7 @@ describe('Code Coverage', () => {
findSecondDropdownItem().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.selectedDailyCoverage).not.toBe(originalSelectedData);
expect(wrapper.vm.selectedDailyCoverage).toBe(expectedData);
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap
index 1586aded6e6..86ccaa43786 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap
+++ b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap
@@ -135,13 +135,13 @@ exports[`Learn GitLab renders correctly 1`] = `
>
<a
class="gl-link"
+ data-testid="uncompleted-learn-gitlab-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Set up CI/CD"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
- rel="noopener noreferrer"
- target="_blank"
+ target="_self"
>
Set up CI/CD
@@ -155,13 +155,13 @@ exports[`Learn GitLab renders correctly 1`] = `
>
<a
class="gl-link"
+ data-testid="uncompleted-learn-gitlab-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Start a free Ultimate trial"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
- rel="noopener noreferrer"
- target="_blank"
+ target="_self"
>
Start a free Ultimate trial
@@ -175,13 +175,13 @@ exports[`Learn GitLab renders correctly 1`] = `
>
<a
class="gl-link"
+ data-testid="uncompleted-learn-gitlab-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Add code owners"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
- rel="noopener noreferrer"
- target="_blank"
+ target="_self"
>
Add code owners
@@ -202,13 +202,13 @@ exports[`Learn GitLab renders correctly 1`] = `
>
<a
class="gl-link"
+ data-testid="uncompleted-learn-gitlab-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Add merge request approval"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
- rel="noopener noreferrer"
- target="_blank"
+ target="_self"
>
Add merge request approval
@@ -265,13 +265,13 @@ exports[`Learn GitLab renders correctly 1`] = `
>
<a
class="gl-link"
+ data-testid="uncompleted-learn-gitlab-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Create an issue"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
- rel="noopener noreferrer"
- target="_blank"
+ target="_self"
>
Create an issue
@@ -285,13 +285,13 @@ exports[`Learn GitLab renders correctly 1`] = `
>
<a
class="gl-link"
+ data-testid="uncompleted-learn-gitlab-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Submit a merge request"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
href="http://example.com/"
- rel="noopener noreferrer"
- target="_blank"
+ target="_self"
>
Submit a merge request
@@ -341,11 +341,12 @@ exports[`Learn GitLab renders correctly 1`] = `
>
<a
class="gl-link"
+ data-testid="uncompleted-learn-gitlab-link"
data-track-action="click_link"
data-track-experiment="change_continuous_onboarding_link_urls"
data-track-label="Run a Security scan using CI/CD"
data-track-property="Growth::Conversion::Experiment::LearnGitLab"
- href="http://example.com/"
+ href="https://docs.gitlab.com/ee/foobar/"
rel="noopener noreferrer"
target="_blank"
>
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
index f7b2154a935..3b113f4dcd7 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
+++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
@@ -12,6 +12,10 @@ const defaultProps = {
completed: false,
};
+const docLinkProps = {
+ url: 'https://docs.gitlab.com/ee/user/application_security/security_dashboard/',
+};
+
describe('Learn GitLab Section Link', () => {
let wrapper;
@@ -29,6 +33,8 @@ describe('Learn GitLab Section Link', () => {
const openInviteMembesrModalLink = () =>
wrapper.find('[data-testid="invite-for-help-continuous-onboarding-experiment-link"]');
+ const findUncompletedLink = () => wrapper.find('[data-testid="uncompleted-learn-gitlab-link"]');
+
it('renders no icon when not completed', () => {
createWrapper(undefined, { completed: false });
@@ -53,6 +59,32 @@ describe('Learn GitLab Section Link', () => {
expect(wrapper.find('[data-testid="trial-only"]').exists()).toBe(true);
});
+ describe('doc links', () => {
+ beforeEach(() => {
+ createWrapper('securityScanEnabled', docLinkProps);
+ });
+
+ it('renders links with blank target', () => {
+ const linkElement = findUncompletedLink();
+
+ expect(linkElement.exists()).toBe(true);
+ expect(linkElement.attributes('target')).toEqual('_blank');
+ });
+
+ it('tracks the click', () => {
+ const trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
+
+ findUncompletedLink().trigger('click');
+
+ expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_link', {
+ label: 'Run a Security scan using CI/CD',
+ property: 'Growth::Conversion::Experiment::LearnGitLab',
+ });
+
+ unmockTracking();
+ });
+ });
+
describe('rendering a link to open the invite_members modal instead of a regular link', () => {
it.each`
action | experimentVariant | showModal
@@ -82,11 +114,7 @@ describe('Learn GitLab Section Link', () => {
it('calls the eventHub', () => {
openInviteMembesrModalLink().vm.$emit('click');
- expect(eventHub.$emit).toHaveBeenCalledWith('openModal', {
- inviteeType: 'members',
- source: 'learn_gitlab',
- tasksToBeDoneEnabled: true,
- });
+ expect(eventHub.$emit).toHaveBeenCalledWith('openModal', { source: 'learn_gitlab' });
});
it('tracks the click', async () => {
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
index 7e71622770f..ee682b18af3 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
+++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
@@ -1,13 +1,15 @@
import { GlProgressBar, GlAlert } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import Cookies from 'js-cookie';
import LearnGitlab from '~/pages/projects/learn_gitlab/components/learn_gitlab.vue';
import eventHub from '~/invite_members/event_hub';
+import { INVITE_MODAL_OPEN_COOKIE } from '~/pages/projects/learn_gitlab/constants';
import { testActions, testSections, testProject } from './mock_data';
describe('Learn GitLab', () => {
let wrapper;
let sidebar;
- let inviteMembersOpen = false;
+ let inviteMembers = false;
const createWrapper = () => {
wrapper = mount(LearnGitlab, {
@@ -15,7 +17,7 @@ describe('Learn GitLab', () => {
actions: testActions,
sections: testSections,
project: testProject,
- inviteMembersOpen,
+ inviteMembers,
},
});
};
@@ -36,7 +38,7 @@ describe('Learn GitLab', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
- inviteMembersOpen = false;
+ inviteMembers = false;
sidebar.remove();
});
@@ -59,24 +61,40 @@ describe('Learn GitLab', () => {
describe('Invite Members Modal', () => {
let spy;
+ let cookieSpy;
beforeEach(() => {
spy = jest.spyOn(eventHub, '$emit');
+ cookieSpy = jest.spyOn(Cookies, 'remove');
+ });
+
+ afterEach(() => {
+ Cookies.remove(INVITE_MODAL_OPEN_COOKIE);
});
it('emits openModal', () => {
- inviteMembersOpen = true;
+ inviteMembers = true;
+ Cookies.set(INVITE_MODAL_OPEN_COOKIE, true);
createWrapper();
expect(spy).toHaveBeenCalledWith('openModal', {
mode: 'celebrate',
- inviteeType: 'members',
source: 'learn-gitlab',
});
+ expect(cookieSpy).toHaveBeenCalledWith(INVITE_MODAL_OPEN_COOKIE);
+ });
+
+ it('does not emit openModal when cookie is not set', () => {
+ inviteMembers = true;
+
+ createWrapper();
+
+ expect(spy).not.toHaveBeenCalled();
+ expect(cookieSpy).toHaveBeenCalledWith(INVITE_MODAL_OPEN_COOKIE);
});
- it('does not emit openModal', () => {
+ it('does not emit openModal when inviteMembers is false', () => {
createWrapper();
expect(spy).not.toHaveBeenCalled();
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js b/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js
index 1e633cb7cf5..b21965e8f48 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js
+++ b/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js
@@ -35,7 +35,7 @@ export const testActions = {
svg: 'http://example.com/images/illustration.svg',
},
securityScanEnabled: {
- url: 'http://example.com/',
+ url: 'https://docs.gitlab.com/ee/foobar/',
completed: false,
svg: 'http://example.com/images/illustration.svg',
},
diff --git a/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js b/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js
index f3d76ca2c1b..ae5404f2d13 100644
--- a/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js
+++ b/spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js
@@ -1,5 +1,6 @@
import { GlIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { trimText } from 'helpers/text_helper';
import IntervalPatternInput from '~/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue';
@@ -98,7 +99,7 @@ describe('Interval Pattern Input Component', () => {
it('when a default option is selected', async () => {
selectEveryDayRadio();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findCustomInput().attributes('disabled')).toBeUndefined();
});
@@ -106,7 +107,7 @@ describe('Interval Pattern Input Component', () => {
it('when the custom option is selected', async () => {
selectCustomRadio();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findCustomInput().attributes('disabled')).toBeUndefined();
});
@@ -150,11 +151,11 @@ describe('Interval Pattern Input Component', () => {
it('when everyday is selected, update value', async () => {
selectEveryWeekRadio();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findCustomInput().element.value).toBe(cronIntervalPresets.everyWeek);
selectEveryDayRadio();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findCustomInput().element.value).toBe(cronIntervalPresets.everyDay);
});
});
@@ -170,7 +171,7 @@ describe('Interval Pattern Input Component', () => {
act();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findCustomInput().element.value).toBe(expectedValue);
});
@@ -189,7 +190,7 @@ describe('Interval Pattern Input Component', () => {
findCustomInput().setValue(newValue);
- await wrapper.vm.$nextTick;
+ await nextTick;
expect(findSelectedRadioKey()).toBe(customKey);
});
diff --git a/spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js b/spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js
index 5fed9fcaad2..c28a03b35d7 100644
--- a/spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js
+++ b/spec/frontend/pages/projects/pipeline_schedules/shared/components/pipeline_schedule_callout_spec.js
@@ -1,6 +1,7 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Cookies from 'js-cookie';
+import { nextTick } from 'vue';
import PipelineSchedulesCallout from '~/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue';
const cookieKey = 'pipeline_schedules_callout_dismissed';
@@ -27,7 +28,7 @@ describe('Pipeline Schedule Callout', () => {
Cookies.set(cookieKey, true);
createComponent();
- await wrapper.vm.$nextTick();
+ await nextTick();
});
afterEach(() => {
@@ -71,7 +72,7 @@ describe('Pipeline Schedule Callout', () => {
findDismissCalloutBtn().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findInnerContentOfCallout().exists()).toBe(false);
});
@@ -83,6 +84,7 @@ describe('Pipeline Schedule Callout', () => {
expect(setCookiesSpy).toHaveBeenCalledWith('pipeline_schedules_callout_dismissed', true, {
expires: 365,
+ secure: false,
});
});
});
@@ -90,7 +92,7 @@ describe('Pipeline Schedule Callout', () => {
it('is hidden when close button is clicked', async () => {
findDismissCalloutBtn().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findInnerContentOfCallout().exists()).toBe(false);
});
diff --git a/spec/frontend/pages/projects/shared/permissions/components/project_setting_row_spec.js b/spec/frontend/pages/projects/shared/permissions/components/project_setting_row_spec.js
index 7cbcbdcdd1f..6230809a6aa 100644
--- a/spec/frontend/pages/projects/shared/permissions/components/project_setting_row_spec.js
+++ b/spec/frontend/pages/projects/shared/permissions/components/project_setting_row_spec.js
@@ -1,5 +1,6 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import projectSettingRow from '~/pages/projects/shared/permissions/components/project_setting_row.vue';
describe('Project Setting Row', () => {
@@ -18,43 +19,39 @@ describe('Project Setting Row', () => {
wrapper.destroy();
});
- it('should show the label if it is set', () => {
+ it('should show the label if it is set', async () => {
wrapper.setProps({ label: 'Test label' });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find('label').text()).toEqual('Test label');
- });
+ await nextTick();
+ expect(wrapper.find('label').text()).toEqual('Test label');
});
it('should hide the label if it is not set', () => {
expect(wrapper.find('label').exists()).toBe(false);
});
- it('should show the help icon with the correct help path if it is set', () => {
+ it('should show the help icon with the correct help path if it is set', async () => {
wrapper.setProps({ label: 'Test label', helpPath: '/123' });
- return wrapper.vm.$nextTick(() => {
- const link = wrapper.find('a');
+ await nextTick();
+ const link = wrapper.find('a');
- expect(link.exists()).toBe(true);
- expect(link.attributes().href).toEqual('/123');
- });
+ expect(link.exists()).toBe(true);
+ expect(link.attributes().href).toEqual('/123');
});
- it('should hide the help icon if no help path is set', () => {
+ it('should hide the help icon if no help path is set', async () => {
wrapper.setProps({ label: 'Test label' });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find('a').exists()).toBe(false);
- });
+ await nextTick();
+ expect(wrapper.find('a').exists()).toBe(false);
});
- it('should show the help text if it is set', () => {
+ it('should show the help text if it is set', async () => {
wrapper.setProps({ helpText: 'Test text' });
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find('span').text()).toEqual('Test text');
- });
+ await nextTick();
+ expect(wrapper.find('span').text()).toEqual('Test text');
});
it('should hide the help text if it is set', () => {
diff --git a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
index 8a9bb025d55..305dce51971 100644
--- a/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
+++ b/spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js
@@ -244,7 +244,7 @@ describe('Settings Panel', () => {
wrapper = mountComponent({ currentSettings: { visibilityLevel: visibilityOptions.PUBLIC } });
expect(findRepositoryFeatureProjectRow().props('helpText')).toBe(
- 'View and edit files in this project. Non-project members will only have read access.',
+ 'View and edit files in this project. Non-project members have only read access.',
);
});
});
diff --git a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js
index fd581eebd1e..1f964e8bae2 100644
--- a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js
+++ b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js
@@ -48,10 +48,10 @@ describe('WikiForm', () => {
return format.find(`option[value=${value}]`).setSelected();
};
- const triggerFormSubmit = () => {
+ const triggerFormSubmit = async () => {
findForm().element.dispatchEvent(new Event('submit'));
- return nextTick();
+ await nextTick();
};
const dispatchBeforeUnload = () => {
@@ -574,7 +574,7 @@ describe('WikiForm', () => {
wrapper.findComponent(GlModal).vm.$emit('primary');
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('switches to classic editor', () => {
diff --git a/spec/frontend/performance_bar/components/add_request_spec.js b/spec/frontend/performance_bar/components/add_request_spec.js
index c5247a43f27..5422481439e 100644
--- a/spec/frontend/performance_bar/components/add_request_spec.js
+++ b/spec/frontend/performance_bar/components/add_request_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import AddRequest from '~/performance_bar/components/add_request.vue';
describe('add request form', () => {
@@ -17,9 +18,9 @@ describe('add request form', () => {
});
describe('when clicking the button', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.find('button').trigger('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('shows the form', () => {
@@ -27,9 +28,9 @@ describe('add request form', () => {
});
describe('when pressing escape', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.find('input').trigger('keyup.esc');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('hides the input', () => {
@@ -38,12 +39,11 @@ describe('add request form', () => {
});
describe('when submitting the form', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper.find('input').setValue('http://gitlab.example.com/users/root/calendar.json');
- return wrapper.vm.$nextTick().then(() => {
- wrapper.find('input').trigger('keyup.enter');
- return wrapper.vm.$nextTick();
- });
+ await nextTick();
+ wrapper.find('input').trigger('keyup.enter');
+ await nextTick();
});
it('emits an event to add the request', () => {
@@ -57,11 +57,10 @@ describe('add request form', () => {
expect(wrapper.find('input').exists()).toBe(false);
});
- it('clears the value for next time', () => {
+ it('clears the value for next time', async () => {
wrapper.find('button').trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find('input').text()).toEqual('');
- });
+ await nextTick();
+ expect(wrapper.find('input').text()).toEqual('');
});
});
});
diff --git a/spec/frontend/persistent_user_callout_spec.js b/spec/frontend/persistent_user_callout_spec.js
index 1db255106ed..4633602de26 100644
--- a/spec/frontend/persistent_user_callout_spec.js
+++ b/spec/frontend/persistent_user_callout_spec.js
@@ -10,6 +10,7 @@ jest.mock('~/flash');
describe('PersistentUserCallout', () => {
const dismissEndpoint = '/dismiss';
const featureName = 'feature';
+ const groupId = '5';
function createFixture() {
const fixture = document.createElement('div');
@@ -18,6 +19,7 @@ describe('PersistentUserCallout', () => {
class="container"
data-dismiss-endpoint="${dismissEndpoint}"
data-feature-id="${featureName}"
+ data-group-id="${groupId}"
>
<button type="button" class="js-close"></button>
</div>
@@ -86,7 +88,9 @@ describe('PersistentUserCallout', () => {
return waitForPromises().then(() => {
expect(persistentUserCallout.container.remove).toHaveBeenCalled();
- expect(mockAxios.history.post[0].data).toBe(JSON.stringify({ feature_name: featureName }));
+ expect(mockAxios.history.post[0].data).toBe(
+ JSON.stringify({ feature_name: featureName, group_id: groupId }),
+ );
});
});
@@ -191,8 +195,8 @@ describe('PersistentUserCallout', () => {
return waitForPromises().then(() => {
expect(window.location.assign).toBeCalledWith(href);
- expect(mockAxios.history.post[0].data).toBe(JSON.stringify({ feature_name: featureName }));
expect(persistentUserCallout.container.remove).not.toHaveBeenCalled();
+ expect(mockAxios.history.post[0].data).toBe(JSON.stringify({ feature_name: featureName }));
});
});
diff --git a/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js
index bc77b7045eb..b54feea6ff7 100644
--- a/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js
+++ b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js
@@ -1,17 +1,20 @@
import VueApollo from 'vue-apollo';
import { GlFormTextarea, GlFormInput, GlLoadingIcon } from '@gitlab/ui';
-import { createLocalVue, mount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { objectToQuery, redirectTo } from '~/lib/utils/url_utility';
import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue';
import CommitSection from '~/pipeline_editor/components/commit/commit_section.vue';
import {
COMMIT_ACTION_CREATE,
COMMIT_ACTION_UPDATE,
COMMIT_SUCCESS,
+ COMMIT_SUCCESS_WITH_REDIRECT,
} from '~/pipeline_editor/constants';
+import { resolvers } from '~/pipeline_editor/graphql/resolvers';
import commitCreate from '~/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql';
+import getCurrentBranch from '~/pipeline_editor/graphql/queries/client/current_branch.query.graphql';
import updatePipelineEtag from '~/pipeline_editor/graphql/mutations/client/update_pipeline_etag.mutation.graphql';
import {
@@ -23,18 +26,8 @@ import {
mockCommitMessage,
mockDefaultBranch,
mockProjectFullPath,
- mockNewMergeRequestPath,
} from '../../mock_data';
-const localVue = createLocalVue();
-
-jest.mock('~/lib/utils/url_utility', () => ({
- redirectTo: jest.fn(),
- refreshCurrentPage: jest.fn(),
- objectToQuery: jest.requireActual('~/lib/utils/url_utility').objectToQuery,
- mergeUrlParams: jest.requireActual('~/lib/utils/url_utility').mergeUrlParams,
-}));
-
const mockVariables = {
action: COMMIT_ACTION_UPDATE,
projectPath: mockProjectFullPath,
@@ -48,7 +41,6 @@ const mockVariables = {
const mockProvide = {
ciConfigPath: mockCiConfigPath,
projectFullPath: mockProjectFullPath,
- newMergeRequestPath: mockNewMergeRequestPath,
};
describe('Pipeline Editor | Commit section', () => {
@@ -79,11 +71,23 @@ describe('Pipeline Editor | Commit section', () => {
const createComponentWithApollo = (options) => {
const handlers = [[commitCreate, mockMutateCommitData]];
- localVue.use(VueApollo);
- mockApollo = createMockApollo(handlers);
+ Vue.use(VueApollo);
+ mockApollo = createMockApollo(handlers, resolvers);
+
+ mockApollo.clients.defaultClient.cache.writeQuery({
+ query: getCurrentBranch,
+ data: {
+ workBranches: {
+ __typename: 'BranchList',
+ current: {
+ __typename: 'WorkBranch',
+ name: mockDefaultBranch,
+ },
+ },
+ },
+ });
const apolloConfig = {
- localVue,
apolloProvider: mockApollo,
};
@@ -209,20 +213,23 @@ describe('Pipeline Editor | Commit section', () => {
const newBranch = 'new-branch';
beforeEach(async () => {
+ mockMutateCommitData.mockResolvedValue(mockCommitCreateResponse);
createComponentWithApollo();
+ mockMutateCommitData.mockResolvedValue(mockCommitCreateResponse);
await submitCommit({
branch: newBranch,
openMergeRequest: true,
});
});
- it('redirects to the merge request page with source and target branches', () => {
- const branchesQuery = objectToQuery({
- 'merge_request[source_branch]': newBranch,
- 'merge_request[target_branch]': mockDefaultBranch,
- });
-
- expect(redirectTo).toHaveBeenCalledWith(`${mockNewMergeRequestPath}?${branchesQuery}`);
+ it('emits a commit event with the right type, sourceBranch and targetBranch', () => {
+ expect(wrapper.emitted('commit')).toBeTruthy();
+ expect(wrapper.emitted('commit')[0]).toMatchObject([
+ {
+ type: COMMIT_SUCCESS_WITH_REDIRECT,
+ params: { sourceBranch: newBranch, targetBranch: mockDefaultBranch },
+ },
+ ]);
});
});
diff --git a/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js b/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js
index ab9027a56a4..7dbacad34bf 100644
--- a/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js
+++ b/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js
@@ -12,6 +12,10 @@ import waitForPromises from 'helpers/wait_for_promises';
import BranchSwitcher from '~/pipeline_editor/components/file_nav/branch_switcher.vue';
import { DEFAULT_FAILURE } from '~/pipeline_editor/constants';
import getAvailableBranchesQuery from '~/pipeline_editor/graphql/queries/available_branches.query.graphql';
+import getCurrentBranch from '~/pipeline_editor/graphql/queries/client/current_branch.query.graphql';
+import getLastCommitBranch from '~/pipeline_editor/graphql/queries/client/last_commit_branch.query.graphql';
+import { resolvers } from '~/pipeline_editor/graphql/resolvers';
+
import {
mockBranchPaginationLimit,
mockDefaultBranch,
@@ -34,6 +38,7 @@ describe('Pipeline editor branch switcher', () => {
const createComponent = ({
currentBranch = mockDefaultBranch,
+ availableBranches = ['main'],
isQueryLoading = false,
mountFn = shallowMount,
options = {},
@@ -59,7 +64,7 @@ describe('Pipeline editor branch switcher', () => {
},
data() {
return {
- availableBranches: ['main'],
+ availableBranches,
currentBranch,
};
},
@@ -67,13 +72,44 @@ describe('Pipeline editor branch switcher', () => {
});
};
- const createComponentWithApollo = ({ mountFn = shallowMount, props = {} } = {}) => {
+ const createComponentWithApollo = ({
+ mountFn = shallowMount,
+ props = {},
+ availableBranches = ['main'],
+ } = {}) => {
const handlers = [[getAvailableBranchesQuery, mockAvailableBranchQuery]];
- mockApollo = createMockApollo(handlers);
+ mockApollo = createMockApollo(handlers, resolvers);
+
+ mockApollo.clients.defaultClient.cache.writeQuery({
+ query: getCurrentBranch,
+ data: {
+ workBranches: {
+ __typename: 'BranchList',
+ current: {
+ __typename: 'WorkBranch',
+ name: mockDefaultBranch,
+ },
+ },
+ },
+ });
+
+ mockApollo.clients.defaultClient.cache.writeQuery({
+ query: getLastCommitBranch,
+ data: {
+ workBranches: {
+ __typename: 'BranchList',
+ lastCommit: {
+ __typename: 'WorkBranch',
+ name: '',
+ },
+ },
+ },
+ });
createComponent({
mountFn,
props,
+ availableBranches,
options: {
localVue,
apolloProvider: mockApollo,
@@ -113,7 +149,7 @@ describe('Pipeline editor branch switcher', () => {
describe('when querying for the first time', () => {
beforeEach(() => {
- createComponentWithApollo();
+ createComponentWithApollo({ availableBranches: [] });
});
it('disables the dropdown', () => {
@@ -153,7 +189,7 @@ describe('Pipeline editor branch switcher', () => {
describe('on fetch error', () => {
beforeEach(async () => {
setAvailableBranchesMock(new Error());
- createComponentWithApollo();
+ createComponentWithApollo({ availableBranches: [] });
await waitForPromises();
});
diff --git a/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js b/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js
index c101b1d21c7..35315db39f8 100644
--- a/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js
+++ b/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js
@@ -1,5 +1,6 @@
import { GlIcon, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -8,8 +9,7 @@ import getPipelineQuery from '~/pipeline_editor/graphql/queries/pipeline.query.g
import PipelineEditorMiniGraph from '~/pipeline_editor/components/header/pipeline_editor_mini_graph.vue';
import { mockCommitSha, mockProjectPipeline, mockProjectFullPath } from '../../mock_data';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
describe('Pipeline Status', () => {
let wrapper;
@@ -21,7 +21,6 @@ describe('Pipeline Status', () => {
mockApollo = createMockApollo(handlers);
wrapper = shallowMount(PipelineStatus, {
- localVue,
apolloProvider: mockApollo,
propsData: {
commitSha: mockCommitSha,
@@ -70,13 +69,13 @@ describe('Pipeline Status', () => {
describe('when querying data', () => {
describe('when data is set', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mockPipelineQuery.mockResolvedValue({
data: { project: mockProjectPipeline() },
});
createComponentWithApollo();
- waitForPromises();
+ await waitForPromises();
});
it('query is called with correct variables', async () => {
diff --git a/spec/frontend/pipeline_editor/components/header/pipline_editor_mini_graph_spec.js b/spec/frontend/pipeline_editor/components/header/pipline_editor_mini_graph_spec.js
index 6b9f576917f..93eb18c90cf 100644
--- a/spec/frontend/pipeline_editor/components/header/pipline_editor_mini_graph_spec.js
+++ b/spec/frontend/pipeline_editor/components/header/pipline_editor_mini_graph_spec.js
@@ -1,14 +1,15 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import PipelineEditorMiniGraph from '~/pipeline_editor/components/header/pipeline_editor_mini_graph.vue';
import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql';
import { PIPELINE_FAILURE } from '~/pipeline_editor/constants';
import { mockLinkedPipelines, mockProjectFullPath, mockProjectPipeline } from '../../mock_data';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
describe('Pipeline Status', () => {
let wrapper;
@@ -35,7 +36,6 @@ describe('Pipeline Status', () => {
createComponent({
hasStages,
options: {
- localVue,
apolloProvider: mockApollo,
},
});
@@ -89,9 +89,10 @@ describe('Pipeline Status', () => {
});
describe('when query fails', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mockLinkedPipelinesQuery.mockRejectedValue(new Error());
createComponentWithApollo();
+ await waitForPromises();
});
it('should emit an error event when query fails', async () => {
diff --git a/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js b/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js
index 570323826d1..1ad621e6f45 100644
--- a/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js
+++ b/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js
@@ -1,6 +1,7 @@
import VueApollo from 'vue-apollo';
import { GlIcon } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import { escape } from 'lodash';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -24,8 +25,7 @@ import {
mockYmlHelpPagePath,
} from '../../mock_data';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
describe('Validation segment component', () => {
let wrapper;
@@ -45,7 +45,6 @@ describe('Validation segment component', () => {
wrapper = extendedWrapper(
shallowMount(ValidationSegment, {
- localVue,
apolloProvider: mockApollo,
provide: {
ymlHelpPagePath: mockYmlHelpPagePath,
diff --git a/spec/frontend/pipeline_editor/components/ui/pipeline_editor_messages_spec.js b/spec/frontend/pipeline_editor/components/ui/pipeline_editor_messages_spec.js
index a55176ccd79..d9ecee31e83 100644
--- a/spec/frontend/pipeline_editor/components/ui/pipeline_editor_messages_spec.js
+++ b/spec/frontend/pipeline_editor/components/ui/pipeline_editor_messages_spec.js
@@ -8,6 +8,7 @@ import PipelineEditorMessages from '~/pipeline_editor/components/ui/pipeline_edi
import {
COMMIT_FAILURE,
COMMIT_SUCCESS,
+ COMMIT_SUCCESS_WITH_REDIRECT,
DEFAULT_FAILURE,
DEFAULT_SUCCESS,
LOAD_FAILURE_UNKNOWN,
@@ -34,7 +35,13 @@ describe('Pipeline Editor messages', () => {
it('shows a message for successful commit type', () => {
createComponent({ successType: COMMIT_SUCCESS, showSuccess: true });
- expect(findAlert().text()).toBe(wrapper.vm.$options.successTexts[COMMIT_SUCCESS]);
+ expect(findAlert().text()).toBe(wrapper.vm.$options.success[COMMIT_SUCCESS]);
+ });
+
+ it('shows a message for successful commit with redirect type', () => {
+ createComponent({ successType: COMMIT_SUCCESS_WITH_REDIRECT, showSuccess: true });
+
+ expect(findAlert().text()).toBe(wrapper.vm.$options.success[COMMIT_SUCCESS_WITH_REDIRECT]);
});
it('does not show alert when there is a successType but visibility is off', () => {
@@ -46,7 +53,7 @@ describe('Pipeline Editor messages', () => {
it('shows a success alert with default copy if `showSuccess` is true and the `successType` is not valid,', () => {
createComponent({ successType: 'random', showSuccess: true });
- expect(findAlert().text()).toBe(wrapper.vm.$options.successTexts[DEFAULT_SUCCESS]);
+ expect(findAlert().text()).toBe(wrapper.vm.$options.success[DEFAULT_SUCCESS]);
});
it('emit `hide-success` event when clicking on the dismiss button', async () => {
@@ -71,7 +78,7 @@ describe('Pipeline Editor messages', () => {
`('shows a message for $message', ({ failureType, expectedFailureType }) => {
createComponent({ failureType, showFailure: true });
- expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts[expectedFailureType]);
+ expect(findAlert().text()).toBe(wrapper.vm.$options.errors[expectedFailureType]);
});
it('show failure reasons when there are some', () => {
diff --git a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
index 63eca253c48..0a2c03b7850 100644
--- a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
+++ b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
@@ -5,6 +5,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import waitForPromises from 'helpers/wait_for_promises';
+import { objectToQuery, redirectTo } from '~/lib/utils/url_utility';
import { resolvers } from '~/pipeline_editor/graphql/resolvers';
import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue';
import PipelineEditorEmptyState from '~/pipeline_editor/components/ui/pipeline_editor_empty_state.vue';
@@ -13,7 +14,11 @@ import PipelineEditorHeader from '~/pipeline_editor/components/header/pipeline_e
import ValidationSegment, {
i18n as validationSegmenti18n,
} from '~/pipeline_editor/components/header/validation_segment.vue';
-import { COMMIT_SUCCESS, COMMIT_FAILURE } from '~/pipeline_editor/constants';
+import {
+ COMMIT_SUCCESS,
+ COMMIT_SUCCESS_WITH_REDIRECT,
+ COMMIT_FAILURE,
+} from '~/pipeline_editor/constants';
import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.query.graphql';
import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.query.graphql';
import getTemplate from '~/pipeline_editor/graphql/queries/get_starter_template.query.graphql';
@@ -35,15 +40,22 @@ import {
mockDefaultBranch,
mockEmptyCommitShaResults,
mockNewCommitShaResults,
+ mockNewMergeRequestPath,
mockProjectFullPath,
} from './mock_data';
+jest.mock('~/lib/utils/url_utility', () => ({
+ ...jest.requireActual('~/lib/utils/url_utility'),
+ redirectTo: jest.fn(),
+}));
+
const localVue = createLocalVue();
localVue.use(VueApollo);
const mockProvide = {
ciConfigPath: mockCiConfigPath,
defaultBranch: mockDefaultBranch,
+ newMergeRequestPath: mockNewMergeRequestPath,
projectFullPath: mockProjectFullPath,
};
@@ -311,6 +323,28 @@ describe('Pipeline editor app component', () => {
});
});
+ describe('when the commit succeeds with a redirect', () => {
+ const newBranch = 'new-branch';
+
+ beforeEach(async () => {
+ await createComponentWithApollo({ stubs: { PipelineEditorMessages } });
+
+ findEditorHome().vm.$emit('commit', {
+ type: COMMIT_SUCCESS_WITH_REDIRECT,
+ params: { sourceBranch: newBranch, targetBranch: mockDefaultBranch },
+ });
+ });
+
+ it('redirects to the merge request page with source and target branches', () => {
+ const branchesQuery = objectToQuery({
+ 'merge_request[source_branch]': newBranch,
+ 'merge_request[target_branch]': mockDefaultBranch,
+ });
+
+ expect(redirectTo).toHaveBeenCalledWith(`${mockNewMergeRequestPath}?${branchesQuery}`);
+ });
+ });
+
describe('and the commit mutation fails', () => {
const commitFailedReasons = ['Commit failed'];
diff --git a/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js b/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
index 9e2bf1bd367..eec55091efa 100644
--- a/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
+++ b/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
@@ -1,6 +1,7 @@
import { GlForm, GlSprintf, GlLoadingIcon } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import CreditCardValidationRequiredAlert from 'ee_component/billings/components/cc_validation_required_alert.vue';
import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
@@ -122,7 +123,7 @@ describe('Pipeline New Form', () => {
it('removes ci variable row on remove icon button click', async () => {
findRemoveIcons().at(1).trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findVariableRows()).toHaveLength(2);
});
@@ -132,7 +133,7 @@ describe('Pipeline New Form', () => {
input.element.value = 'test_var_2';
input.trigger('change');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findVariableRows()).toHaveLength(4);
expect(findKeyInputs().at(3).element.value).toBe('');
@@ -205,7 +206,7 @@ describe('Pipeline New Form', () => {
mainInput.element.value = 'build_var';
mainInput.trigger('change');
- await wrapper.vm.$nextTick();
+ await nextTick();
selectBranch('branch-1');
@@ -215,7 +216,7 @@ describe('Pipeline New Form', () => {
branchOneInput.element.value = 'deploy_var';
branchOneInput.trigger('change');
- await wrapper.vm.$nextTick();
+ await nextTick();
selectBranch('main');
@@ -309,7 +310,7 @@ describe('Pipeline New Form', () => {
findKeyInputs().at(0).element.value = 'yml_var_modified';
findKeyInputs().at(0).trigger('change');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findVariableRows().at(0).text()).not.toContain(mockYmlDesc);
});
@@ -418,7 +419,7 @@ describe('Pipeline New Form', () => {
findCCAlert().vm.$emit('dismiss');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findCCAlert().exists()).toBe(false);
expect(wrapper.vm.$data.error).toBe(null);
diff --git a/spec/frontend/pipeline_wizard/components/commit_spec.js b/spec/frontend/pipeline_wizard/components/commit_spec.js
new file mode 100644
index 00000000000..6496850b028
--- /dev/null
+++ b/spec/frontend/pipeline_wizard/components/commit_spec.js
@@ -0,0 +1,282 @@
+import { GlButton, GlFormGroup } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { __, s__, sprintf } from '~/locale';
+import { mountExtended } from 'jest/__helpers__/vue_test_utils_helper';
+import CommitStep, { i18n } from '~/pipeline_wizard/components/commit.vue';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import createCommitMutation from '~/pipeline_wizard/queries/create_commit.graphql';
+import getFileMetadataQuery from '~/pipeline_wizard/queries/get_file_meta.graphql';
+import RefSelector from '~/ref/components/ref_selector.vue';
+import flushPromises from 'helpers/flush_promises';
+import {
+ createCommitMutationErrorResult,
+ createCommitMutationResult,
+ fileQueryErrorResult,
+ fileQueryResult,
+ fileQueryEmptyResult,
+} from '../mock/query_responses';
+
+Vue.use(VueApollo);
+
+const COMMIT_MESSAGE_ADD_FILE = s__('PipelineWizardDefaultCommitMessage|Add %{filename}');
+const COMMIT_MESSAGE_UPDATE_FILE = s__('PipelineWizardDefaultCommitMessage|Update %{filename}');
+
+describe('Pipeline Wizard - Commit Page', () => {
+ const createCommitMutationHandler = jest.fn();
+ const $toast = {
+ show: jest.fn(),
+ };
+
+ let wrapper;
+
+ const getMockApollo = (scenario = {}) => {
+ return createMockApollo([
+ [
+ createCommitMutation,
+ createCommitMutationHandler.mockResolvedValue(
+ scenario.commitHasError ? createCommitMutationErrorResult : createCommitMutationResult,
+ ),
+ ],
+ [
+ getFileMetadataQuery,
+ (vars) => {
+ if (scenario.fileResultByRef) return scenario.fileResultByRef[vars.ref];
+ if (scenario.hasError) return fileQueryErrorResult;
+ return scenario.fileExists ? fileQueryResult : fileQueryEmptyResult;
+ },
+ ],
+ ]);
+ };
+ const createComponent = (props = {}, mockApollo = getMockApollo()) => {
+ wrapper = mountExtended(CommitStep, {
+ apolloProvider: mockApollo,
+ propsData: {
+ projectPath: 'some/path',
+ defaultBranch: 'main',
+ filename: 'newFile.yml',
+ ...props,
+ },
+ mocks: { $toast },
+ stubs: {
+ RefSelector: true,
+ GlFormGroup,
+ },
+ });
+ };
+
+ function getButtonWithLabel(label) {
+ return wrapper.findAllComponents(GlButton).filter((n) => n.text().match(label));
+ }
+
+ describe('ui setup', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('shows a commit message input with the correct label', () => {
+ expect(wrapper.findByTestId('commit_message').exists()).toBe(true);
+ expect(wrapper.find('label[for="commit_message"]').text()).toBe(i18n.commitMessageLabel);
+ });
+
+ it('shows a branch selector with the correct label', () => {
+ expect(wrapper.findByTestId('branch').exists()).toBe(true);
+ expect(wrapper.find('label[for="branch"]').text()).toBe(i18n.branchSelectorLabel);
+ });
+
+ it('shows a commit button', () => {
+ expect(getButtonWithLabel(i18n.commitButtonLabel).exists()).toBe(true);
+ });
+
+ it('shows a back button', () => {
+ expect(getButtonWithLabel(__('Back')).exists()).toBe(true);
+ });
+
+ it('does not show a next button', () => {
+ expect(getButtonWithLabel(__('Next')).exists()).toBe(false);
+ });
+ });
+
+ describe('loading the remote file', () => {
+ const projectPath = 'foo/bar';
+ const filename = 'foo.yml';
+
+ it('does not show a load error if call is successful', async () => {
+ createComponent({ projectPath, filename });
+ await flushPromises();
+ expect(wrapper.findByTestId('load-error').exists()).not.toBe(true);
+ });
+
+ it('shows a load error if call returns an unexpected error', async () => {
+ const branch = 'foo';
+ createComponent(
+ { defaultBranch: branch, projectPath, filename },
+ createMockApollo([[getFileMetadataQuery, () => fileQueryErrorResult]]),
+ );
+ await flushPromises();
+ expect(wrapper.findByTestId('load-error').exists()).toBe(true);
+ expect(wrapper.findByTestId('load-error').text()).toBe(i18n.errors.loadError);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+ });
+
+ describe('commit result handling', () => {
+ describe('successful commit', () => {
+ beforeEach(async () => {
+ createComponent();
+ await flushPromises();
+ await getButtonWithLabel(__('Commit')).trigger('click');
+ await flushPromises();
+ });
+
+ it('will not show an error', async () => {
+ expect(wrapper.findByTestId('commit-error').exists()).not.toBe(true);
+ });
+
+ it('will show a toast message', () => {
+ expect($toast.show).toHaveBeenCalledWith(
+ s__('PipelineWizard|The file has been committed.'),
+ );
+ });
+
+ it('emits a done event', () => {
+ expect(wrapper.emitted().done.length).toBe(1);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ jest.clearAllMocks();
+ });
+ });
+
+ describe('failed commit', () => {
+ beforeEach(async () => {
+ createComponent({}, getMockApollo({ commitHasError: true }));
+ await flushPromises();
+ await getButtonWithLabel(__('Commit')).trigger('click');
+ await flushPromises();
+ });
+
+ it('will show an error', async () => {
+ expect(wrapper.findByTestId('commit-error').exists()).toBe(true);
+ expect(wrapper.findByTestId('commit-error').text()).toBe(i18n.errors.commitError);
+ });
+
+ it('will not show a toast message', () => {
+ expect($toast.show).not.toHaveBeenCalledWith(i18n.commitSuccessMessage);
+ });
+
+ it('will not emit a done event', () => {
+ expect(wrapper.emitted().done?.length).toBeFalsy();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ jest.clearAllMocks();
+ });
+ });
+ });
+
+ describe('modelling different input combinations', () => {
+ const projectPath = 'some/path';
+ const defaultBranch = 'foo';
+ const fileContent = 'foo: bar';
+
+ describe.each`
+ filename | fileExistsOnDefaultBranch | fileExistsOnInputtedBranch | fileLoadError | commitMessageInputValue | branchInputValue | expectedCommitBranch | expectedCommitMessage | expectedAction
+ ${'foo.yml'} | ${false} | ${undefined} | ${false} | ${'foo'} | ${undefined} | ${defaultBranch} | ${'foo'} | ${'CREATE'}
+ ${'foo.yml'} | ${true} | ${undefined} | ${false} | ${'foo'} | ${undefined} | ${defaultBranch} | ${'foo'} | ${'UPDATE'}
+ ${'foo.yml'} | ${false} | ${true} | ${false} | ${'foo'} | ${'dev'} | ${'dev'} | ${'foo'} | ${'UPDATE'}
+ ${'foo.yml'} | ${false} | ${undefined} | ${false} | ${null} | ${undefined} | ${defaultBranch} | ${COMMIT_MESSAGE_ADD_FILE} | ${'CREATE'}
+ ${'foo.yml'} | ${true} | ${undefined} | ${false} | ${null} | ${undefined} | ${defaultBranch} | ${COMMIT_MESSAGE_UPDATE_FILE} | ${'UPDATE'}
+ ${'foo.yml'} | ${false} | ${true} | ${false} | ${null} | ${'dev'} | ${'dev'} | ${COMMIT_MESSAGE_UPDATE_FILE} | ${'UPDATE'}
+ `(
+ 'Test with fileExistsOnDefaultBranch=$fileExistsOnDefaultBranch, fileExistsOnInputtedBranch=$fileExistsOnInputtedBranch, commitMessageInputValue=$commitMessageInputValue, branchInputValue=$branchInputValue, commitReturnsError=$commitReturnsError',
+ ({
+ filename,
+ fileExistsOnDefaultBranch,
+ fileExistsOnInputtedBranch,
+ commitMessageInputValue,
+ branchInputValue,
+ expectedCommitBranch,
+ expectedCommitMessage,
+ expectedAction,
+ }) => {
+ let consoleSpy;
+
+ beforeAll(async () => {
+ createComponent(
+ {
+ filename,
+ defaultBranch,
+ projectPath,
+ fileContent,
+ },
+ getMockApollo({
+ fileResultByRef: {
+ [defaultBranch]: fileExistsOnDefaultBranch ? fileQueryResult : fileQueryEmptyResult,
+ [branchInputValue]: fileExistsOnInputtedBranch
+ ? fileQueryResult
+ : fileQueryEmptyResult,
+ },
+ }),
+ );
+
+ await flushPromises();
+
+ consoleSpy = jest.spyOn(console, 'error');
+
+ await wrapper
+ .findByTestId('commit_message')
+ .get('textarea')
+ .setValue(commitMessageInputValue);
+
+ if (branchInputValue) {
+ await wrapper.getComponent(RefSelector).vm.$emit('input', branchInputValue);
+ }
+ await Vue.nextTick();
+
+ await flushPromises();
+ });
+
+ afterAll(() => {
+ wrapper.destroy();
+ });
+
+ it('sets up without error', async () => {
+ expect(consoleSpy).not.toHaveBeenCalled();
+ });
+
+ it('does not show a load error', async () => {
+ expect(wrapper.findByTestId('load-error').exists()).not.toBe(true);
+ });
+
+ it('sends the expected commit mutation', async () => {
+ await getButtonWithLabel(__('Commit')).trigger('click');
+
+ expect(createCommitMutationHandler).toHaveBeenCalledWith({
+ input: {
+ actions: [
+ {
+ action: expectedAction,
+ content: fileContent,
+ filePath: `/${filename}`,
+ },
+ ],
+ branch: expectedCommitBranch,
+ message: sprintf(expectedCommitMessage, { filename }),
+ projectPath,
+ },
+ });
+ });
+ },
+ );
+ });
+});
diff --git a/spec/frontend/pipeline_wizard/components/editor_spec.js b/spec/frontend/pipeline_wizard/components/editor_spec.js
new file mode 100644
index 00000000000..446412a4f02
--- /dev/null
+++ b/spec/frontend/pipeline_wizard/components/editor_spec.js
@@ -0,0 +1,69 @@
+import { mount } from '@vue/test-utils';
+import { Document } from 'yaml';
+import YamlEditor from '~/pipeline_wizard/components/editor.vue';
+
+describe('Pages Yaml Editor wrapper', () => {
+ const defaultOptions = {
+ propsData: { doc: new Document({ foo: 'bar' }), filename: 'foo.yml' },
+ };
+
+ describe('mount hook', () => {
+ const wrapper = mount(YamlEditor, defaultOptions);
+
+ it('editor is mounted', () => {
+ expect(wrapper.vm.editor).not.toBeFalsy();
+ expect(wrapper.find('.gl-source-editor').exists()).toBe(true);
+ });
+ });
+
+ describe('watchers', () => {
+ describe('doc', () => {
+ const doc = new Document({ baz: ['bar'] });
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = mount(YamlEditor, defaultOptions);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it("causes the editor's value to be set to the stringified document", async () => {
+ await wrapper.setProps({ doc });
+ expect(wrapper.vm.editor.getValue()).toEqual(doc.toString());
+ });
+
+ it('emits an update:yaml event with the yaml representation of doc', async () => {
+ await wrapper.setProps({ doc });
+ const changeEvents = wrapper.emitted('update:yaml');
+ expect(changeEvents[2]).toEqual([doc.toString()]);
+ });
+
+ it('does not cause the touch event to be emitted', () => {
+ wrapper.setProps({ doc });
+ expect(wrapper.emitted('touch')).not.toBeTruthy();
+ });
+ });
+
+ describe('highlight', () => {
+ const highlight = 'foo';
+ const wrapper = mount(YamlEditor, defaultOptions);
+
+ it('calls editor.highlight(path, keep=true)', async () => {
+ const highlightSpy = jest.spyOn(wrapper.vm.yamlEditorExtension.obj, 'highlight');
+ await wrapper.setProps({ highlight });
+ expect(highlightSpy).toHaveBeenCalledWith(expect.anything(), highlight, true);
+ });
+ });
+ });
+
+ describe('events', () => {
+ const wrapper = mount(YamlEditor, defaultOptions);
+
+ it('emits touch if content is changed in editor', async () => {
+ await wrapper.vm.editor.setValue('foo: boo');
+ expect(wrapper.emitted('touch')).toBeTruthy();
+ });
+ });
+});
diff --git a/spec/frontend/pipeline_wizard/components/step_nav_spec.js b/spec/frontend/pipeline_wizard/components/step_nav_spec.js
new file mode 100644
index 00000000000..c6eac1386fa
--- /dev/null
+++ b/spec/frontend/pipeline_wizard/components/step_nav_spec.js
@@ -0,0 +1,79 @@
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import StepNav from '~/pipeline_wizard/components/step_nav.vue';
+
+describe('Pipeline Wizard - Step Navigation Component', () => {
+ const defaultProps = { showBackButton: true, showNextButton: true };
+
+ let wrapper;
+ let prevButton;
+ let nextButton;
+
+ const createComponent = (props = {}) => {
+ wrapper = mountExtended(StepNav, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ prevButton = wrapper.findByTestId('back-button');
+ nextButton = wrapper.findByTestId('next-button');
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it.each`
+ scenario | showBackButton | showNextButton
+ ${'does not show prev button'} | ${false} | ${false}
+ ${'has prev, but not next'} | ${true} | ${false}
+ ${'has next, but not prev'} | ${false} | ${true}
+ ${'has both next and prev'} | ${true} | ${true}
+ `('$scenario', async ({ showBackButton, showNextButton }) => {
+ createComponent({ showBackButton, showNextButton });
+
+ expect(prevButton.exists()).toBe(showBackButton);
+ expect(nextButton.exists()).toBe(showNextButton);
+ });
+
+ it('shows the expected button text', () => {
+ createComponent();
+
+ expect(prevButton.text()).toBe('Back');
+ expect(nextButton.text()).toBe('Next');
+ });
+
+ it('emits "back" events when clicking prev button', async () => {
+ createComponent();
+
+ await prevButton.trigger('click');
+ expect(wrapper.emitted().back.length).toBe(1);
+ });
+
+ it('emits "next" events when clicking next button', async () => {
+ createComponent();
+
+ await nextButton.trigger('click');
+ expect(wrapper.emitted().next.length).toBe(1);
+ });
+
+ it('enables the next button if nextButtonEnabled ist set to true', async () => {
+ createComponent({ nextButtonEnabled: true });
+
+ expect(nextButton.attributes('disabled')).not.toBe('disabled');
+ });
+
+ it('disables the next button if nextButtonEnabled ist set to false', async () => {
+ createComponent({ nextButtonEnabled: false });
+
+ expect(nextButton.attributes('disabled')).toBe('disabled');
+ });
+
+ it('does not emit "next" event when clicking next button while nextButtonEnabled ist set to false', async () => {
+ createComponent({ nextButtonEnabled: false });
+
+ await nextButton.trigger('click');
+
+ expect(wrapper.emitted().next).toBe(undefined);
+ });
+});
diff --git a/spec/frontend/pipeline_wizard/components/widgets/text_spec.js b/spec/frontend/pipeline_wizard/components/widgets/text_spec.js
new file mode 100644
index 00000000000..a11c0214d15
--- /dev/null
+++ b/spec/frontend/pipeline_wizard/components/widgets/text_spec.js
@@ -0,0 +1,152 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlFormGroup, GlFormInput } from '@gitlab/ui';
+import TextWidget from '~/pipeline_wizard/components/widgets/text.vue';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+
+describe('Pipeline Wizard - Text Widget', () => {
+ const defaultProps = {
+ label: 'This label',
+ description: 'some description',
+ placeholder: 'some placeholder',
+ pattern: '^[a-z]+$',
+ invalidFeedback: 'some feedback',
+ };
+
+ let wrapper;
+
+ const findGlFormGroup = () => wrapper.findComponent(GlFormGroup);
+ const findGlFormGroupInvalidFeedback = () => findGlFormGroup().find('.invalid-feedback');
+ const findGlFormInput = () => wrapper.findComponent(GlFormInput);
+
+ const createComponent = (props = {}, mountFn = mountExtended) => {
+ wrapper = mountFn(TextWidget, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ it('creates an input element with the correct label', () => {
+ createComponent();
+
+ expect(wrapper.findByLabelText(defaultProps.label).exists()).toBe(true);
+ });
+
+ it('passes the description', () => {
+ createComponent({}, shallowMount);
+
+ expect(findGlFormGroup().attributes('description')).toBe(defaultProps.description);
+ });
+
+ it('sets the "text" type on the input component', () => {
+ createComponent();
+
+ expect(findGlFormInput().attributes('type')).toBe('text');
+ });
+
+ it('passes the placeholder', () => {
+ createComponent();
+
+ expect(findGlFormInput().attributes('placeholder')).toBe(defaultProps.placeholder);
+ });
+
+ it('emits an update event on input', async () => {
+ createComponent();
+
+ const localValue = 'somevalue';
+ await findGlFormInput().setValue(localValue);
+
+ expect(wrapper.emitted('input')).toEqual([[localValue]]);
+ });
+
+ it('passes invalid feedback message', () => {
+ createComponent();
+
+ expect(findGlFormGroupInvalidFeedback().text()).toBe(defaultProps.invalidFeedback);
+ });
+
+ it('provides invalid feedback', async () => {
+ createComponent({ validate: true });
+
+ await findGlFormInput().setValue('invalid%99');
+
+ expect(findGlFormGroup().classes()).toContain('is-invalid');
+ expect(findGlFormInput().classes()).toContain('is-invalid');
+ });
+
+ it('provides valid feedback', async () => {
+ createComponent({ validate: true });
+
+ await findGlFormInput().setValue('valid');
+
+ expect(findGlFormGroup().classes()).toContain('is-valid');
+ expect(findGlFormInput().classes()).toContain('is-valid');
+ });
+
+ it('does not show validation state when untouched', () => {
+ createComponent({ value: 'invalid99' });
+
+ expect(findGlFormGroup().classes()).not.toContain('is-valid');
+ expect(findGlFormGroup().classes()).not.toContain('is-invalid');
+ });
+
+ it('shows invalid state on blur', async () => {
+ createComponent();
+
+ await findGlFormInput().setValue('invalid%99');
+
+ expect(findGlFormGroup().classes()).not.toContain('is-invalid');
+
+ await findGlFormInput().trigger('blur');
+
+ expect(findGlFormInput().classes()).toContain('is-invalid');
+ expect(findGlFormGroup().classes()).toContain('is-invalid');
+ });
+
+ it('shows invalid state when toggling `validate` prop', async () => {
+ createComponent({
+ required: true,
+ validate: false,
+ });
+
+ expect(findGlFormGroup().classes()).not.toContain('is-invalid');
+
+ await wrapper.setProps({ validate: true });
+
+ expect(findGlFormGroup().classes()).toContain('is-invalid');
+ });
+
+ it('does not update validation if not required', async () => {
+ createComponent({
+ pattern: null,
+ validate: true,
+ });
+
+ expect(findGlFormGroup().classes()).not.toContain('is-invalid');
+ });
+
+ it('sets default value', () => {
+ const defaultValue = 'foo';
+ createComponent({
+ default: defaultValue,
+ });
+
+ expect(wrapper.findByLabelText(defaultProps.label).element.value).toBe(defaultValue);
+ });
+
+ it('emits default value on setup', () => {
+ const defaultValue = 'foo';
+ createComponent({
+ default: defaultValue,
+ });
+
+ expect(wrapper.emitted('input')).toEqual([[defaultValue]]);
+ });
+});
diff --git a/spec/frontend/pipeline_wizard/mock/query_responses.js b/spec/frontend/pipeline_wizard/mock/query_responses.js
new file mode 100644
index 00000000000..95dcb881a04
--- /dev/null
+++ b/spec/frontend/pipeline_wizard/mock/query_responses.js
@@ -0,0 +1,62 @@
+export const createCommitMutationResult = {
+ data: {
+ commitCreate: {
+ commit: {
+ id: '82a9df1',
+ },
+ content: 'foo: bar',
+ errors: null,
+ },
+ },
+};
+
+export const createCommitMutationErrorResult = {
+ data: {
+ commitCreate: {
+ commit: null,
+ content: null,
+ errors: ['Some Error Message'],
+ },
+ },
+};
+
+export const fileQueryResult = {
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/1',
+ repository: {
+ blobs: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Blob/9ff96777b315cd37188f7194d8382c718cb2933c',
+ },
+ ],
+ },
+ },
+ },
+ },
+};
+
+export const fileQueryEmptyResult = {
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/2',
+ repository: {
+ blobs: {
+ nodes: [],
+ },
+ },
+ },
+ },
+};
+
+export const fileQueryErrorResult = {
+ data: {
+ foo: 'bar',
+ project: {
+ id: null,
+ repository: null,
+ },
+ },
+ errors: [{ message: 'GraphQL Error' }],
+};
diff --git a/spec/frontend/pipelines/__snapshots__/utils_spec.js.snap b/spec/frontend/pipelines/__snapshots__/utils_spec.js.snap
index 52461885342..2d2e5db598a 100644
--- a/spec/frontend/pipelines/__snapshots__/utils_spec.js.snap
+++ b/spec/frontend/pipelines/__snapshots__/utils_spec.js.snap
@@ -30,6 +30,7 @@ Array [
"hasDetails": true,
"icon": "status_success",
"id": "7",
+ "label": "passed",
"tooltip": "passed",
},
},
@@ -71,6 +72,7 @@ Array [
"hasDetails": true,
"icon": "status_success",
"id": "12",
+ "label": "passed",
"tooltip": "passed",
},
},
@@ -112,6 +114,7 @@ Array [
"hasDetails": true,
"icon": "status_success",
"id": "17",
+ "label": "passed",
"tooltip": "passed",
},
},
@@ -153,6 +156,7 @@ Array [
"hasDetails": true,
"icon": "status_success",
"id": "22",
+ "label": "passed",
"tooltip": "passed",
},
},
@@ -178,6 +182,7 @@ Array [
"hasDetails": true,
"icon": "status_success",
"id": "25",
+ "label": "passed",
"tooltip": "passed",
},
},
@@ -203,6 +208,7 @@ Array [
"hasDetails": true,
"icon": "status_success",
"id": "28",
+ "label": "passed",
"tooltip": "passed",
},
},
@@ -237,6 +243,7 @@ Array [
"hasDetails": true,
"icon": "status_success",
"id": "60",
+ "label": null,
"tooltip": null,
},
},
@@ -295,6 +302,7 @@ Array [
"hasDetails": true,
"icon": "status_success",
"id": "35",
+ "label": "passed",
"tooltip": "passed",
},
},
@@ -348,6 +356,7 @@ Array [
"hasDetails": true,
"icon": "status_success",
"id": "43",
+ "label": "passed",
"tooltip": "passed",
},
},
@@ -385,6 +394,7 @@ Array [
"hasDetails": true,
"icon": "status_success",
"id": "50",
+ "label": "passed",
"tooltip": "passed",
},
},
@@ -423,6 +433,7 @@ Array [
"hasDetails": true,
"icon": "status_success",
"id": "64",
+ "label": null,
"tooltip": null,
},
},
diff --git a/spec/frontend/pipelines/components/dag/dag_annotations_spec.js b/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
index 1941a7f2777..212f8e19a6d 100644
--- a/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import DagAnnotations from '~/pipelines/components/dag/dag_annotations.vue';
import { singleNote, multiNote } from './mock_data';
@@ -82,26 +83,24 @@ describe('The DAG annotations', () => {
});
describe('clicking hide', () => {
- it('hides listed items and changes text to show', () => {
+ it('hides listed items and changes text to show', async () => {
expect(getAllTextBlocks().length).toBe(Object.keys(multiNote).length);
expect(getToggleButton().text()).toBe('Hide list');
getToggleButton().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(getAllTextBlocks().length).toBe(0);
- expect(getToggleButton().text()).toBe('Show list');
- });
+ await nextTick();
+ expect(getAllTextBlocks().length).toBe(0);
+ expect(getToggleButton().text()).toBe('Show list');
});
});
describe('clicking show', () => {
- it('shows listed items and changes text to hide', () => {
+ it('shows listed items and changes text to hide', async () => {
getToggleButton().trigger('click');
getToggleButton().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(getAllTextBlocks().length).toBe(Object.keys(multiNote).length);
- expect(getToggleButton().text()).toBe('Hide list');
- });
+ await nextTick();
+ expect(getAllTextBlocks().length).toBe(Object.keys(multiNote).length);
+ expect(getToggleButton().text()).toBe('Hide list');
});
});
});
diff --git a/spec/frontend/pipelines/components/dag/dag_spec.js b/spec/frontend/pipelines/components/dag/dag_spec.js
index 14030930657..d78df3eb35e 100644
--- a/spec/frontend/pipelines/components/dag/dag_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_spec.js
@@ -1,5 +1,6 @@
import { GlAlert, GlEmptyState } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { ADD_NOTE, REMOVE_NOTE, REPLACE_NOTES } from '~/pipelines/components/dag/constants';
import Dag from '~/pipelines/components/dag/dag.vue';
import DagAnnotations from '~/pipelines/components/dag/dag_annotations.vue';
@@ -153,11 +154,11 @@ describe('Pipeline DAG graph wrapper', () => {
expect(getNotes().exists()).toBe(false);
getGraph().vm.$emit('update-annotation', { type: ADD_NOTE, data: currentNote });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(getNotes().exists()).toBe(true);
getGraph().vm.$emit('update-annotation', { type: REMOVE_NOTE, data: currentNote });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(getNotes().exists()).toBe(false);
});
@@ -165,11 +166,11 @@ describe('Pipeline DAG graph wrapper', () => {
expect(getNotes().exists()).toBe(false);
getGraph().vm.$emit('update-annotation', { type: REPLACE_NOTES, data: multiNote });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(getNotes().exists()).toBe(true);
getGraph().vm.$emit('update-annotation', { type: REPLACE_NOTES, data: {} });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(getNotes().exists()).toBe(false);
});
});
diff --git a/spec/frontend/pipelines/components/jobs/jobs_app_spec.js b/spec/frontend/pipelines/components/jobs/jobs_app_spec.js
index 1ea6096c922..65814ad9a7f 100644
--- a/spec/frontend/pipelines/components/jobs/jobs_app_spec.js
+++ b/spec/frontend/pipelines/components/jobs/jobs_app_spec.js
@@ -1,5 +1,6 @@
import { GlIntersectionObserver, GlSkeletonLoader } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -9,8 +10,7 @@ import JobsTable from '~/jobs/components/table/jobs_table.vue';
import getPipelineJobsQuery from '~/pipelines/graphql/queries/get_pipeline_jobs.query.graphql';
import { mockPipelineJobsQueryResponse } from '../../mock_data';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
jest.mock('~/flash');
@@ -36,7 +36,6 @@ describe('Jobs app', () => {
fullPath: 'root/ci-project',
pipelineIid: 1,
},
- localVue,
apolloProvider: createMockApolloProvider(resolver),
});
};
@@ -74,16 +73,16 @@ describe('Jobs app', () => {
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({
- message: 'An error occured while fetching the pipelines jobs.',
+ message: 'An error occurred while fetching the pipelines jobs.',
});
});
it('handles infinite scrolling by calling fetchMore', async () => {
createComponent(resolverSpy);
-
await waitForPromises();
triggerInfiniteScroll();
+ await waitForPromises();
expect(resolverSpy).toHaveBeenCalledWith({
after: 'eyJpZCI6Ijg0NyJ9',
@@ -96,10 +95,10 @@ describe('Jobs app', () => {
createComponent(resolverSpy);
expect(findSkeletonLoader().exists()).toBe(true);
-
await waitForPromises();
triggerInfiniteScroll();
+ await waitForPromises();
expect(findSkeletonLoader().exists()).toBe(false);
});
diff --git a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
index 661c8d99477..97b59a09518 100644
--- a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
@@ -1,6 +1,7 @@
import { GlFilteredSearch } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import Api from '~/api';
import axios from '~/lib/utils/axios_utils';
import PipelinesFilteredSearch from '~/pipelines/components/pipelines_list/pipelines_filtered_search.vue';
@@ -103,46 +104,42 @@ describe('Pipelines filtered search', () => {
expect(wrapper.emitted('filterPipelines')[0]).toEqual([mockSearch]);
});
- it('disables tag name token when branch name token is active', () => {
+ it('disables tag name token when branch name token is active', async () => {
findFilteredSearch().vm.$emit('input', [
{ type: 'ref', value: { data: 'branch-1', operator: '=' } },
{ type: 'filtered-search-term', value: { data: '' } },
]);
- return wrapper.vm.$nextTick().then(() => {
- expect(findBranchToken().disabled).toBe(false);
- expect(findTagToken().disabled).toBe(true);
- });
+ await nextTick();
+ expect(findBranchToken().disabled).toBe(false);
+ expect(findTagToken().disabled).toBe(true);
});
- it('disables branch name token when tag name token is active', () => {
+ it('disables branch name token when tag name token is active', async () => {
findFilteredSearch().vm.$emit('input', [
{ type: 'tag', value: { data: 'tag-1', operator: '=' } },
{ type: 'filtered-search-term', value: { data: '' } },
]);
- return wrapper.vm.$nextTick().then(() => {
- expect(findBranchToken().disabled).toBe(true);
- expect(findTagToken().disabled).toBe(false);
- });
+ await nextTick();
+ expect(findBranchToken().disabled).toBe(true);
+ expect(findTagToken().disabled).toBe(false);
});
- it('resets tokens disabled state on clear', () => {
+ it('resets tokens disabled state on clear', async () => {
findFilteredSearch().vm.$emit('clearInput');
- return wrapper.vm.$nextTick().then(() => {
- expect(findBranchToken().disabled).toBe(false);
- expect(findTagToken().disabled).toBe(false);
- });
+ await nextTick();
+ expect(findBranchToken().disabled).toBe(false);
+ expect(findTagToken().disabled).toBe(false);
});
- it('resets tokens disabled state when clearing tokens by backspace', () => {
+ it('resets tokens disabled state when clearing tokens by backspace', async () => {
findFilteredSearch().vm.$emit('input', [{ type: 'filtered-search-term', value: { data: '' } }]);
- return wrapper.vm.$nextTick().then(() => {
- expect(findBranchToken().disabled).toBe(false);
- expect(findTagToken().disabled).toBe(false);
- });
+ await nextTick();
+ expect(findBranchToken().disabled).toBe(false);
+ expect(findTagToken().disabled).toBe(false);
});
describe('Url query params', () => {
diff --git a/spec/frontend/pipelines/graph/action_component_spec.js b/spec/frontend/pipelines/graph/action_component_spec.js
index 177b026491c..fab6e6887b7 100644
--- a/spec/frontend/pipelines/graph/action_component_spec.js
+++ b/spec/frontend/pipelines/graph/action_component_spec.js
@@ -1,6 +1,7 @@
import { GlButton } 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 axios from '~/lib/utils/axios_utils';
import ActionComponent from '~/pipelines/components/jobs_shared/action_component.vue';
@@ -9,6 +10,7 @@ describe('pipeline graph action component', () => {
let wrapper;
let mock;
const findButton = () => wrapper.find(GlButton);
+ const findTooltipWrapper = () => wrapper.find('[data-testid="ci-action-icon-tooltip-wrapper"]');
beforeEach(() => {
mock = new MockAdapter(axios);
@@ -30,19 +32,14 @@ describe('pipeline graph action component', () => {
});
it('should render the provided title as a bootstrap tooltip', () => {
- expect(wrapper.attributes('title')).toBe('bar');
+ expect(findTooltipWrapper().attributes('title')).toBe('bar');
});
- it('should update bootstrap tooltip when title changes', (done) => {
+ it('should update bootstrap tooltip when title changes', async () => {
wrapper.setProps({ tooltipText: 'changed' });
- wrapper.vm
- .$nextTick()
- .then(() => {
- expect(wrapper.attributes('title')).toBe('changed');
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(findTooltipWrapper().attributes('title')).toBe('changed');
});
it('should render an svg', () => {
@@ -64,13 +61,11 @@ describe('pipeline graph action component', () => {
.catch(done.fail);
});
- it('renders a loading icon while waiting for request', (done) => {
+ it('renders a loading icon while waiting for request', async () => {
findButton().trigger('click');
- wrapper.vm.$nextTick(() => {
- expect(wrapper.find('.js-action-icon-loading').exists()).toBe(true);
- done();
- });
+ await nextTick();
+ expect(wrapper.find('.js-action-icon-loading').exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
index 04e004dc6c1..8bc6c086b9d 100644
--- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
+++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
@@ -1,10 +1,11 @@
import { GlAlert, GlButton, GlButtonGroup, GlLoadingIcon } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import Vue, { nextTick } from 'vue';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
import getUserCallouts from '~/graphql_shared/queries/get_user_callouts.query.graphql';
import axios from '~/lib/utils/axios_utils';
@@ -100,15 +101,6 @@ describe('Pipeline graph wrapper', () => {
wrapper.destroy();
});
- beforeAll(() => {
- jest.useFakeTimers();
- });
-
- afterAll(() => {
- jest.runOnlyPendingTimers();
- jest.useRealTimers();
- });
-
describe('when data is loading', () => {
it('displays the loading icon', () => {
createComponentWithApollo();
@@ -134,8 +126,7 @@ describe('Pipeline graph wrapper', () => {
describe('when data has loaded', () => {
beforeEach(async () => {
createComponentWithApollo();
- jest.runOnlyPendingTimers();
- await nextTick();
+ await waitForPromises();
});
it('does not display the loading icon', () => {
@@ -163,8 +154,7 @@ describe('Pipeline graph wrapper', () => {
createComponentWithApollo({
getPipelineDetailsHandler: jest.fn().mockRejectedValue(new Error('GraphQL error')),
});
- jest.runOnlyPendingTimers();
- await nextTick();
+ await waitForPromises();
});
it('does not display the loading icon', () => {
@@ -187,8 +177,7 @@ describe('Pipeline graph wrapper', () => {
pipelineIid: '',
},
});
- jest.runOnlyPendingTimers();
- await nextTick();
+ await waitForPromises();
});
it('does not display the loading icon', () => {
@@ -210,7 +199,7 @@ describe('Pipeline graph wrapper', () => {
createComponentWithApollo();
jest.spyOn(wrapper.vm.$apollo.queries.headerPipeline, 'refetch');
jest.spyOn(wrapper.vm.$apollo.queries.pipeline, 'refetch');
- await nextTick();
+ await waitForPromises();
getGraph().vm.$emit('refreshPipelineGraph');
});
@@ -224,8 +213,7 @@ describe('Pipeline graph wrapper', () => {
describe('when query times out', () => {
const advanceApolloTimers = async () => {
jest.runOnlyPendingTimers();
- await nextTick();
- await nextTick();
+ await waitForPromises();
};
beforeEach(async () => {
@@ -245,7 +233,7 @@ describe('Pipeline graph wrapper', () => {
.mockResolvedValueOnce(errorData);
createComponentWithApollo({ getPipelineDetailsHandler: failSucceedFail });
- await nextTick();
+ await waitForPromises();
});
it('shows correct errors and does not overwrite populated data when data is empty', async () => {
@@ -274,8 +262,7 @@ describe('Pipeline graph wrapper', () => {
mountFn: mount,
});
- jest.runOnlyPendingTimers();
- await nextTick();
+ await waitForPromises();
});
it('appears when pipeline uses needs', () => {
@@ -318,7 +305,7 @@ describe('Pipeline graph wrapper', () => {
});
jest.runOnlyPendingTimers();
- await nextTick();
+ await waitForPromises();
});
it('sets showLinks to true', async () => {
@@ -327,8 +314,9 @@ describe('Pipeline graph wrapper', () => {
expect(getLinksLayer().props('showLinks')).toBe(false);
expect(getViewSelector().props('type')).toBe(LAYER_VIEW);
await getDependenciesToggle().vm.$emit('change', true);
+
jest.runOnlyPendingTimers();
- await nextTick();
+ await waitForPromises();
expect(wrapper.findComponent(LinksLayer).props('showLinks')).toBe(true);
});
});
@@ -343,8 +331,7 @@ describe('Pipeline graph wrapper', () => {
mountFn: mount,
});
- jest.runOnlyPendingTimers();
- await nextTick();
+ await waitForPromises();
});
it('shows the hover tip in the view selector', async () => {
@@ -365,7 +352,7 @@ describe('Pipeline graph wrapper', () => {
});
jest.runOnlyPendingTimers();
- await nextTick();
+ await waitForPromises();
});
it('does not show the hover tip', async () => {
@@ -382,8 +369,7 @@ describe('Pipeline graph wrapper', () => {
mountFn: mount,
});
- jest.runOnlyPendingTimers();
- await nextTick();
+ await waitForPromises();
});
afterEach(() => {
@@ -411,8 +397,7 @@ describe('Pipeline graph wrapper', () => {
getPipelineDetailsHandler: jest.fn().mockResolvedValue(nonNeedsResponse),
});
- jest.runOnlyPendingTimers();
- await nextTick();
+ await waitForPromises();
});
afterEach(() => {
@@ -435,7 +420,7 @@ describe('Pipeline graph wrapper', () => {
});
jest.runOnlyPendingTimers();
- await nextTick();
+ await waitForPromises();
});
it('does not appear when pipeline does not use needs', () => {
@@ -461,8 +446,7 @@ describe('Pipeline graph wrapper', () => {
describe('with no metrics path', () => {
beforeEach(async () => {
createComponentWithApollo();
- jest.runOnlyPendingTimers();
- await nextTick();
+ await waitForPromises();
});
it('is not called', () => {
@@ -505,8 +489,7 @@ describe('Pipeline graph wrapper', () => {
},
});
- jest.runOnlyPendingTimers();
- await nextTick();
+ await waitForPromises();
});
it('attempts to collect metrics', () => {
@@ -517,7 +500,7 @@ describe('Pipeline graph wrapper', () => {
});
describe('with duration and no error', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mock = new MockAdapter(axios);
mock.onPost(metricsPath).reply(200, {});
@@ -536,6 +519,7 @@ describe('Pipeline graph wrapper', () => {
currentViewType: LAYER_VIEW,
},
});
+ await waitForPromises();
});
afterEach(() => {
diff --git a/spec/frontend/pipelines/graph/job_item_spec.js b/spec/frontend/pipelines/graph/job_item_spec.js
index 06f1fa4c827..23e7ed7ebb4 100644
--- a/spec/frontend/pipelines/graph/job_item_spec.js
+++ b/spec/frontend/pipelines/graph/job_item_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import JobItem from '~/pipelines/components/graph/job_item.vue';
describe('pipeline graph job item', () => {
@@ -6,6 +7,7 @@ describe('pipeline graph job item', () => {
const findJobWithoutLink = () => wrapper.find('[data-testid="job-without-link"]');
const findJobWithLink = () => wrapper.find('[data-testid="job-with-link"]');
+ const findActionComponent = () => wrapper.find('[data-testid="ci-action-component"]');
const createWrapper = (propsData) => {
wrapper = mount(JobItem, {
@@ -68,28 +70,38 @@ describe('pipeline graph job item', () => {
hasDetails: false,
},
};
+ const mockJobWithUnauthorizedAction = {
+ id: 4258,
+ name: 'stop-environment',
+ status: {
+ icon: 'status_manual',
+ label: 'manual stop action (not allowed)',
+ tooltip: 'manual action',
+ group: 'manual',
+ detailsPath: '/root/ci-mock/builds/4258',
+ hasDetails: true,
+ action: null,
+ },
+ };
afterEach(() => {
wrapper.destroy();
});
describe('name with link', () => {
- it('should render the job name and status with a link', (done) => {
+ it('should render the job name and status with a link', async () => {
createWrapper({ job: mockJob });
- wrapper.vm.$nextTick(() => {
- const link = wrapper.find('a');
-
- expect(link.attributes('href')).toBe(mockJob.status.detailsPath);
+ await nextTick();
+ const link = wrapper.find('a');
- expect(link.attributes('title')).toBe(`${mockJob.name} - ${mockJob.status.label}`);
+ expect(link.attributes('href')).toBe(mockJob.status.detailsPath);
- expect(wrapper.find('.ci-status-icon-success').exists()).toBe(true);
+ expect(link.attributes('title')).toBe(`${mockJob.name} - ${mockJob.status.label}`);
- expect(wrapper.text()).toBe(mockJob.name);
+ expect(wrapper.find('.ci-status-icon-success').exists()).toBe(true);
- done();
- });
+ expect(wrapper.text()).toBe(mockJob.name);
});
});
@@ -118,8 +130,21 @@ describe('pipeline graph job item', () => {
it('it should render the action icon', () => {
createWrapper({ job: mockJob });
- expect(wrapper.find('.ci-action-icon-container').exists()).toBe(true);
- expect(wrapper.find('.ci-action-icon-wrapper').exists()).toBe(true);
+ const actionComponent = findActionComponent();
+
+ expect(actionComponent.exists()).toBe(true);
+ expect(actionComponent.props('actionIcon')).toBe('retry');
+ expect(actionComponent.attributes('disabled')).not.toBe('disabled');
+ });
+
+ it('it should render disabled action icon when user cannot run the action', () => {
+ createWrapper({ job: mockJobWithUnauthorizedAction });
+
+ const actionComponent = findActionComponent();
+
+ expect(actionComponent.exists()).toBe(true);
+ expect(actionComponent.props('actionIcon')).toBe('stop');
+ expect(actionComponent.attributes('disabled')).toBe('disabled');
});
});
diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
index af5cd907dd8..d800a8c341e 100644
--- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
@@ -9,6 +9,23 @@ import mockPipeline from './linked_pipelines_mock_data';
describe('Linked pipeline', () => {
let wrapper;
+ const downstreamProps = {
+ pipeline: {
+ ...mockPipeline,
+ multiproject: false,
+ },
+ columnTitle: 'Downstream',
+ type: DOWNSTREAM,
+ expanded: false,
+ isLoading: false,
+ };
+
+ const upstreamProps = {
+ ...downstreamProps,
+ columnTitle: 'Upstream',
+ type: UPSTREAM,
+ };
+
const findButton = () => wrapper.find(GlButton);
const findDownstreamPipelineTitle = () => wrapper.find('[data-testid="downstream-title"]');
const findPipelineLabel = () => wrapper.find('[data-testid="downstream-pipeline-label"]');
@@ -86,91 +103,65 @@ describe('Linked pipeline', () => {
});
});
- describe('parent/child', () => {
- const downstreamProps = {
- pipeline: {
- ...mockPipeline,
- multiproject: false,
- },
- columnTitle: 'Downstream',
- type: DOWNSTREAM,
- expanded: false,
- isLoading: false,
- };
+ describe('upstream pipelines', () => {
+ beforeEach(() => {
+ createWrapper(upstreamProps);
+ });
- const upstreamProps = {
- ...downstreamProps,
- columnTitle: 'Upstream',
- type: UPSTREAM,
- };
+ it('should display parent label when pipeline project id is the same as triggered_by pipeline project id', () => {
+ expect(findPipelineLabel().exists()).toBe(true);
+ });
- it('parent/child label container should exist', () => {
+ it('upstream pipeline should contain the correct link', () => {
+ expect(findPipelineLink().attributes('href')).toBe(upstreamProps.pipeline.path);
+ });
+
+ it('applies the reverse-row css class to the card', () => {
+ expect(findLinkedPipeline().classes()).toContain('gl-flex-direction-row-reverse');
+ expect(findLinkedPipeline().classes()).not.toContain('gl-flex-direction-row');
+ });
+ });
+
+ describe('downstream pipelines', () => {
+ beforeEach(() => {
createWrapper(downstreamProps);
+ });
+
+ it('parent/child label container should exist', () => {
expect(findPipelineLabel().exists()).toBe(true);
});
it('should display child label when pipeline project id is the same as triggered pipeline project id', () => {
- createWrapper(downstreamProps);
expect(findPipelineLabel().exists()).toBe(true);
});
it('should have the name of the trigger job on the card when it is a child pipeline', () => {
- createWrapper(downstreamProps);
expect(findDownstreamPipelineTitle().text()).toBe(mockPipeline.sourceJob.name);
});
- it('should display parent label when pipeline project id is the same as triggered_by pipeline project id', () => {
- createWrapper(upstreamProps);
- expect(findPipelineLabel().exists()).toBe(true);
- });
-
it('downstream pipeline should contain the correct link', () => {
- createWrapper(downstreamProps);
expect(findPipelineLink().attributes('href')).toBe(downstreamProps.pipeline.path);
});
- it('upstream pipeline should contain the correct link', () => {
- createWrapper(upstreamProps);
- expect(findPipelineLink().attributes('href')).toBe(upstreamProps.pipeline.path);
+ it('applies the flex-row css class to the card', () => {
+ expect(findLinkedPipeline().classes()).toContain('gl-flex-direction-row');
+ expect(findLinkedPipeline().classes()).not.toContain('gl-flex-direction-row-reverse');
});
+ });
+ describe('expand button', () => {
it.each`
- presentClass | missingClass
- ${'gl-right-0'} | ${'gl-left-0'}
- ${'gl-border-l-1!'} | ${'gl-border-r-1!'}
- `(
- 'pipeline expand button should be postioned right when child pipeline',
- ({ presentClass, missingClass }) => {
- createWrapper(downstreamProps);
- expect(findExpandButton().classes()).toContain(presentClass);
- expect(findExpandButton().classes()).not.toContain(missingClass);
- },
- );
-
- it.each`
- presentClass | missingClass
- ${'gl-left-0'} | ${'gl-right-0'}
- ${'gl-border-r-1!'} | ${'gl-border-l-1!'}
- `(
- 'pipeline expand button should be postioned left when parent pipeline',
- ({ presentClass, missingClass }) => {
- createWrapper(upstreamProps);
- expect(findExpandButton().classes()).toContain(presentClass);
- expect(findExpandButton().classes()).not.toContain(missingClass);
- },
- );
-
- it.each`
- pipelineType | anglePosition | expanded
- ${downstreamProps} | ${'angle-right'} | ${false}
- ${downstreamProps} | ${'angle-left'} | ${true}
- ${upstreamProps} | ${'angle-left'} | ${false}
- ${upstreamProps} | ${'angle-right'} | ${true}
+ pipelineType | anglePosition | borderClass | expanded
+ ${downstreamProps} | ${'angle-right'} | ${'gl-border-l-1!'} | ${false}
+ ${downstreamProps} | ${'angle-left'} | ${'gl-border-l-1!'} | ${true}
+ ${upstreamProps} | ${'angle-left'} | ${'gl-border-r-1!'} | ${false}
+ ${upstreamProps} | ${'angle-right'} | ${'gl-border-r-1!'} | ${true}
`(
- '$pipelineType.columnTitle pipeline button icon should be $anglePosition if expanded state is $expanded',
- ({ pipelineType, anglePosition, expanded }) => {
+ '$pipelineType.columnTitle pipeline button icon should be $anglePosition with $borderClass if expanded state is $expanded',
+ ({ pipelineType, anglePosition, borderClass, expanded }) => {
createWrapper({ ...pipelineType, expanded });
expect(findExpandButton().props('icon')).toBe(anglePosition);
+ expect(findExpandButton().classes()).toContain(borderClass);
},
);
});
diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
index 2f03b846525..1673065e09c 100644
--- a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
@@ -1,6 +1,8 @@
-import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
+import { mount, shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
import {
DOWNSTREAM,
@@ -40,13 +42,11 @@ describe('Linked Pipelines Column', () => {
const findPipelineGraph = () => wrapper.find(PipelineGraph);
const findExpandButton = () => wrapper.find('[data-testid="expand-pipeline-button"]');
- const localVue = createLocalVue();
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const createComponent = ({ apolloProvider, mountFn = shallowMount, props = {} } = {}) => {
wrapper = mountFn(LinkedPipelinesColumn, {
apolloProvider,
- localVue,
propsData: {
...defaultProps,
...props,
@@ -87,13 +87,7 @@ describe('Linked Pipelines Column', () => {
describe('click action', () => {
const clickExpandButton = async () => {
await findExpandButton().trigger('click');
- await wrapper.vm.$nextTick();
- };
-
- const clickExpandButtonAndAwaitTimers = async () => {
- await clickExpandButton();
- jest.runOnlyPendingTimers();
- await wrapper.vm.$nextTick();
+ await waitForPromises();
};
describe('layer type rendering', () => {
@@ -106,9 +100,9 @@ describe('Linked Pipelines Column', () => {
it('calls listByLayers only once no matter how many times view is switched', async () => {
expect(layersFn).not.toHaveBeenCalled();
- await clickExpandButtonAndAwaitTimers();
+ await clickExpandButton();
await wrapper.setProps({ viewType: LAYER_VIEW });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(layersFn).toHaveBeenCalledTimes(1);
await wrapper.setProps({ viewType: STAGE_VIEW });
await wrapper.setProps({ viewType: LAYER_VIEW });
@@ -132,7 +126,7 @@ describe('Linked Pipelines Column', () => {
});
it('shows the stage view, even when the main graph view type is layers', async () => {
- await clickExpandButtonAndAwaitTimers();
+ await clickExpandButton();
expect(findPipelineGraph().props('viewType')).toBe(STAGE_VIEW);
});
});
@@ -145,7 +139,7 @@ describe('Linked Pipelines Column', () => {
it('toggles the pipeline visibility', async () => {
expect(findPipelineGraph().exists()).toBe(false);
- await clickExpandButtonAndAwaitTimers();
+ await clickExpandButton();
expect(findPipelineGraph().exists()).toBe(true);
await clickExpandButton();
expect(findPipelineGraph().exists()).toBe(false);
@@ -167,7 +161,7 @@ describe('Linked Pipelines Column', () => {
it('does not show the pipeline', async () => {
expect(findPipelineGraph().exists()).toBe(false);
- await clickExpandButtonAndAwaitTimers();
+ await clickExpandButton();
expect(findPipelineGraph().exists()).toBe(false);
});
});
@@ -195,7 +189,7 @@ describe('Linked Pipelines Column', () => {
it('toggles the pipeline visibility', async () => {
expect(findPipelineGraph().exists()).toBe(false);
- await clickExpandButtonAndAwaitTimers();
+ await clickExpandButton();
expect(findPipelineGraph().exists()).toBe(true);
await clickExpandButton();
expect(findPipelineGraph().exists()).toBe(false);
@@ -218,7 +212,7 @@ describe('Linked Pipelines Column', () => {
it('does not show the pipeline', async () => {
expect(findPipelineGraph().exists()).toBe(false);
- await clickExpandButtonAndAwaitTimers();
+ await clickExpandButton();
expect(findPipelineGraph().exists()).toBe(false);
});
});
diff --git a/spec/frontend/pipelines/graph/mock_data.js b/spec/frontend/pipelines/graph/mock_data.js
index 41823bfdb9f..0cf7dc507f4 100644
--- a/spec/frontend/pipelines/graph/mock_data.js
+++ b/spec/frontend/pipelines/graph/mock_data.js
@@ -57,6 +57,7 @@ export const mockPipelineResponse = {
id: '7',
icon: 'status_success',
tooltip: 'passed',
+ label: 'passed',
hasDetails: true,
detailsPath: '/root/abcd-dag/-/jobs/1482',
group: 'success',
@@ -106,6 +107,7 @@ export const mockPipelineResponse = {
id: '12',
icon: 'status_success',
tooltip: 'passed',
+ label: 'passed',
hasDetails: true,
detailsPath: '/root/abcd-dag/-/jobs/1515',
group: 'success',
@@ -155,6 +157,7 @@ export const mockPipelineResponse = {
id: '17',
icon: 'status_success',
tooltip: 'passed',
+ label: 'passed',
hasDetails: true,
detailsPath: '/root/abcd-dag/-/jobs/1484',
group: 'success',
@@ -204,6 +207,7 @@ export const mockPipelineResponse = {
id: '22',
icon: 'status_success',
tooltip: 'passed',
+ label: 'passed',
hasDetails: true,
detailsPath: '/root/abcd-dag/-/jobs/1485',
group: 'success',
@@ -235,6 +239,7 @@ export const mockPipelineResponse = {
id: '25',
icon: 'status_success',
tooltip: 'passed',
+ label: 'passed',
hasDetails: true,
detailsPath: '/root/abcd-dag/-/jobs/1486',
group: 'success',
@@ -266,6 +271,7 @@ export const mockPipelineResponse = {
id: '28',
icon: 'status_success',
tooltip: 'passed',
+ label: 'passed',
hasDetails: true,
detailsPath: '/root/abcd-dag/-/jobs/1487',
group: 'success',
@@ -330,6 +336,7 @@ export const mockPipelineResponse = {
id: '35',
icon: 'status_success',
tooltip: 'passed',
+ label: 'passed',
hasDetails: true,
detailsPath: '/root/abcd-dag/-/jobs/1514',
group: 'success',
@@ -413,6 +420,7 @@ export const mockPipelineResponse = {
id: '43',
icon: 'status_success',
tooltip: 'passed',
+ label: 'passed',
hasDetails: true,
detailsPath: '/root/abcd-dag/-/jobs/1489',
group: 'success',
@@ -498,6 +506,7 @@ export const mockPipelineResponse = {
id: '50',
icon: 'status_success',
tooltip: 'passed',
+ label: 'passed',
hasDetails: true,
detailsPath: '/root/abcd-dag/-/jobs/1490',
group: 'success',
@@ -601,6 +610,7 @@ export const mockPipelineResponse = {
id: '60',
icon: 'status_success',
tooltip: null,
+ label: null,
hasDetails: true,
detailsPath: '/root/kinder-pipe/-/pipelines/154',
group: 'success',
@@ -643,6 +653,7 @@ export const mockPipelineResponse = {
id: '64',
icon: 'status_success',
tooltip: null,
+ label: null,
hasDetails: true,
detailsPath: '/root/abcd-dag/-/pipelines/153',
group: 'success',
@@ -850,6 +861,7 @@ export const wrappedPipelineReturn = {
id: '84',
icon: 'status_success',
tooltip: 'passed',
+ label: 'passed',
hasDetails: true,
detailsPath: '/root/elemenohpee/-/jobs/1662',
group: 'success',
diff --git a/spec/frontend/pipelines/header_component_spec.js b/spec/frontend/pipelines/header_component_spec.js
index 9e51003da66..1d89f949564 100644
--- a/spec/frontend/pipelines/header_component_spec.js
+++ b/spec/frontend/pipelines/header_component_spec.js
@@ -4,6 +4,7 @@ import HeaderComponent from '~/pipelines/components/header_component.vue';
import cancelPipelineMutation from '~/pipelines/graphql/mutations/cancel_pipeline.mutation.graphql';
import deletePipelineMutation from '~/pipelines/graphql/mutations/delete_pipeline.mutation.graphql';
import retryPipelineMutation from '~/pipelines/graphql/mutations/retry_pipeline.mutation.graphql';
+import { BUTTON_TOOLTIP_RETRY } from '~/pipelines/constants';
import {
mockCancelledPipelineHeader,
mockFailedPipelineHeader,
@@ -113,6 +114,10 @@ describe('Pipeline details header', () => {
variables: { id: mockCancelledPipelineHeader.id },
});
});
+
+ it('should render retry action tooltip', () => {
+ expect(findRetryButton().attributes('title')).toBe(BUTTON_TOOLTIP_RETRY);
+ });
});
describe('Cancel action', () => {
diff --git a/spec/frontend/pipelines/mock_data.js b/spec/frontend/pipelines/mock_data.js
index b9d20eb7ca5..8cb6cf3bed6 100644
--- a/spec/frontend/pipelines/mock_data.js
+++ b/spec/frontend/pipelines/mock_data.js
@@ -634,3 +634,683 @@ export const mockPipelineJobsQueryResponse = {
},
},
};
+
+export const mockPipeline = (projectPath) => {
+ return {
+ pipeline: {
+ id: 1,
+ user: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url: '',
+ web_url: 'http://0.0.0.0:3000/root',
+ show_status: false,
+ path: '/root',
+ },
+ active: false,
+ source: 'merge_request_event',
+ created_at: '2021-10-19T21:17:38.698Z',
+ updated_at: '2021-10-21T18:00:42.758Z',
+ path: 'foo',
+ flags: {},
+ merge_request: {
+ iid: 1,
+ path: `/${projectPath}/1`,
+ title: 'commit',
+ source_branch: 'test-commit-name',
+ source_branch_path: `/${projectPath}`,
+ target_branch: 'main',
+ target_branch_path: `/${projectPath}/-/commit/main`,
+ },
+ ref: {
+ name: 'refs/merge-requests/1/head',
+ path: `/${projectPath}/-/commits/refs/merge-requests/1/head`,
+ tag: false,
+ branch: false,
+ merge_request: true,
+ },
+ commit: {
+ id: 'fd6df5b3229e213c97d308844a6f3e7fd71e8f8c',
+ short_id: 'fd6df5b3',
+ created_at: '2021-10-19T21:17:12.000+00:00',
+ parent_ids: ['7147906b84306e83cb3fec6582a25390b75713c6'],
+ title: 'Commit Title',
+ message: 'Commit',
+ author_name: 'Administrator',
+ author_email: 'admin@example.com',
+ authored_date: '2021-10-19T21:17:12.000+00:00',
+ committer_name: 'Administrator',
+ committer_email: 'admin@example.com',
+ committed_date: '2021-10-19T21:17:12.000+00:00',
+ trailers: {},
+ web_url: '',
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url: '',
+ web_url: '',
+ show_status: false,
+ path: '/root',
+ },
+ author_gravatar_url: '',
+ commit_url: `/${projectPath}/fd6df5b3229e213c97d308844a6f3e7fd71e8f8c`,
+ commit_path: `/${projectPath}/commit/fd6df5b3229e213c97d308844a6f3e7fd71e8f8c`,
+ },
+ project: {
+ full_path: `/${projectPath}`,
+ },
+ triggered_by: null,
+ triggered: [],
+ },
+ pipelineScheduleUrl: 'foo',
+ pipelineKey: 'id',
+ viewType: 'root',
+ };
+};
+
+export const mockPipelineTag = () => {
+ return {
+ pipeline: {
+ id: 311,
+ iid: 37,
+ user: {
+ id: 1,
+ username: 'root',
+ name: 'Administrator',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'http://gdk.test:3000/root',
+ show_status: false,
+ path: '/root',
+ },
+ active: false,
+ source: 'push',
+ created_at: '2022-02-02T15:39:04.012Z',
+ updated_at: '2022-02-02T15:40:59.573Z',
+ path: '/root/mr-widgets/-/pipelines/311',
+ flags: {
+ stuck: false,
+ auto_devops: false,
+ merge_request: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: false,
+ failure_reason: false,
+ detached_merge_request_pipeline: false,
+ merge_request_pipeline: false,
+ merge_train_pipeline: false,
+ latest: true,
+ },
+ details: {
+ status: {
+ icon: 'status_warning',
+ text: 'passed',
+ label: 'passed with warnings',
+ group: 'success-with-warnings',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/root/mr-widgets/-/pipelines/311',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ stages: [
+ {
+ name: 'accessibility',
+ title: 'accessibility: passed',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/root/mr-widgets/-/pipelines/311#accessibility',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ path: '/root/mr-widgets/-/pipelines/311#accessibility',
+ dropdown_path: '/root/mr-widgets/-/pipelines/311/stage.json?stage=accessibility',
+ },
+ {
+ name: 'validate',
+ title: 'validate: passed with warnings',
+ status: {
+ icon: 'status_warning',
+ text: 'passed',
+ label: 'passed with warnings',
+ group: 'success-with-warnings',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/root/mr-widgets/-/pipelines/311#validate',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ path: '/root/mr-widgets/-/pipelines/311#validate',
+ dropdown_path: '/root/mr-widgets/-/pipelines/311/stage.json?stage=validate',
+ },
+ {
+ name: 'test',
+ title: 'test: passed',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/root/mr-widgets/-/pipelines/311#test',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ path: '/root/mr-widgets/-/pipelines/311#test',
+ dropdown_path: '/root/mr-widgets/-/pipelines/311/stage.json?stage=test',
+ },
+ {
+ name: 'build',
+ title: 'build: passed',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/root/mr-widgets/-/pipelines/311#build',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ path: '/root/mr-widgets/-/pipelines/311#build',
+ dropdown_path: '/root/mr-widgets/-/pipelines/311/stage.json?stage=build',
+ },
+ ],
+ duration: 93,
+ finished_at: '2022-02-02T15:40:59.384Z',
+ name: 'Pipeline',
+ manual_actions: [],
+ scheduled_actions: [],
+ },
+ ref: {
+ name: 'test',
+ path: '/root/mr-widgets/-/commits/test',
+ tag: true,
+ branch: false,
+ merge_request: false,
+ },
+ commit: {
+ id: '9b92b4f730d1611bd9a086ca221ae206d5da1e59',
+ short_id: '9b92b4f7',
+ created_at: '2022-01-13T13:59:03.000+00:00',
+ parent_ids: ['0ba763634114e207dc72c65c8e9459556b1204fb'],
+ title: 'Update hello_world.js',
+ message: 'Update hello_world.js',
+ author_name: 'Administrator',
+ author_email: 'admin@example.com',
+ authored_date: '2022-01-13T13:59:03.000+00:00',
+ committer_name: 'Administrator',
+ committer_email: 'admin@example.com',
+ committed_date: '2022-01-13T13:59:03.000+00:00',
+ trailers: {},
+ web_url:
+ 'http://gdk.test:3000/root/mr-widgets/-/commit/9b92b4f730d1611bd9a086ca221ae206d5da1e59',
+ author: {
+ id: 1,
+ username: 'root',
+ name: 'Administrator',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'http://gdk.test:3000/root',
+ show_status: false,
+ path: '/root',
+ },
+ author_gravatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ commit_url:
+ 'http://gdk.test:3000/root/mr-widgets/-/commit/9b92b4f730d1611bd9a086ca221ae206d5da1e59',
+ commit_path: '/root/mr-widgets/-/commit/9b92b4f730d1611bd9a086ca221ae206d5da1e59',
+ },
+ retry_path: '/root/mr-widgets/-/pipelines/311/retry',
+ delete_path: '/root/mr-widgets/-/pipelines/311',
+ failed_builds: [
+ {
+ id: 1696,
+ name: 'fmt',
+ started: '2022-02-02T15:39:45.192Z',
+ complete: true,
+ archived: false,
+ build_path: '/root/mr-widgets/-/jobs/1696',
+ retry_path: '/root/mr-widgets/-/jobs/1696/retry',
+ playable: false,
+ scheduled: false,
+ created_at: '2022-02-02T15:39:04.136Z',
+ updated_at: '2022-02-02T15:39:57.969Z',
+ status: {
+ icon: 'status_warning',
+ text: 'failed',
+ label: 'failed (allowed to fail)',
+ group: 'failed-with-warnings',
+ tooltip: 'failed - (script failure) (allowed to fail)',
+ has_details: true,
+ details_path: '/root/mr-widgets/-/jobs/1696',
+ illustration: {
+ image:
+ '/assets/illustrations/skipped-job_empty-29a8a37d8a61d1b6f68cf3484f9024e53cd6eb95e28eae3554f8011a1146bf27.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/mr-widgets/-/jobs/1696/retry',
+ method: 'post',
+ button_title: 'Retry this job',
+ },
+ },
+ recoverable: false,
+ },
+ ],
+ project: {
+ id: 23,
+ name: 'mr-widgets',
+ full_path: '/root/mr-widgets',
+ full_name: 'Administrator / mr-widgets',
+ },
+ triggered_by: null,
+ triggered: [],
+ },
+ pipelineScheduleUrl: 'foo',
+ pipelineKey: 'id',
+ viewType: 'root',
+ };
+};
+
+export const mockPipelineBranch = () => {
+ return {
+ pipeline: {
+ id: 268,
+ iid: 34,
+ user: {
+ id: 1,
+ username: 'root',
+ name: 'Administrator',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'http://gdk.test:3000/root',
+ show_status: false,
+ path: '/root',
+ },
+ active: false,
+ source: 'push',
+ created_at: '2022-01-14T17:40:27.866Z',
+ updated_at: '2022-01-14T18:02:35.850Z',
+ path: '/root/mr-widgets/-/pipelines/268',
+ flags: {
+ stuck: false,
+ auto_devops: false,
+ merge_request: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: false,
+ failure_reason: false,
+ detached_merge_request_pipeline: false,
+ merge_request_pipeline: false,
+ merge_train_pipeline: false,
+ latest: true,
+ },
+ details: {
+ status: {
+ icon: 'status_warning',
+ text: 'passed',
+ label: 'passed with warnings',
+ group: 'success-with-warnings',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/root/mr-widgets/-/pipelines/268',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ stages: [
+ {
+ name: 'validate',
+ title: 'validate: passed with warnings',
+ status: {
+ icon: 'status_warning',
+ text: 'passed',
+ label: 'passed with warnings',
+ group: 'success-with-warnings',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/root/mr-widgets/-/pipelines/268#validate',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ path: '/root/mr-widgets/-/pipelines/268#validate',
+ dropdown_path: '/root/mr-widgets/-/pipelines/268/stage.json?stage=validate',
+ },
+ {
+ name: 'test',
+ title: 'test: passed',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/root/mr-widgets/-/pipelines/268#test',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ path: '/root/mr-widgets/-/pipelines/268#test',
+ dropdown_path: '/root/mr-widgets/-/pipelines/268/stage.json?stage=test',
+ },
+ {
+ name: 'build',
+ title: 'build: passed',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/root/mr-widgets/-/pipelines/268#build',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ path: '/root/mr-widgets/-/pipelines/268#build',
+ dropdown_path: '/root/mr-widgets/-/pipelines/268/stage.json?stage=build',
+ },
+ ],
+ duration: 75,
+ finished_at: '2022-01-14T18:02:35.842Z',
+ name: 'Pipeline',
+ manual_actions: [],
+ scheduled_actions: [],
+ },
+ ref: {
+ name: 'update-ci',
+ path: '/root/mr-widgets/-/commits/update-ci',
+ tag: false,
+ branch: true,
+ merge_request: false,
+ },
+ commit: {
+ id: '96aef9ecec5752c09371c1ade5fc77860aafc863',
+ short_id: '96aef9ec',
+ created_at: '2022-01-14T17:40:26.000+00:00',
+ parent_ids: ['06860257572d4cf84b73806250b78169050aed83'],
+ title: 'Update main.tf',
+ message: 'Update main.tf',
+ author_name: 'Administrator',
+ author_email: 'admin@example.com',
+ authored_date: '2022-01-14T17:40:26.000+00:00',
+ committer_name: 'Administrator',
+ committer_email: 'admin@example.com',
+ committed_date: '2022-01-14T17:40:26.000+00:00',
+ trailers: {},
+ web_url:
+ 'http://gdk.test:3000/root/mr-widgets/-/commit/96aef9ecec5752c09371c1ade5fc77860aafc863',
+ author: {
+ id: 1,
+ username: 'root',
+ name: 'Administrator',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'http://gdk.test:3000/root',
+ show_status: false,
+ path: '/root',
+ },
+ author_gravatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ commit_url:
+ 'http://gdk.test:3000/root/mr-widgets/-/commit/96aef9ecec5752c09371c1ade5fc77860aafc863',
+ commit_path: '/root/mr-widgets/-/commit/96aef9ecec5752c09371c1ade5fc77860aafc863',
+ },
+ retry_path: '/root/mr-widgets/-/pipelines/268/retry',
+ delete_path: '/root/mr-widgets/-/pipelines/268',
+ failed_builds: [
+ {
+ id: 1260,
+ name: 'fmt',
+ started: '2022-01-14T17:40:36.435Z',
+ complete: true,
+ archived: false,
+ build_path: '/root/mr-widgets/-/jobs/1260',
+ retry_path: '/root/mr-widgets/-/jobs/1260/retry',
+ playable: false,
+ scheduled: false,
+ created_at: '2022-01-14T17:40:27.879Z',
+ updated_at: '2022-01-14T17:40:42.129Z',
+ status: {
+ icon: 'status_warning',
+ text: 'failed',
+ label: 'failed (allowed to fail)',
+ group: 'failed-with-warnings',
+ tooltip: 'failed - (script failure) (allowed to fail)',
+ has_details: true,
+ details_path: '/root/mr-widgets/-/jobs/1260',
+ illustration: {
+ image:
+ '/assets/illustrations/skipped-job_empty-29a8a37d8a61d1b6f68cf3484f9024e53cd6eb95e28eae3554f8011a1146bf27.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/mr-widgets/-/jobs/1260/retry',
+ method: 'post',
+ button_title: 'Retry this job',
+ },
+ },
+ recoverable: false,
+ },
+ ],
+ project: {
+ id: 23,
+ name: 'mr-widgets',
+ full_path: '/root/mr-widgets',
+ full_name: 'Administrator / mr-widgets',
+ },
+ triggered_by: null,
+ triggered: [],
+ },
+ pipelineScheduleUrl: 'foo',
+ pipelineKey: 'id',
+ viewType: 'root',
+ };
+};
+
+export const mockPipelineNoCommit = () => {
+ return {
+ pipeline: {
+ id: 268,
+ iid: 34,
+ user: {
+ id: 1,
+ username: 'root',
+ name: 'Administrator',
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'http://gdk.test:3000/root',
+ show_status: false,
+ path: '/root',
+ },
+ active: false,
+ source: 'push',
+ created_at: '2022-01-14T17:40:27.866Z',
+ updated_at: '2022-01-14T18:02:35.850Z',
+ path: '/root/mr-widgets/-/pipelines/268',
+ flags: {
+ stuck: false,
+ auto_devops: false,
+ merge_request: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: false,
+ failure_reason: false,
+ detached_merge_request_pipeline: false,
+ merge_request_pipeline: false,
+ merge_train_pipeline: false,
+ latest: true,
+ },
+ details: {
+ status: {
+ icon: 'status_warning',
+ text: 'passed',
+ label: 'passed with warnings',
+ group: 'success-with-warnings',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/root/mr-widgets/-/pipelines/268',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ stages: [
+ {
+ name: 'validate',
+ title: 'validate: passed with warnings',
+ status: {
+ icon: 'status_warning',
+ text: 'passed',
+ label: 'passed with warnings',
+ group: 'success-with-warnings',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/root/mr-widgets/-/pipelines/268#validate',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ path: '/root/mr-widgets/-/pipelines/268#validate',
+ dropdown_path: '/root/mr-widgets/-/pipelines/268/stage.json?stage=validate',
+ },
+ {
+ name: 'test',
+ title: 'test: passed',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/root/mr-widgets/-/pipelines/268#test',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ path: '/root/mr-widgets/-/pipelines/268#test',
+ dropdown_path: '/root/mr-widgets/-/pipelines/268/stage.json?stage=test',
+ },
+ {
+ name: 'build',
+ title: 'build: passed',
+ status: {
+ icon: 'status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ tooltip: 'passed',
+ has_details: true,
+ details_path: '/root/mr-widgets/-/pipelines/268#build',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png',
+ },
+ path: '/root/mr-widgets/-/pipelines/268#build',
+ dropdown_path: '/root/mr-widgets/-/pipelines/268/stage.json?stage=build',
+ },
+ ],
+ duration: 75,
+ finished_at: '2022-01-14T18:02:35.842Z',
+ name: 'Pipeline',
+ manual_actions: [],
+ scheduled_actions: [],
+ },
+ ref: {
+ name: 'update-ci',
+ path: '/root/mr-widgets/-/commits/update-ci',
+ tag: false,
+ branch: true,
+ merge_request: false,
+ },
+ retry_path: '/root/mr-widgets/-/pipelines/268/retry',
+ delete_path: '/root/mr-widgets/-/pipelines/268',
+ failed_builds: [
+ {
+ id: 1260,
+ name: 'fmt',
+ started: '2022-01-14T17:40:36.435Z',
+ complete: true,
+ archived: false,
+ build_path: '/root/mr-widgets/-/jobs/1260',
+ retry_path: '/root/mr-widgets/-/jobs/1260/retry',
+ playable: false,
+ scheduled: false,
+ created_at: '2022-01-14T17:40:27.879Z',
+ updated_at: '2022-01-14T17:40:42.129Z',
+ status: {
+ icon: 'status_warning',
+ text: 'failed',
+ label: 'failed (allowed to fail)',
+ group: 'failed-with-warnings',
+ tooltip: 'failed - (script failure) (allowed to fail)',
+ has_details: true,
+ details_path: '/root/mr-widgets/-/jobs/1260',
+ illustration: {
+ image:
+ '/assets/illustrations/skipped-job_empty-29a8a37d8a61d1b6f68cf3484f9024e53cd6eb95e28eae3554f8011a1146bf27.svg',
+ size: 'svg-430',
+ title: 'This job does not have a trace.',
+ },
+ favicon:
+ '/assets/ci_favicons/favicon_status_failed-41304d7f7e3828808b0c26771f0309e55296819a9beea3ea9fbf6689d9857c12.png',
+ action: {
+ icon: 'retry',
+ title: 'Retry',
+ path: '/root/mr-widgets/-/jobs/1260/retry',
+ method: 'post',
+ button_title: 'Retry this job',
+ },
+ },
+ recoverable: false,
+ },
+ ],
+ project: {
+ id: 23,
+ name: 'mr-widgets',
+ full_path: '/root/mr-widgets',
+ full_name: 'Administrator / mr-widgets',
+ },
+ triggered_by: null,
+ triggered: [],
+ },
+ pipelineScheduleUrl: 'foo',
+ pipelineKey: 'id',
+ viewType: 'root',
+ };
+};
diff --git a/spec/frontend/pipelines/notification/deprecated_type_keyword_notification_spec.js b/spec/frontend/pipelines/notification/deprecated_type_keyword_notification_spec.js
new file mode 100644
index 00000000000..f626652a944
--- /dev/null
+++ b/spec/frontend/pipelines/notification/deprecated_type_keyword_notification_spec.js
@@ -0,0 +1,146 @@
+import VueApollo from 'vue-apollo';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlAlert, GlSprintf } from '@gitlab/ui';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import DeprecatedTypeKeywordNotification from '~/pipelines/components/notification/deprecated_type_keyword_notification.vue';
+import getPipelineWarnings from '~/pipelines/graphql/queries/get_pipeline_warnings.query.graphql';
+import {
+ mockWarningsWithoutDeprecation,
+ mockWarningsRootType,
+ mockWarningsType,
+ mockWarningsTypesAll,
+} from './mock_data';
+
+const defaultProvide = {
+ deprecatedKeywordsDocPath: '/help/ci/yaml/index.md#deprecated-keywords',
+ fullPath: '/namespace/my-project',
+ pipelineIid: 4,
+};
+
+let wrapper;
+
+const mockWarnings = jest.fn();
+
+const createComponent = ({ isLoading = false, options = {} } = {}) => {
+ return shallowMount(DeprecatedTypeKeywordNotification, {
+ stubs: {
+ GlSprintf,
+ },
+ provide: {
+ ...defaultProvide,
+ },
+ mocks: {
+ $apollo: {
+ queries: {
+ warnings: {
+ loading: isLoading,
+ },
+ },
+ },
+ },
+ ...options,
+ });
+};
+
+const createComponentWithApollo = () => {
+ const localVue = createLocalVue();
+ localVue.use(VueApollo);
+
+ const handlers = [[getPipelineWarnings, mockWarnings]];
+ const mockApollo = createMockApollo(handlers);
+
+ return createComponent({
+ options: {
+ localVue,
+ apolloProvider: mockApollo,
+ mocks: {},
+ },
+ });
+};
+
+const findAlert = () => wrapper.findComponent(GlAlert);
+const findAlertItems = () => findAlert().findAll('li');
+
+afterEach(() => {
+ wrapper.destroy();
+});
+
+describe('Deprecated keyword notification', () => {
+ describe('while loading the pipeline warnings', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ isLoading: true });
+ });
+
+ it('does not display the notification', () => {
+ expect(findAlert().exists()).toBe(false);
+ });
+ });
+
+ describe('if there is an error in the query', () => {
+ beforeEach(async () => {
+ mockWarnings.mockResolvedValue({ errors: ['It didnt work'] });
+ wrapper = createComponentWithApollo();
+ await waitForPromises();
+ });
+
+ it('does not display the notification', () => {
+ expect(findAlert().exists()).toBe(false);
+ });
+ });
+
+ describe('with a valid query result', () => {
+ describe('if there are no deprecation warnings', () => {
+ beforeEach(async () => {
+ mockWarnings.mockResolvedValue(mockWarningsWithoutDeprecation);
+ wrapper = createComponentWithApollo();
+ await waitForPromises();
+ });
+ it('does not show the notification', () => {
+ expect(findAlert().exists()).toBe(false);
+ });
+ });
+
+ describe('with a root type deprecation message', () => {
+ beforeEach(async () => {
+ mockWarnings.mockResolvedValue(mockWarningsRootType);
+ wrapper = createComponentWithApollo();
+ await waitForPromises();
+ });
+ it('shows the notification with one item', () => {
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlertItems()).toHaveLength(1);
+ expect(findAlertItems().at(0).text()).toContain('types');
+ });
+ });
+
+ describe('with a job type deprecation message', () => {
+ beforeEach(async () => {
+ mockWarnings.mockResolvedValue(mockWarningsType);
+ wrapper = createComponentWithApollo();
+ await waitForPromises();
+ });
+ it('shows the notification with one item', () => {
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlertItems()).toHaveLength(1);
+ expect(findAlertItems().at(0).text()).toContain('type');
+ expect(findAlertItems().at(0).text()).not.toContain('types');
+ });
+ });
+
+ describe('with both the root types and job type deprecation message', () => {
+ beforeEach(async () => {
+ mockWarnings.mockResolvedValue(mockWarningsTypesAll);
+ wrapper = createComponentWithApollo();
+ await waitForPromises();
+ });
+ it('shows the notification with two items', () => {
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlertItems()).toHaveLength(2);
+ expect(findAlertItems().at(0).text()).toContain('types');
+ expect(findAlertItems().at(1).text()).toContain('type');
+ expect(findAlertItems().at(1).text()).not.toContain('types');
+ });
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/notification/mock_data.js b/spec/frontend/pipelines/notification/mock_data.js
new file mode 100644
index 00000000000..e36f391a854
--- /dev/null
+++ b/spec/frontend/pipelines/notification/mock_data.js
@@ -0,0 +1,33 @@
+const randomWarning = {
+ content: 'another random warning',
+ id: 'gid://gitlab/Ci::PipelineMessage/272',
+};
+
+const rootTypeWarning = {
+ content: 'root `types` will be removed in 15.0.',
+ id: 'gid://gitlab/Ci::PipelineMessage/273',
+};
+
+const typeWarning = {
+ content: '`type` will be removed in 15.0.',
+ id: 'gid://gitlab/Ci::PipelineMessage/274',
+};
+
+function createWarningMock(warnings) {
+ return {
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/28"',
+ pipeline: {
+ id: 'gid://gitlab/Ci::Pipeline/183',
+ warningMessages: warnings,
+ },
+ },
+ },
+ };
+}
+
+export const mockWarningsWithoutDeprecation = createWarningMock([randomWarning]);
+export const mockWarningsRootType = createWarningMock([rootTypeWarning]);
+export const mockWarningsType = createWarningMock([typeWarning]);
+export const mockWarningsTypesAll = createWarningMock([rootTypeWarning, typeWarning]);
diff --git a/spec/frontend/pipelines/pipeline_triggerer_spec.js b/spec/frontend/pipelines/pipeline_triggerer_spec.js
index ffb2721f159..701b1691c7b 100644
--- a/spec/frontend/pipelines/pipeline_triggerer_spec.js
+++ b/spec/frontend/pipelines/pipeline_triggerer_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import pipelineTriggerer from '~/pipelines/components/pipelines_list/pipeline_triggerer.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
@@ -47,15 +48,14 @@ describe('Pipelines Triggerer', () => {
});
});
- it('should render "API" when no triggerer is provided', () => {
+ it('should render "API" when no triggerer is provided', async () => {
wrapper.setProps({
pipeline: {
user: null,
},
});
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find('.js-pipeline-url-api').text()).toEqual('API');
- });
+ await nextTick();
+ expect(wrapper.find('.js-pipeline-url-api').text()).toEqual('API');
});
});
diff --git a/spec/frontend/pipelines/pipeline_url_spec.js b/spec/frontend/pipelines/pipeline_url_spec.js
index 912b5afe0e1..b24e2e09ea8 100644
--- a/spec/frontend/pipelines/pipeline_url_spec.js
+++ b/spec/frontend/pipelines/pipeline_url_spec.js
@@ -1,41 +1,48 @@
-import { shallowMount } from '@vue/test-utils';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { trimText } from 'helpers/text_helper';
import PipelineUrlComponent from '~/pipelines/components/pipelines_list/pipeline_url.vue';
+import {
+ mockPipeline,
+ mockPipelineBranch,
+ mockPipelineTag,
+ mockPipelineNoCommit,
+} from './mock_data';
const projectPath = 'test/test';
describe('Pipeline Url Component', () => {
let wrapper;
- const findTableCell = () => wrapper.find('[data-testid="pipeline-url-table-cell"]');
- const findPipelineUrlLink = () => wrapper.find('[data-testid="pipeline-url-link"]');
- const findScheduledTag = () => wrapper.find('[data-testid="pipeline-url-scheduled"]');
- const findLatestTag = () => wrapper.find('[data-testid="pipeline-url-latest"]');
- const findYamlTag = () => wrapper.find('[data-testid="pipeline-url-yaml"]');
- const findFailureTag = () => wrapper.find('[data-testid="pipeline-url-failure"]');
- const findAutoDevopsTag = () => wrapper.find('[data-testid="pipeline-url-autodevops"]');
- const findAutoDevopsTagLink = () => wrapper.find('[data-testid="pipeline-url-autodevops-link"]');
- const findStuckTag = () => wrapper.find('[data-testid="pipeline-url-stuck"]');
- const findDetachedTag = () => wrapper.find('[data-testid="pipeline-url-detached"]');
- const findForkTag = () => wrapper.find('[data-testid="pipeline-url-fork"]');
- const findTrainTag = () => wrapper.find('[data-testid="pipeline-url-train"]');
-
- const defaultProps = {
- pipeline: {
- id: 1,
- path: 'foo',
- project: { full_path: `/${projectPath}` },
- flags: {},
- },
- pipelineScheduleUrl: 'foo',
- pipelineKey: 'id',
- };
-
- const createComponent = (props) => {
- wrapper = shallowMount(PipelineUrlComponent, {
+ const findTableCell = () => wrapper.findByTestId('pipeline-url-table-cell');
+ const findPipelineUrlLink = () => wrapper.findByTestId('pipeline-url-link');
+ const findScheduledTag = () => wrapper.findByTestId('pipeline-url-scheduled');
+ const findLatestTag = () => wrapper.findByTestId('pipeline-url-latest');
+ const findYamlTag = () => wrapper.findByTestId('pipeline-url-yaml');
+ const findFailureTag = () => wrapper.findByTestId('pipeline-url-failure');
+ const findAutoDevopsTag = () => wrapper.findByTestId('pipeline-url-autodevops');
+ const findAutoDevopsTagLink = () => wrapper.findByTestId('pipeline-url-autodevops-link');
+ const findStuckTag = () => wrapper.findByTestId('pipeline-url-stuck');
+ const findDetachedTag = () => wrapper.findByTestId('pipeline-url-detached');
+ const findForkTag = () => wrapper.findByTestId('pipeline-url-fork');
+ const findTrainTag = () => wrapper.findByTestId('pipeline-url-train');
+ const findRefName = () => wrapper.findByTestId('merge-request-ref');
+ const findCommitShortSha = () => wrapper.findByTestId('commit-short-sha');
+ const findCommitIcon = () => wrapper.findByTestId('commit-icon');
+ const findCommitIconType = () => wrapper.findByTestId('commit-icon-type');
+
+ const findCommitTitleContainer = () => wrapper.findByTestId('commit-title-container');
+ const findCommitTitle = () => wrapper.findByTestId('commit-title');
+
+ const defaultProps = mockPipeline(projectPath);
+
+ const createComponent = (props, rearrangePipelinesTable = false) => {
+ wrapper = shallowMountExtended(PipelineUrlComponent, {
propsData: { ...defaultProps, ...props },
provide: {
targetProjectFullPath: projectPath,
+ glFeatures: {
+ rearrangePipelinesTable,
+ },
},
});
};
@@ -45,158 +52,218 @@ describe('Pipeline Url Component', () => {
wrapper = null;
});
- it('should render pipeline url table cell', () => {
- createComponent();
+ describe('with the rearrangePipelinesTable feature flag turned off', () => {
+ it('should render pipeline url table cell', () => {
+ createComponent();
- expect(findTableCell().exists()).toBe(true);
- });
+ expect(findTableCell().exists()).toBe(true);
+ });
- it('should render a link the provided path and id', () => {
- createComponent();
+ it('should render a link the provided path and id', () => {
+ createComponent();
- expect(findPipelineUrlLink().attributes('href')).toBe('foo');
+ expect(findPipelineUrlLink().attributes('href')).toBe('foo');
- expect(findPipelineUrlLink().text()).toBe('#1');
- });
+ expect(findPipelineUrlLink().text()).toBe('#1');
+ });
- it('should not render tags when flags are not set', () => {
- createComponent();
-
- expect(findStuckTag().exists()).toBe(false);
- expect(findLatestTag().exists()).toBe(false);
- expect(findYamlTag().exists()).toBe(false);
- expect(findAutoDevopsTag().exists()).toBe(false);
- expect(findFailureTag().exists()).toBe(false);
- expect(findScheduledTag().exists()).toBe(false);
- expect(findForkTag().exists()).toBe(false);
- expect(findTrainTag().exists()).toBe(false);
- });
+ it('should not render tags when flags are not set', () => {
+ createComponent();
+
+ expect(findStuckTag().exists()).toBe(false);
+ expect(findLatestTag().exists()).toBe(false);
+ expect(findYamlTag().exists()).toBe(false);
+ expect(findAutoDevopsTag().exists()).toBe(false);
+ expect(findFailureTag().exists()).toBe(false);
+ expect(findScheduledTag().exists()).toBe(false);
+ expect(findForkTag().exists()).toBe(false);
+ expect(findTrainTag().exists()).toBe(false);
+ });
- it('should render the stuck tag when flag is provided', () => {
- createComponent({
- pipeline: {
- flags: {
- stuck: true,
- },
- },
+ it('should render the stuck tag when flag is provided', () => {
+ const stuckPipeline = defaultProps.pipeline;
+ stuckPipeline.flags.stuck = true;
+
+ createComponent({
+ ...stuckPipeline.pipeline,
+ });
+
+ expect(findStuckTag().text()).toContain('stuck');
});
- expect(findStuckTag().text()).toContain('stuck');
- });
+ it('should render latest tag when flag is provided', () => {
+ const latestPipeline = defaultProps.pipeline;
+ latestPipeline.flags.latest = true;
- it('should render latest tag when flag is provided', () => {
- createComponent({
- pipeline: {
- flags: {
- latest: true,
- },
- },
+ createComponent({
+ ...latestPipeline,
+ });
+
+ expect(findLatestTag().text()).toContain('latest');
});
- expect(findLatestTag().text()).toContain('latest');
- });
+ it('should render a yaml badge when it is invalid', () => {
+ const yamlPipeline = defaultProps.pipeline;
+ yamlPipeline.flags.yaml_errors = true;
- it('should render a yaml badge when it is invalid', () => {
- createComponent({
- pipeline: {
- flags: {
- yaml_errors: true,
- },
- },
+ createComponent({
+ ...yamlPipeline,
+ });
+
+ expect(findYamlTag().text()).toContain('yaml invalid');
});
- expect(findYamlTag().text()).toContain('yaml invalid');
- });
+ it('should render an autodevops badge when flag is provided', () => {
+ const autoDevopsPipeline = defaultProps.pipeline;
+ autoDevopsPipeline.flags.auto_devops = true;
- it('should render an autodevops badge when flag is provided', () => {
- createComponent({
- pipeline: {
- ...defaultProps.pipeline,
- flags: {
- auto_devops: true,
- },
- },
+ createComponent({
+ ...autoDevopsPipeline,
+ });
+
+ expect(trimText(findAutoDevopsTag().text())).toBe('Auto DevOps');
+
+ expect(findAutoDevopsTagLink().attributes()).toMatchObject({
+ href: '/help/topics/autodevops/index.md',
+ target: '_blank',
+ });
});
- expect(trimText(findAutoDevopsTag().text())).toBe('Auto DevOps');
+ it('should render a detached badge when flag is provided', () => {
+ const detachedMRPipeline = defaultProps.pipeline;
+ detachedMRPipeline.flags.detached_merge_request_pipeline = true;
- expect(findAutoDevopsTagLink().attributes()).toMatchObject({
- href: '/help/topics/autodevops/index.md',
- target: '_blank',
+ createComponent({
+ ...detachedMRPipeline,
+ });
+
+ expect(findDetachedTag().text()).toContain('detached');
});
- });
- it('should render a detached badge when flag is provided', () => {
- createComponent({
- pipeline: {
- flags: {
- detached_merge_request_pipeline: true,
- },
- },
+ it('should render error badge when pipeline has a failure reason set', () => {
+ const failedPipeline = defaultProps.pipeline;
+ failedPipeline.flags.failure_reason = true;
+ failedPipeline.failure_reason = 'some reason';
+
+ createComponent({
+ ...failedPipeline,
+ });
+
+ expect(findFailureTag().text()).toContain('error');
+ expect(findFailureTag().attributes('title')).toContain('some reason');
});
- expect(findDetachedTag().text()).toContain('detached');
- });
+ it('should render scheduled badge when pipeline was triggered by a schedule', () => {
+ const scheduledPipeline = defaultProps.pipeline;
+ scheduledPipeline.source = 'schedule';
- it('should render error badge when pipeline has a failure reason set', () => {
- createComponent({
- pipeline: {
- flags: {
- failure_reason: true,
- },
- failure_reason: 'some reason',
- },
+ createComponent({
+ ...scheduledPipeline,
+ });
+
+ expect(findScheduledTag().exists()).toBe(true);
+ expect(findScheduledTag().text()).toContain('Scheduled');
});
- expect(findFailureTag().text()).toContain('error');
- expect(findFailureTag().attributes('title')).toContain('some reason');
- });
+ it('should render the fork badge when the pipeline was run in a fork', () => {
+ const forkedPipeline = defaultProps.pipeline;
+ forkedPipeline.project.full_path = '/test/forked';
- it('should render scheduled badge when pipeline was triggered by a schedule', () => {
- createComponent({
- pipeline: {
- flags: {},
- source: 'schedule',
- },
+ createComponent({
+ ...forkedPipeline,
+ });
+
+ expect(findForkTag().exists()).toBe(true);
+ expect(findForkTag().text()).toBe('fork');
});
- expect(findScheduledTag().exists()).toBe(true);
- expect(findScheduledTag().text()).toContain('Scheduled');
- });
+ it('should render the train badge when the pipeline is a merge train pipeline', () => {
+ const mergeTrainPipeline = defaultProps.pipeline;
+ mergeTrainPipeline.flags.merge_train_pipeline = true;
- it('should render the fork badge when the pipeline was run in a fork', () => {
- createComponent({
- pipeline: {
- flags: {},
- project: { fullPath: '/test/forked' },
- },
+ createComponent({
+ ...mergeTrainPipeline,
+ });
+
+ expect(findTrainTag().text()).toContain('train');
});
- expect(findForkTag().exists()).toBe(true);
- expect(findForkTag().text()).toBe('fork');
- });
+ it('should not render the train badge when the pipeline is not a merge train pipeline', () => {
+ const mergeTrainPipeline = defaultProps.pipeline;
+ mergeTrainPipeline.flags.merge_train_pipeline = false;
- it('should render the train badge when the pipeline is a merge train pipeline', () => {
- createComponent({
- pipeline: {
- flags: {
- merge_train_pipeline: true,
- },
- },
+ createComponent({
+ ...mergeTrainPipeline,
+ });
+
+ expect(findTrainTag().exists()).toBe(false);
});
- expect(findTrainTag().text()).toContain('train');
+ it('should not render the commit wrapper and commit-short-sha', () => {
+ createComponent();
+
+ expect(findCommitTitleContainer().exists()).toBe(false);
+ expect(findCommitShortSha().exists()).toBe(false);
+ });
});
- it('should not render the train badge when the pipeline is not a merge train pipeline', () => {
- createComponent({
- pipeline: {
- flags: {
- merge_train_pipeline: false,
- },
+ describe('with the rearrangePipelinesTable feature flag turned on', () => {
+ it('should render the commit title, commit reference and commit-short-sha', () => {
+ createComponent({}, true);
+
+ const commitWrapper = findCommitTitleContainer();
+
+ expect(findCommitTitle(commitWrapper).exists()).toBe(true);
+ expect(findRefName().exists()).toBe(true);
+ expect(findCommitShortSha().exists()).toBe(true);
+ });
+
+ it('should render commit icon tooltip', () => {
+ createComponent({}, true);
+
+ expect(findCommitIcon().attributes('title')).toBe('Commit');
+ });
+
+ it.each`
+ pipeline | expectedTitle
+ ${mockPipelineTag()} | ${'Tag'}
+ ${mockPipelineBranch()} | ${'Branch'}
+ ${mockPipeline()} | ${'Merge Request'}
+ `(
+ 'should render tooltip $expectedTitle for commit icon type',
+ ({ pipeline, expectedTitle }) => {
+ createComponent(pipeline, true);
+
+ expect(findCommitIconType().attributes('title')).toBe(expectedTitle);
},
+ );
+
+ describe('with commit', () => {
+ beforeEach(() => {
+ createComponent({}, true);
+ });
+
+ it('displays commit title with link to pipeline', () => {
+ expect(findCommitTitle().attributes('href')).toBe(defaultProps.pipeline.path);
+ });
+
+ it('displays commit title text', () => {
+ expect(findCommitTitle().text()).toBe(defaultProps.pipeline.commit.title);
+ });
});
- expect(findTrainTag().exists()).toBe(false);
+ describe('without commit', () => {
+ beforeEach(() => {
+ createComponent(mockPipelineNoCommit(), true);
+ });
+
+ it('displays cant find head commit text', () => {
+ expect(findCommitTitle().text()).toBe("Can't find HEAD commit for this branch");
+ });
+
+ it('displays link to pipeline', () => {
+ expect(findCommitTitle().attributes('href')).toBe(mockPipelineNoCommit().pipeline.path);
+ });
+ });
});
});
diff --git a/spec/frontend/pipelines/pipelines_actions_spec.js b/spec/frontend/pipelines/pipelines_actions_spec.js
index c4bfec8ae14..9b2ee6b8278 100644
--- a/spec/frontend/pipelines/pipelines_actions_spec.js
+++ b/spec/frontend/pipelines/pipelines_actions_spec.js
@@ -1,14 +1,21 @@
import { GlDropdown, GlDropdownItem } 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 { TEST_HOST } from 'spec/test_constants';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
+import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import PipelinesManualActions from '~/pipelines/components/pipelines_list/pipelines_manual_actions.vue';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
jest.mock('~/flash');
+jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal', () => {
+ return {
+ confirmAction: jest.fn(),
+ };
+});
describe('Pipelines Actions dropdown', () => {
let wrapper;
@@ -35,6 +42,7 @@ describe('Pipelines Actions dropdown', () => {
wrapper = null;
mock.restore();
+ confirmAction.mockReset();
});
describe('manual actions', () => {
@@ -68,7 +76,7 @@ describe('Pipelines Actions dropdown', () => {
findAllDropdownItems().at(0).vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findDropdown().props('loading')).toBe(true);
await waitForPromises();
@@ -80,7 +88,7 @@ describe('Pipelines Actions dropdown', () => {
findAllDropdownItems().at(0).vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findDropdown().props('loading')).toBe(true);
await waitForPromises();
@@ -111,11 +119,11 @@ describe('Pipelines Actions dropdown', () => {
it('makes post request after confirming', async () => {
mock.onPost(scheduledJobAction.path).reply(200);
- jest.spyOn(window, 'confirm').mockReturnValue(true);
+ confirmAction.mockResolvedValueOnce(true);
findAllDropdownItems().at(0).vm.$emit('click');
- expect(window.confirm).toHaveBeenCalled();
+ expect(confirmAction).toHaveBeenCalled();
await waitForPromises();
@@ -124,11 +132,11 @@ describe('Pipelines Actions dropdown', () => {
it('does not make post request if confirmation is cancelled', async () => {
mock.onPost(scheduledJobAction.path).reply(200);
- jest.spyOn(window, 'confirm').mockReturnValue(false);
+ confirmAction.mockResolvedValueOnce(false);
findAllDropdownItems().at(0).vm.$emit('click');
- expect(window.confirm).toHaveBeenCalled();
+ expect(confirmAction).toHaveBeenCalled();
await waitForPromises();
diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js
index 6fdbe907aed..f200d683a7a 100644
--- a/spec/frontend/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_spec.js
@@ -9,7 +9,11 @@ import PipelineTriggerer from '~/pipelines/components/pipelines_list/pipeline_tr
import PipelineUrl from '~/pipelines/components/pipelines_list/pipeline_url.vue';
import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue';
import PipelinesTimeago from '~/pipelines/components/pipelines_list/time_ago.vue';
-import { PipelineKeyOptions } from '~/pipelines/constants';
+import {
+ PipelineKeyOptions,
+ BUTTON_TOOLTIP_RETRY,
+ BUTTON_TOOLTIP_CANCEL,
+} from '~/pipelines/constants';
import eventHub from '~/pipelines/event_hub';
import CiBadge from '~/vue_shared/components/ci_badge_link.vue';
@@ -33,13 +37,18 @@ describe('Pipelines Table', () => {
return pipelines.find((p) => p.user !== null && p.commit !== null);
};
- const createComponent = (props = {}) => {
+ const createComponent = (props = {}, rearrangePipelinesTable = false) => {
wrapper = extendedWrapper(
mount(PipelinesTable, {
propsData: {
...defaultProps,
...props,
},
+ provide: {
+ glFeatures: {
+ rearrangePipelinesTable,
+ },
+ },
}),
);
};
@@ -61,6 +70,8 @@ describe('Pipelines Table', () => {
const findStagesTh = () => wrapper.findByTestId('stages-th');
const findTimeAgoTh = () => wrapper.findByTestId('timeago-th');
const findActionsTh = () => wrapper.findByTestId('actions-th');
+ const findRetryBtn = () => wrapper.findByTestId('pipelines-retry-button');
+ const findCancelBtn = () => wrapper.findByTestId('pipelines-cancel-button');
beforeEach(() => {
pipeline = createMockPipeline();
@@ -71,7 +82,7 @@ describe('Pipelines Table', () => {
wrapper = null;
});
- describe('Pipelines Table', () => {
+ describe('Pipelines Table with rearrangePipelinesTable feature flag turned off', () => {
beforeEach(() => {
createComponent({ pipelines: [pipeline], viewType: 'root' });
});
@@ -187,6 +198,39 @@ describe('Pipelines Table', () => {
it('should render pipeline operations', () => {
expect(findActions().exists()).toBe(true);
});
+
+ it('should render retry action tooltip', () => {
+ expect(findRetryBtn().attributes('title')).toBe(BUTTON_TOOLTIP_RETRY);
+ });
+
+ it('should render cancel action tooltip', () => {
+ expect(findCancelBtn().attributes('title')).toBe(BUTTON_TOOLTIP_CANCEL);
+ });
+ });
+ });
+
+ describe('Pipelines Table with rearrangePipelinesTable feature flag turned on', () => {
+ beforeEach(() => {
+ createComponent({ pipelines: [pipeline], viewType: 'root' }, true);
+ });
+
+ it('should render table head with correct columns', () => {
+ expect(findStatusTh().text()).toBe('Status');
+ expect(findPipelineTh().text()).toBe('Pipeline');
+ expect(findStagesTh().text()).toBe('Stages');
+ expect(findActionsTh().text()).toBe('Actions');
+ });
+
+ describe('triggerer cell', () => {
+ it('should render the pipeline triggerer', () => {
+ expect(findTriggerer().exists()).toBe(true);
+ });
+ });
+
+ describe('commit cell', () => {
+ it('should not render commit information', () => {
+ expect(findCommit().exists()).toBe(false);
+ });
});
});
});
diff --git a/spec/frontend/pipelines/test_reports/test_case_details_spec.js b/spec/frontend/pipelines/test_reports/test_case_details_spec.js
index c995eb864d1..4b33c1522a5 100644
--- a/spec/frontend/pipelines/test_reports/test_case_details_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_case_details_spec.js
@@ -1,11 +1,9 @@
import { GlModal } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import TestCaseDetails from '~/pipelines/components/test_reports/test_case_details.vue';
import CodeBlock from '~/vue_shared/components/code_block.vue';
-const localVue = createLocalVue();
-
describe('Test case details', () => {
let wrapper;
const defaultTestCase = {
@@ -29,7 +27,6 @@ describe('Test case details', () => {
const createComponent = (testCase = {}) => {
wrapper = extendedWrapper(
shallowMount(TestCaseDetails, {
- localVue,
propsData: {
modalId: 'my-modal',
testCase: {
diff --git a/spec/frontend/pipelines/test_reports/test_reports_spec.js b/spec/frontend/pipelines/test_reports/test_reports_spec.js
index 384b7cf6930..e0daf8cb4b5 100644
--- a/spec/frontend/pipelines/test_reports/test_reports_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_reports_spec.js
@@ -1,5 +1,6 @@
import { GlLoadingIcon } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import testReports from 'test_fixtures/pipelines/test_report.json';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
@@ -9,8 +10,7 @@ import TestSummary from '~/pipelines/components/test_reports/test_summary.vue';
import TestSummaryTable from '~/pipelines/components/test_reports/test_summary_table.vue';
import * as getters from '~/pipelines/stores/test_reports/getters';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Test reports app', () => {
let wrapper;
@@ -44,7 +44,6 @@ describe('Test reports app', () => {
wrapper = extendedWrapper(
shallowMount(TestReports, {
store,
- localVue,
}),
);
};
diff --git a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
index 793bad6b82a..97241e14129 100644
--- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
@@ -1,5 +1,6 @@
import { GlButton, GlFriendlyWrap, GlLink, GlPagination } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import testReports from 'test_fixtures/pipelines/test_report.json';
import SuiteTable from '~/pipelines/components/test_reports/test_suite_table.vue';
@@ -8,8 +9,7 @@ import * as getters from '~/pipelines/stores/test_reports/getters';
import { formatFilePath } from '~/pipelines/stores/test_reports/utils';
import skippedTestCases from './mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Test reports suite table', () => {
let wrapper;
@@ -47,7 +47,6 @@ describe('Test reports suite table', () => {
wrapper = shallowMount(SuiteTable, {
store,
- localVue,
stubs: { GlFriendlyWrap },
});
};
diff --git a/spec/frontend/pipelines/test_reports/test_summary_table_spec.js b/spec/frontend/pipelines/test_reports/test_summary_table_spec.js
index 0813739d72f..1598d5c337f 100644
--- a/spec/frontend/pipelines/test_reports/test_summary_table_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_summary_table_spec.js
@@ -1,11 +1,11 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import testReports from 'test_fixtures/pipelines/test_report.json';
import SummaryTable from '~/pipelines/components/test_reports/test_summary_table.vue';
import * as getters from '~/pipelines/stores/test_reports/getters';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Test reports summary table', () => {
let wrapper;
@@ -29,7 +29,6 @@ describe('Test reports summary table', () => {
wrapper = mount(SummaryTable, {
propsData: defaultProps,
store,
- localVue,
});
};
diff --git a/spec/frontend/popovers/components/popovers_spec.js b/spec/frontend/popovers/components/popovers_spec.js
index 2751a878e51..6fdcd34ae83 100644
--- a/spec/frontend/popovers/components/popovers_spec.js
+++ b/spec/frontend/popovers/components/popovers_spec.js
@@ -1,5 +1,6 @@
import { GlPopover } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { useMockMutationObserver } from 'helpers/mock_dom_observer';
import Popovers from '~/popovers/components/popovers.vue';
@@ -7,10 +8,10 @@ describe('popovers/components/popovers.vue', () => {
const { trigger: triggerMutate } = useMockMutationObserver();
let wrapper;
- const buildWrapper = (...targets) => {
+ const buildWrapper = async (...targets) => {
wrapper = shallowMount(Popovers);
wrapper.vm.addPopovers(targets);
- return wrapper.vm.$nextTick();
+ await nextTick();
};
const createPopoverTarget = (options = {}) => {
@@ -49,7 +50,7 @@ describe('popovers/components/popovers.vue', () => {
buildWrapper(target);
wrapper.vm.addPopovers([target]);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.findAll(GlPopover)).toHaveLength(1);
});
@@ -86,7 +87,7 @@ describe('popovers/components/popovers.vue', () => {
await buildWrapper(createPopoverTarget(), createPopoverTarget());
wrapper.vm.dispose();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(allPopovers()).toHaveLength(0);
});
@@ -97,7 +98,7 @@ describe('popovers/components/popovers.vue', () => {
await buildWrapper(target, createPopoverTarget());
wrapper.vm.dispose(target);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(allPopovers()).toHaveLength(1);
});
@@ -109,13 +110,13 @@ describe('popovers/components/popovers.vue', () => {
await buildWrapper(target);
wrapper.vm.addPopovers([target, createPopoverTarget()]);
- await wrapper.vm.$nextTick();
+ await nextTick();
triggerMutate(document.body, {
entry: { removedNodes: [target] },
options: { childList: true },
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(allPopovers()).toHaveLength(1);
});
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/profile/account/components/delete_account_modal_spec.js b/spec/frontend/profile/account/components/delete_account_modal_spec.js
index f1784500baf..ad62d84c43c 100644
--- a/spec/frontend/profile/account/components/delete_account_modal_spec.js
+++ b/spec/frontend/profile/account/components/delete_account_modal_spec.js
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
import { merge } from 'lodash';
-import Vue from 'vue';
+import { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import deleteAccountModal from '~/profile/account/components/delete_account_modal.vue';
@@ -56,7 +56,7 @@ describe('DeleteAccountModal component', () => {
const findModal = () => wrapper.find(GlModalStub);
describe('with password confirmation', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
createWrapper({
propsData: {
confirmWithPassword: true,
@@ -65,48 +65,40 @@ describe('DeleteAccountModal component', () => {
vm.isOpen = true;
- Vue.nextTick().then(done).catch(done.fail);
+ await nextTick();
});
- it('does not accept empty password', (done) => {
+ it('does not accept empty password', async () => {
const { form, input } = findElements();
jest.spyOn(form, 'submit').mockImplementation(() => {});
input.value = '';
input.dispatchEvent(new Event('input'));
- Vue.nextTick()
- .then(() => {
- expect(vm.enteredPassword).toBe(input.value);
- expect(findModal().attributes('ok-disabled')).toBe('true');
- findModal().vm.$emit('primary');
+ await nextTick();
+ expect(vm.enteredPassword).toBe(input.value);
+ expect(findModal().attributes('ok-disabled')).toBe('true');
+ findModal().vm.$emit('primary');
- expect(form.submit).not.toHaveBeenCalled();
- })
- .then(done)
- .catch(done.fail);
+ expect(form.submit).not.toHaveBeenCalled();
});
- it('submits form with password', (done) => {
+ it('submits form with password', async () => {
const { form, input } = findElements();
jest.spyOn(form, 'submit').mockImplementation(() => {});
input.value = 'anything';
input.dispatchEvent(new Event('input'));
- Vue.nextTick()
- .then(() => {
- expect(vm.enteredPassword).toBe(input.value);
- expect(findModal().attributes('ok-disabled')).toBeUndefined();
- findModal().vm.$emit('primary');
+ await nextTick();
+ expect(vm.enteredPassword).toBe(input.value);
+ expect(findModal().attributes('ok-disabled')).toBeUndefined();
+ findModal().vm.$emit('primary');
- expect(form.submit).toHaveBeenCalled();
- })
- .then(done)
- .catch(done.fail);
+ expect(form.submit).toHaveBeenCalled();
});
});
describe('with username confirmation', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
createWrapper({
propsData: {
confirmWithPassword: false,
@@ -115,43 +107,35 @@ describe('DeleteAccountModal component', () => {
vm.isOpen = true;
- Vue.nextTick().then(done).catch(done.fail);
+ await nextTick();
});
- it('does not accept wrong username', (done) => {
+ it('does not accept wrong username', async () => {
const { form, input } = findElements();
jest.spyOn(form, 'submit').mockImplementation(() => {});
input.value = 'this is wrong';
input.dispatchEvent(new Event('input'));
- Vue.nextTick()
- .then(() => {
- expect(vm.enteredUsername).toBe(input.value);
- expect(findModal().attributes('ok-disabled')).toBe('true');
- findModal().vm.$emit('primary');
+ await nextTick();
+ expect(vm.enteredUsername).toBe(input.value);
+ expect(findModal().attributes('ok-disabled')).toBe('true');
+ findModal().vm.$emit('primary');
- expect(form.submit).not.toHaveBeenCalled();
- })
- .then(done)
- .catch(done.fail);
+ expect(form.submit).not.toHaveBeenCalled();
});
- it('submits form with correct username', (done) => {
+ it('submits form with correct username', async () => {
const { form, input } = findElements();
jest.spyOn(form, 'submit').mockImplementation(() => {});
input.value = username;
input.dispatchEvent(new Event('input'));
- Vue.nextTick()
- .then(() => {
- expect(vm.enteredUsername).toBe(input.value);
- expect(findModal().attributes('ok-disabled')).toBeUndefined();
- findModal().vm.$emit('primary');
+ await nextTick();
+ expect(vm.enteredUsername).toBe(input.value);
+ expect(findModal().attributes('ok-disabled')).toBeUndefined();
+ findModal().vm.$emit('primary');
- expect(form.submit).toHaveBeenCalled();
- })
- .then(done)
- .catch(done.fail);
+ expect(form.submit).toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/profile/account/components/update_username_spec.js b/spec/frontend/profile/account/components/update_username_spec.js
index bda07af4feb..e342b7c4ba1 100644
--- a/spec/frontend/profile/account/components/update_username_spec.js
+++ b/spec/frontend/profile/account/components/update_username_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 { TEST_HOST } from 'helpers/test_constants';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
@@ -58,7 +59,7 @@ describe('UpdateUsername component', () => {
it('has a disabled button if the username was not changed', async () => {
const { openModalBtn } = findElements();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(openModalBtn.props('disabled')).toBe(true);
});
@@ -69,7 +70,7 @@ describe('UpdateUsername component', () => {
input.element.value = 'newUsername';
input.trigger('input');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(openModalBtn.props('disabled')).toBe(false);
});
@@ -83,7 +84,7 @@ describe('UpdateUsername component', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ newUsername });
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('confirmation modal contains proper header and body', async () => {
@@ -100,7 +101,7 @@ describe('UpdateUsername component', () => {
jest.spyOn(axios, 'put');
await wrapper.vm.onConfirm();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(axios.put).toHaveBeenCalledWith(actionUrl, { user: { username: newUsername } });
});
@@ -117,7 +118,7 @@ describe('UpdateUsername component', () => {
});
await wrapper.vm.onConfirm();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(input.attributes('disabled')).toBe(undefined);
expect(openModalBtn.props('disabled')).toBe(true);
diff --git a/spec/frontend/projects/commit/components/branches_dropdown_spec.js b/spec/frontend/projects/commit/components/branches_dropdown_spec.js
index 30556cdeae1..e2848e615c3 100644
--- a/spec/frontend/projects/commit/components/branches_dropdown_spec.js
+++ b/spec/frontend/projects/commit/components/branches_dropdown_spec.js
@@ -1,6 +1,6 @@
import { GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
@@ -115,7 +115,7 @@ describe('BranchesDropdown', () => {
findSearchBoxByType().vm.$emit('input', '_anything_');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(spy).toHaveBeenCalledWith('_anything_');
expect(wrapper.vm.searchTerm).toBe('_anything_');
diff --git a/spec/frontend/projects/commit/components/form_modal_spec.js b/spec/frontend/projects/commit/components/form_modal_spec.js
index 93e2ae13628..79e9dab935d 100644
--- a/spec/frontend/projects/commit/components/form_modal_spec.js
+++ b/spec/frontend/projects/commit/components/form_modal_spec.js
@@ -2,6 +2,7 @@ import { GlModal, GlForm, GlFormCheckbox, GlSprintf } from '@gitlab/ui';
import { within } from '@testing-library/dom';
import { shallowMount, mount, createWrapper } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import api from '~/api';
import axios from '~/lib/utils/axios_utils';
@@ -156,7 +157,7 @@ describe('CommitFormModal', () => {
it('Changes the start_branch input value', async () => {
findBranchesDropdown().vm.$emit('selectBranch', '_changed_branch_value_');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findStartBranch().attributes('value')).toBe('_changed_branch_value_');
});
@@ -165,7 +166,7 @@ describe('CommitFormModal', () => {
createComponent(shallowMount, {}, {}, { isCherryPick: true });
findProjectsDropdown().vm.$emit('selectProject', '_changed_project_value_');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findTargetProject().attributes('value')).toBe('_changed_project_value_');
});
@@ -174,7 +175,7 @@ describe('CommitFormModal', () => {
it('action primary button triggers Redis HLL tracking api call', async () => {
createComponent(mount, {}, {}, { primaryActionEventName: 'test_event' });
- await wrapper.vm.$nextTick();
+ await nextTick();
jest.spyOn(findForm().element, 'submit');
diff --git a/spec/frontend/projects/commits/components/author_select_spec.js b/spec/frontend/projects/commits/components/author_select_spec.js
index 23b4cccd92c..4e567ab030e 100644
--- a/spec/frontend/projects/commits/components/author_select_spec.js
+++ b/spec/frontend/projects/commits/components/author_select_spec.js
@@ -1,12 +1,12 @@
import { GlDropdown, GlDropdownSectionHeader, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import * as urlUtility from '~/lib/utils/url_utility';
import AuthorSelect from '~/projects/commits/components/author_select.vue';
import { createStore } from '~/projects/commits/store';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
const commitsPath = 'author/search/url';
const currentAuthor = 'lorem';
@@ -38,7 +38,6 @@ describe('Author Select', () => {
`);
wrapper = shallowMount(AuthorSelect, {
- localVue,
store: new Vuex.Store(store),
propsData: {
projectCommitsEl: document.querySelector('.js-project-commits-show'),
@@ -64,36 +63,33 @@ describe('Author Select', () => {
const findDropdownItems = () => wrapper.findAll(GlDropdownItem);
describe('user is searching via "filter by commit message"', () => {
- it('disables dropdown container', () => {
+ it('disables dropdown container', 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({ hasSearchParam: true });
- return wrapper.vm.$nextTick().then(() => {
- expect(findDropdownContainer().attributes('disabled')).toBeFalsy();
- });
+ await nextTick();
+ expect(findDropdownContainer().attributes('disabled')).toBeFalsy();
});
- it('has correct tooltip message', () => {
+ it('has correct tooltip message', 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({ hasSearchParam: true });
- return wrapper.vm.$nextTick().then(() => {
- expect(findDropdownContainer().attributes('title')).toBe(
- 'Searching by both author and message is currently not supported.',
- );
- });
+ await nextTick();
+ expect(findDropdownContainer().attributes('title')).toBe(
+ 'Searching by both author and message is currently not supported.',
+ );
});
- it('disables dropdown', () => {
+ it('disables dropdown', 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({ hasSearchParam: false });
- return wrapper.vm.$nextTick().then(() => {
- expect(findDropdown().attributes('disabled')).toBeFalsy();
- });
+ await nextTick();
+ expect(findDropdown().attributes('disabled')).toBeFalsy();
});
it('hasSearchParam if user types a truthy string', () => {
@@ -108,14 +104,13 @@ describe('Author Select', () => {
expect(findDropdown().attributes('text')).toBe('Author');
});
- it('displays the current selected author', () => {
+ it('displays the current selected author', 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({ currentAuthor });
- return wrapper.vm.$nextTick().then(() => {
- expect(findDropdown().attributes('text')).toBe(currentAuthor);
- });
+ await nextTick();
+ expect(findDropdown().attributes('text')).toBe(currentAuthor);
});
it('displays correct header text', () => {
@@ -150,13 +145,12 @@ describe('Author Select', () => {
expect(findDropdownItems().at(0).text()).toBe('Any Author');
});
- it('displays the project authors', () => {
- return wrapper.vm.$nextTick().then(() => {
- expect(findDropdownItems()).toHaveLength(authors.length + 1);
- });
+ it('displays the project authors', async () => {
+ await nextTick();
+ expect(findDropdownItems()).toHaveLength(authors.length + 1);
});
- it('has the correct props', () => {
+ it('has the correct props', async () => {
const [{ avatar_url, username }] = authors;
const result = {
avatarUrl: avatar_url,
@@ -168,15 +162,13 @@ describe('Author Select', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ currentAuthor });
- return wrapper.vm.$nextTick().then(() => {
- expect(findDropdownItems().at(1).props()).toEqual(expect.objectContaining(result));
- });
+ await nextTick();
+ expect(findDropdownItems().at(1).props()).toEqual(expect.objectContaining(result));
});
- it("display the author's name", () => {
- return wrapper.vm.$nextTick().then(() => {
- expect(findDropdownItems().at(1).text()).toBe(currentAuthor);
- });
+ it("display the author's name", async () => {
+ await nextTick();
+ expect(findDropdownItems().at(1).text()).toBe(currentAuthor);
});
it('passes selected author to redirectPath', () => {
diff --git a/spec/frontend/projects/compare/components/app_spec.js b/spec/frontend/projects/compare/components/app_spec.js
index 7989a6f3d74..18e7f2e0f6e 100644
--- a/spec/frontend/projects/compare/components/app_spec.js
+++ b/spec/frontend/projects/compare/components/app_spec.js
@@ -1,5 +1,6 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import CompareApp from '~/projects/compare/components/app.vue';
import RevisionCard from '~/projects/compare/components/revision_card.vue';
import { appDefaultProps as defaultProps } from './mock_data';
@@ -91,7 +92,7 @@ describe('CompareApp component', () => {
project,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findTargetRevisionCard().props('selectedProject')).toEqual(
expect.objectContaining(project),
@@ -106,7 +107,7 @@ describe('CompareApp component', () => {
revision,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findSourceRevisionCard().props('paramsBranch')).toBe(revision);
});
@@ -125,7 +126,7 @@ describe('CompareApp component', () => {
it('swaps revisions when clicked', async () => {
findSwapRevisionsButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findTargetRevisionCard().props('paramsBranch')).toBe(defaultProps.paramsTo);
expect(findSourceRevisionCard().props('paramsBranch')).toBe(defaultProps.paramsFrom);
diff --git a/spec/frontend/projects/compare/components/repo_dropdown_spec.js b/spec/frontend/projects/compare/components/repo_dropdown_spec.js
index 27a7a32ebca..98aec347e4b 100644
--- a/spec/frontend/projects/compare/components/repo_dropdown_spec.js
+++ b/spec/frontend/projects/compare/components/repo_dropdown_spec.js
@@ -1,5 +1,6 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import RepoDropdown from '~/projects/compare/components/repo_dropdown.vue';
import { revisionCardDefaultProps as defaultProps } from './mock_data';
@@ -39,7 +40,7 @@ describe('RepoDropdown component', () => {
it('does not emit `changeTargetProject` event', async () => {
wrapper.vm.emitTargetProject('foo');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('changeTargetProject')).toBeUndefined();
});
});
@@ -67,13 +68,13 @@ describe('RepoDropdown component', () => {
it('updates the hidden input value when onClick method is triggered', async () => {
const repoId = '1';
wrapper.vm.onClick({ id: repoId });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findHiddenInput().attributes('value')).toBe(repoId);
});
it('emits `selectProject` event when another target project is selected', async () => {
findGlDropdown().findAll(GlDropdownItem).at(0).vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('selectProject')[0][0]).toEqual({
direction: 'from',
diff --git a/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js b/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js
index eb80d57fb3c..102f95f65da 100644
--- a/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js
+++ b/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js
@@ -1,6 +1,7 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import RevisionDropdown from '~/projects/compare/components/revision_dropdown_legacy.vue';
@@ -105,7 +106,7 @@ describe('RevisionDropdown component', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ branches: ['some-branch'] });
- await wrapper.vm.$nextTick();
+ await nextTick();
findFirstGlDropdownItem().vm.$emit('click');
diff --git a/spec/frontend/projects/compare/components/revision_dropdown_spec.js b/spec/frontend/projects/compare/components/revision_dropdown_spec.js
index 118bb68585e..c8a90848492 100644
--- a/spec/frontend/projects/compare/components/revision_dropdown_spec.js
+++ b/spec/frontend/projects/compare/components/revision_dropdown_spec.js
@@ -1,6 +1,7 @@
import { GlDropdown, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import RevisionDropdown from '~/projects/compare/components/revision_dropdown.vue';
@@ -141,7 +142,7 @@ describe('RevisionDropdown component', () => {
it('emits `selectRevision` event when another revision is selected', async () => {
createComponent();
wrapper.vm.branches = ['some-branch'];
- await wrapper.vm.$nextTick();
+ await nextTick();
findGlDropdown().findAll(GlDropdownItem).at(0).vm.$emit('click');
diff --git a/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap b/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap
index e1e1aac09aa..b8f9951bbfc 100644
--- a/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap
+++ b/spec/frontend/projects/components/__snapshots__/project_delete_button_spec.js.snap
@@ -49,16 +49,17 @@ exports[`Project remove modal initialized matches the snapshot 1`] = `
primarybuttontext=""
secondarybuttonlink=""
secondarybuttontext=""
- title="You are about to permanently delete this project"
+ title=""
variant="danger"
>
- <p>
- This project is
- <strong>
- NOT
- </strong>
- a fork, and has the following:
- </p>
+ <h4
+ class="gl-alert-title"
+ data-testid="delete-alert-title"
+ >
+
+ You are about to delete this project containing:
+
+ </h4>
<ul>
<li>
@@ -77,25 +78,17 @@ exports[`Project remove modal initialized matches the snapshot 1`] = `
4 stars
</li>
</ul>
- After a project is permanently deleted, it
- <strong>
- cannot be recovered
- </strong>
- . Permanently deleting this project will
- <strong>
- immediately delete
- </strong>
- its repositories and
+ This project is
<strong>
- all related resources
+ NOT
</strong>
- , including issues, merge requests etc.
+ a fork. This process deletes the project repository and all related resources.
</gl-alert-stub>
<p
class="gl-mb-1"
>
- Please type the following to confirm:
+ Enter the following to confirm:
</p>
<p>
diff --git a/spec/frontend/projects/components/project_delete_button_spec.js b/spec/frontend/projects/components/project_delete_button_spec.js
index bb6021fadda..a3bc4931eb3 100644
--- a/spec/frontend/projects/components/project_delete_button_spec.js
+++ b/spec/frontend/projects/components/project_delete_button_spec.js
@@ -50,7 +50,12 @@ describe('Project remove modal', () => {
it('passes confirmPhrase and formPath props to the shared delete button', () => {
expect(findSharedDeleteButton().props()).toEqual({
confirmPhrase: defaultProps.confirmPhrase,
+ forksCount: defaultProps.forksCount,
formPath: defaultProps.formPath,
+ isFork: defaultProps.isFork,
+ issuesCount: defaultProps.issuesCount,
+ mergeRequestsCount: defaultProps.mergeRequestsCount,
+ starsCount: defaultProps.starsCount,
});
});
});
diff --git a/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap b/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap
index dd54db7dc0a..2d1039a8743 100644
--- a/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap
+++ b/spec/frontend/projects/components/shared/__snapshots__/delete_button_spec.js.snap
@@ -34,13 +34,63 @@ exports[`Project remove modal intialized matches the snapshot 1`] = `
ok-variant="danger"
title-class="gl-text-red-500"
>
- Delete project. Are you ABSOLUTELY SURE?
+ Are you absolutely sure?
<div>
+ <gl-alert-stub
+ class="gl-mb-5"
+ dismisslabel="Dismiss"
+ primarybuttonlink=""
+ primarybuttontext=""
+ secondarybuttonlink=""
+ secondarybuttontext=""
+ title=""
+ variant="danger"
+ >
+ <h4
+ class="gl-alert-title"
+ data-testid="delete-alert-title"
+ >
+
+ You are about to delete this project containing:
+
+ </h4>
+
+ <ul>
+ <li>
+ <gl-sprintf-stub
+ message="1 issue"
+ />
+ </li>
+
+ <li>
+ <gl-sprintf-stub
+ message="2 merge requests"
+ />
+ </li>
+
+ <li>
+ <gl-sprintf-stub
+ message="3 forks"
+ />
+ </li>
+
+ <li>
+ <gl-sprintf-stub
+ message="4 stars"
+ />
+ </li>
+ </ul>
+
+ <gl-sprintf-stub
+ data-testid="delete-alert-body"
+ message="This project is %{strongStart}NOT%{strongEnd} a fork. This process deletes the project repository and all related resources."
+ />
+ </gl-alert-stub>
<p
class="gl-mb-1"
>
- Please type the following to confirm:
+ Enter the following to confirm:
</p>
<p>
diff --git a/spec/frontend/projects/components/shared/delete_button_spec.js b/spec/frontend/projects/components/shared/delete_button_spec.js
index 3e491584670..45c39ee91d8 100644
--- a/spec/frontend/projects/components/shared/delete_button_spec.js
+++ b/spec/frontend/projects/components/shared/delete_button_spec.js
@@ -12,15 +12,25 @@ describe('Project remove modal', () => {
const findConfirmButton = () => wrapper.find('.js-modal-action-primary');
const findAuthenticityTokenInput = () => findFormElement().find('input[name=authenticity_token]');
const findModal = () => wrapper.find(GlModal);
+ const findTitle = () => wrapper.find('[data-testid="delete-alert-title"]');
+ const findAlertBody = () => wrapper.find('[data-testid="delete-alert-body"]');
const defaultProps = {
confirmPhrase: 'foo',
formPath: 'some/path',
+ isFork: false,
+ issuesCount: 1,
+ mergeRequestsCount: 2,
+ forksCount: 3,
+ starsCount: 4,
};
- const createComponent = (data = {}, stubs = {}) => {
+ const createComponent = (data = {}, stubs = {}, props = {}) => {
wrapper = shallowMount(SharedDeleteButton, {
- propsData: defaultProps,
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
data: () => data,
stubs: {
GlModal: stubComponent(GlModal, {
@@ -88,4 +98,20 @@ describe('Project remove modal', () => {
expect(findFormElement().element.submit).toHaveBeenCalled();
});
});
+
+ describe('when project is a fork', () => {
+ beforeEach(() => {
+ createComponent({}, {}, { isFork: true });
+ });
+
+ it('matches the fork title', () => {
+ expect(findTitle().text()).toEqual('You are about to delete this forked project containing:');
+ });
+
+ it('matches the fork body', () => {
+ expect(findAlertBody().attributes().message).toEqual(
+ 'This process deletes the project repository and all related resources.',
+ );
+ });
+ });
});
diff --git a/spec/frontend/projects/new/components/deployment_target_select_spec.js b/spec/frontend/projects/new/components/deployment_target_select_spec.js
new file mode 100644
index 00000000000..8fe4c5f1230
--- /dev/null
+++ b/spec/frontend/projects/new/components/deployment_target_select_spec.js
@@ -0,0 +1,82 @@
+import { GlFormGroup, GlFormSelect } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { mockTracking } from 'helpers/tracking_helper';
+import DeploymentTargetSelect from '~/projects/new/components/deployment_target_select.vue';
+import {
+ DEPLOYMENT_TARGET_SELECTIONS,
+ DEPLOYMENT_TARGET_LABEL,
+ DEPLOYMENT_TARGET_EVENT,
+ NEW_PROJECT_FORM,
+} from '~/projects/new/constants';
+
+describe('Deployment target select', () => {
+ let wrapper;
+ let trackingSpy;
+
+ const findFormGroup = () => wrapper.findComponent(GlFormGroup);
+ const findSelect = () => wrapper.findComponent(GlFormSelect);
+
+ const createdWrapper = () => {
+ wrapper = shallowMount(DeploymentTargetSelect, {
+ stubs: {
+ GlFormSelect,
+ },
+ });
+ };
+
+ const createForm = () => {
+ setFixtures(`
+ <form id="${NEW_PROJECT_FORM}">
+ </form>
+ `);
+ };
+
+ beforeEach(() => {
+ createForm();
+ createdWrapper();
+
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders the correct label', () => {
+ expect(findFormGroup().attributes('label')).toBe('Project deployment target (optional)');
+ });
+
+ it('renders a select with the disabled default option', () => {
+ expect(findSelect().find('option').text()).toBe('Select the deployment target');
+ expect(findSelect().find('option').attributes('disabled')).toBe('disabled');
+ });
+
+ describe.each`
+ selectedTarget | formSubmitted | eventSent
+ ${null} | ${true} | ${false}
+ ${DEPLOYMENT_TARGET_SELECTIONS[0]} | ${false} | ${false}
+ ${DEPLOYMENT_TARGET_SELECTIONS[0]} | ${true} | ${true}
+ `('Snowplow tracking event', ({ selectedTarget, formSubmitted, eventSent }) => {
+ beforeEach(() => {
+ findSelect().vm.$emit('input', selectedTarget);
+
+ if (formSubmitted) {
+ const form = document.getElementById(NEW_PROJECT_FORM);
+ form.dispatchEvent(new Event('submit'));
+ }
+ });
+
+ if (eventSent) {
+ it(`is sent, when the the selectedTarget is ${selectedTarget} and the formSubmitted is ${formSubmitted} `, () => {
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, DEPLOYMENT_TARGET_EVENT, {
+ label: DEPLOYMENT_TARGET_LABEL,
+ property: selectedTarget,
+ });
+ });
+ } else {
+ it(`is not sent, when the the selectedTarget is ${selectedTarget} and the formSubmitted is ${formSubmitted} `, () => {
+ expect(trackingSpy).toHaveBeenCalledTimes(0);
+ });
+ }
+ });
+});
diff --git a/spec/frontend/projects/new/components/new_project_url_select_spec.js b/spec/frontend/projects/new/components/new_project_url_select_spec.js
index 258fa7636d4..921f5b74278 100644
--- a/spec/frontend/projects/new/components/new_project_url_select_spec.js
+++ b/spec/frontend/projects/new/components/new_project_url_select_spec.js
@@ -6,9 +6,10 @@ import {
GlSearchBoxByType,
} from '@gitlab/ui';
import { mount, 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';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import eventHub from '~/projects/new/event_hub';
@@ -94,13 +95,14 @@ describe('NewProjectUrlSelect component', () => {
const clickDropdownItem = async () => {
wrapper.findComponent(GlDropdownItem).vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
};
const showDropdown = async () => {
findDropdown().vm.$emit('shown');
await wrapper.vm.$apollo.queries.currentUser.refetch();
jest.runOnlyPendingTimers();
+ await waitForPromises();
};
afterEach(() => {
@@ -235,8 +237,7 @@ describe('NewProjectUrlSelect component', () => {
};
wrapper = mountComponent({ search: 'no matches', queryResponse, mountFn: mount });
- jest.runOnlyPendingTimers();
- await wrapper.vm.$nextTick();
+ await waitForPromises();
expect(wrapper.find('li').text()).toBe('No matches found');
});
diff --git a/spec/frontend/projects/pipelines/charts/components/app_spec.js b/spec/frontend/projects/pipelines/charts/components/app_spec.js
index 574756322c7..9c94925c817 100644
--- a/spec/frontend/projects/pipelines/charts/components/app_spec.js
+++ b/spec/frontend/projects/pipelines/charts/components/app_spec.js
@@ -1,5 +1,6 @@
import { GlTabs, GlTab } from '@gitlab/ui';
import { merge } from 'lodash';
+import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
@@ -99,7 +100,7 @@ describe('ProjectsPipelinesChartsApp', () => {
tabs.vm.$emit('input', 1);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(tabs.attributes('value')).toBe('1');
});
@@ -115,7 +116,7 @@ describe('ProjectsPipelinesChartsApp', () => {
tabs.vm.$emit('input', 0);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(updateHistory).not.toHaveBeenCalled();
});
@@ -183,7 +184,7 @@ describe('ProjectsPipelinesChartsApp', () => {
popstateHandler();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findGlTabs().attributes('value')).toBe('1');
});
diff --git a/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_charts_spec.js b/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_charts_spec.js
index 9adc6dba51e..cafb3f231bd 100644
--- a/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_charts_spec.js
+++ b/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_charts_spec.js
@@ -1,6 +1,6 @@
-import { GlSegmentedControl } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
+import { GlSegmentedControl } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import CiCdAnalyticsAreaChart from '~/vue_shared/components/ci_cd_analytics/ci_cd_analytics_area_chart.vue';
import CiCdAnalyticsCharts from '~/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue';
import { transformedAreaChartData, chartOptions } from '../mock_data';
@@ -29,12 +29,15 @@ const DEFAULT_PROPS = {
describe('~/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue', () => {
let wrapper;
- const createWrapper = (props = {}) =>
- shallowMount(CiCdAnalyticsCharts, {
+ const createWrapper = (props = {}, slots = {}) =>
+ shallowMountExtended(CiCdAnalyticsCharts, {
propsData: {
...DEFAULT_PROPS,
...props,
},
+ scopedSlots: {
+ ...slots,
+ },
});
afterEach(() => {
@@ -44,20 +47,20 @@ describe('~/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue', (
}
});
- describe('segmented control', () => {
- let segmentedControl;
+ const findMetricsSlot = () => wrapper.findByTestId('metrics-slot');
+ const findSegmentedControl = () => wrapper.findComponent(GlSegmentedControl);
+ describe('segmented control', () => {
beforeEach(() => {
wrapper = createWrapper();
- segmentedControl = wrapper.find(GlSegmentedControl);
});
it('should default to the first chart', () => {
- expect(segmentedControl.props('checked')).toBe(0);
+ expect(findSegmentedControl().props('checked')).toBe(0);
});
it('should use the title and index as values', () => {
- const options = segmentedControl.props('options');
+ const options = findSegmentedControl().props('options');
expect(options).toHaveLength(3);
expect(options).toEqual([
{
@@ -76,7 +79,7 @@ describe('~/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue', (
});
it('should select a different chart on change', async () => {
- segmentedControl.vm.$emit('input', 1);
+ findSegmentedControl().vm.$emit('input', 1);
const chart = wrapper.find(CiCdAnalyticsAreaChart);
@@ -91,4 +94,24 @@ describe('~/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue', (
wrapper = createWrapper({ charts: [] });
expect(wrapper.find(CiCdAnalyticsAreaChart).exists()).toBe(false);
});
+
+ describe('slots', () => {
+ beforeEach(() => {
+ wrapper = createWrapper(
+ {},
+ {
+ metrics: '<div data-testid="metrics-slot">selected chart: {{props.selectedChart}}</div>',
+ },
+ );
+ });
+
+ it('renders a metrics slot', async () => {
+ const selectedChart = 1;
+ findSegmentedControl().vm.$emit('input', selectedChart);
+
+ await nextTick();
+
+ expect(findMetricsSlot().text()).toBe(`selected chart: ${selectedChart}`);
+ });
+ });
});
diff --git a/spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js b/spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js
index 6ef49390c47..3c91b913e67 100644
--- a/spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js
+++ b/spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js
@@ -1,7 +1,9 @@
import { GlColumnChart } from '@gitlab/ui/dist/charts';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import PipelineCharts from '~/projects/pipelines/charts/components/pipeline_charts.vue';
import StatisticsList from '~/projects/pipelines/charts/components/statistics_list.vue';
import getPipelineCountByStatus from '~/projects/pipelines/charts/graphql/queries/get_pipeline_count_by_status.query.graphql';
@@ -10,8 +12,7 @@ import CiCdAnalyticsCharts from '~/vue_shared/components/ci_cd_analytics/ci_cd_a
import { mockPipelineCount, mockPipelineStatistics } from '../mock_data';
const projectPath = 'gitlab-org/gitlab';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
describe('~/projects/pipelines/charts/components/pipeline_charts.vue', () => {
let wrapper;
@@ -25,14 +26,15 @@ describe('~/projects/pipelines/charts/components/pipeline_charts.vue', () => {
return createMockApollo(requestHandlers);
}
- beforeEach(() => {
+ beforeEach(async () => {
wrapper = shallowMount(PipelineCharts, {
provide: {
projectPath,
},
- localVue,
apolloProvider: createMockApolloProvider(),
});
+
+ await waitForPromises();
});
afterEach(() => {
diff --git a/spec/frontend/projects/project_find_file_spec.js b/spec/frontend/projects/project_find_file_spec.js
index 9c1000039b1..eec54dd04bc 100644
--- a/spec/frontend/projects/project_find_file_spec.js
+++ b/spec/frontend/projects/project_find_file_spec.js
@@ -1,6 +1,7 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import { TEST_HOST } from 'helpers/test_constants';
+import waitForPromises from 'helpers/wait_for_promises';
import { sanitize } from '~/lib/dompurify';
import axios from '~/lib/utils/axios_utils';
import ProjectFindFile from '~/projects/project_find_file';
@@ -53,7 +54,7 @@ describe('ProjectFindFile', () => {
{ path: 'folde?rC/fil#F.txt', escaped: 'folde%3FrC/fil%23F.txt' },
];
- beforeEach((done) => {
+ beforeEach(() => {
// Create a mock adapter for stubbing axios API requests
mock = new MockAdapter(axios);
@@ -64,7 +65,7 @@ describe('ProjectFindFile', () => {
);
getProjectFindFileInstance(); // This triggers a load / axios call + subsequent render in the constructor
- setImmediate(done);
+ return waitForPromises();
});
afterEach(() => {
diff --git a/spec/frontend/projects/settings/components/shared_runners_toggle_spec.js b/spec/frontend/projects/settings/components/shared_runners_toggle_spec.js
index 0c5bbe2a115..0a05832ceb6 100644
--- a/spec/frontend/projects/settings/components/shared_runners_toggle_spec.js
+++ b/spec/frontend/projects/settings/components/shared_runners_toggle_spec.js
@@ -1,6 +1,7 @@
import { GlAlert, GlToggle, GlTooltip } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import MockAxiosAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import SharedRunnersToggleComponent from '~/projects/settings/components/shared_runners_toggle.vue';
@@ -121,7 +122,7 @@ describe('projects/settings/components/shared_runners', () => {
expect(isToggleLoading()).toBe(false);
findSharedRunnersToggle().vm.$emit('change', true);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(isToggleLoading()).toBe(true);
await waitForPromises();
diff --git a/spec/frontend/projects/settings/components/transfer_project_form_spec.js b/spec/frontend/projects/settings/components/transfer_project_form_spec.js
index f7ce7c6f840..85b09ced024 100644
--- a/spec/frontend/projects/settings/components/transfer_project_form_spec.js
+++ b/spec/frontend/projects/settings/components/transfer_project_form_spec.js
@@ -1,4 +1,7 @@
-import { namespaces } from 'jest/vue_shared/components/namespace_select/mock_data';
+import {
+ groupNamespaces,
+ userNamespaces,
+} from 'jest/vue_shared/components/namespace_select/mock_data';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import TransferProjectForm from '~/projects/settings/components/transfer_project_form.vue';
import NamespaceSelect from '~/vue_shared/components/namespace_select/namespace_select.vue';
@@ -13,7 +16,8 @@ describe('Transfer project form', () => {
const createComponent = () =>
shallowMountExtended(TransferProjectForm, {
propsData: {
- namespaces,
+ userNamespaces,
+ groupNamespaces,
confirmButtonText,
confirmationPhrase,
},
@@ -43,7 +47,7 @@ describe('Transfer project form', () => {
});
describe('with a selected namespace', () => {
- const [selectedItem] = namespaces.group;
+ const [selectedItem] = groupNamespaces;
beforeEach(() => {
findNamespaceSelect().vm.$emit('select', selectedItem);
diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js
index 875c58583df..57e515723e5 100644
--- a/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js
+++ b/spec/frontend/projects/settings_service_desk/components/service_desk_setting_spec.js
@@ -139,7 +139,7 @@ describe('ServiceDeskSetting', () => {
input.setValue('abc_A.');
input.trigger('blur');
- await wrapper.vm.$nextTick();
+ await nextTick();
const errorText = wrapper.find('.invalid-feedback');
expect(errorText.exists()).toBe(true);
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/prometheus_metrics/prometheus_metrics_spec.js b/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js
index a703dc0a66f..ee74e28ba23 100644
--- a/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js
+++ b/spec/frontend/prometheus_metrics/prometheus_metrics_spec.js
@@ -1,4 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
+import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import PANEL_STATE from '~/prometheus_metrics/constants';
import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics';
@@ -132,7 +133,7 @@ describe('PrometheusMetrics', () => {
mock.restore();
});
- it('should show loader animation while response is being loaded and hide it when request is complete', (done) => {
+ it('should show loader animation while response is being loaded and hide it when request is complete', async () => {
mockSuccess();
prometheusMetrics.loadActiveMetrics();
@@ -140,34 +141,31 @@ describe('PrometheusMetrics', () => {
expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeFalsy();
expect(axios.get).toHaveBeenCalledWith(prometheusMetrics.activeMetricsEndpoint);
- setImmediate(() => {
- expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
- done();
- });
+ await waitForPromises();
+
+ expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
});
- it('should show empty state if response failed to load', (done) => {
+ it('should show empty state if response failed to load', async () => {
mockError();
prometheusMetrics.loadActiveMetrics();
- setImmediate(() => {
- expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
- expect(prometheusMetrics.$monitoredMetricsEmpty.hasClass('hidden')).toBeFalsy();
- done();
- });
+ await waitForPromises();
+
+ expect(prometheusMetrics.$monitoredMetricsLoading.hasClass('hidden')).toBeTruthy();
+ expect(prometheusMetrics.$monitoredMetricsEmpty.hasClass('hidden')).toBeFalsy();
});
- it('should populate metrics list once response is loaded', (done) => {
+ it('should populate metrics list once response is loaded', async () => {
jest.spyOn(prometheusMetrics, 'populateActiveMetrics').mockImplementation();
mockSuccess();
prometheusMetrics.loadActiveMetrics();
- setImmediate(() => {
- expect(prometheusMetrics.populateActiveMetrics).toHaveBeenCalledWith(metrics);
- done();
- });
+ await waitForPromises();
+
+ expect(prometheusMetrics.populateActiveMetrics).toHaveBeenCalledWith(metrics);
});
});
});
diff --git a/spec/frontend/ref/components/ref_selector_spec.js b/spec/frontend/ref/components/ref_selector_spec.js
index b486992ac4b..e1fc60f0d92 100644
--- a/spec/frontend/ref/components/ref_selector_spec.js
+++ b/spec/frontend/ref/components/ref_selector_spec.js
@@ -1,5 +1,6 @@
import { GlLoadingIcon, GlSearchBoxByType, GlDropdownItem, GlDropdown, GlIcon } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { merge, last } from 'lodash';
@@ -20,8 +21,7 @@ import {
} from '~/ref/constants';
import createStore from '~/ref/stores/';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Ref selector component', () => {
const fixtures = { branches, tags, commit };
@@ -52,7 +52,6 @@ describe('Ref selector component', () => {
stubs: {
GlSearchBoxByType: true,
},
- localVue,
store: createStore(),
},
mountOverrides,
@@ -138,19 +137,19 @@ describe('Ref selector component', () => {
findSearchBox().vm.$emit('input', newQuery);
};
- const selectFirstBranch = () => {
+ const selectFirstBranch = async () => {
findFirstBranchDropdownItem().vm.$emit('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
};
- const selectFirstTag = () => {
+ const selectFirstTag = async () => {
findFirstTagDropdownItem().vm.$emit('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
};
- const selectFirstCommit = () => {
+ const selectFirstCommit = async () => {
findFirstCommitDropdownItem().vm.$emit('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
};
const waitForRequests = ({ andClearMocks } = { andClearMocks: false }) =>
@@ -220,12 +219,11 @@ describe('Ref selector component', () => {
return waitForRequests();
});
- it('renders the updated ref name', () => {
+ it('renders the updated ref name', async () => {
wrapper.setProps({ value: updatedRef });
- return localVue.nextTick().then(() => {
- expect(findButtonContent().text()).toBe(updatedRef);
- });
+ await nextTick();
+ expect(findButtonContent().text()).toBe(updatedRef);
});
});
@@ -547,9 +545,8 @@ describe('Ref selector component', () => {
await selectFirstBranch();
- return localVue.nextTick().then(() => {
- expect(findButtonContent().text()).toBe(fixtures.branches[0].name);
- });
+ await nextTick();
+ expect(findButtonContent().text()).toBe(fixtures.branches[0].name);
});
it("updates the v-model binding with the branch's name", async () => {
@@ -567,9 +564,8 @@ describe('Ref selector component', () => {
await selectFirstTag();
- return localVue.nextTick().then(() => {
- expect(findButtonContent().text()).toBe(fixtures.tags[0].name);
- });
+ await nextTick();
+ expect(findButtonContent().text()).toBe(fixtures.tags[0].name);
});
it("updates the v-model binding with the tag's name", async () => {
@@ -587,9 +583,8 @@ describe('Ref selector component', () => {
await selectFirstCommit();
- return localVue.nextTick().then(() => {
- expect(findButtonContent().text()).toBe(fixtures.commit.id);
- });
+ await nextTick();
+ expect(findButtonContent().text()).toBe(fixtures.commit.id);
});
it("updates the v-model binding with the commit's full SHA", async () => {
diff --git a/spec/frontend/related_issues/components/related_issuable_input_spec.js b/spec/frontend/related_issues/components/related_issuable_input_spec.js
index 79b228454f4..7d11e3cffb0 100644
--- a/spec/frontend/related_issues/components/related_issuable_input_spec.js
+++ b/spec/frontend/related_issues/components/related_issuable_input_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import RelatedIssuableInput from '~/related_issues/components/related_issuable_input.vue';
import { issuableTypesMap, PathIdSeparator } from '~/related_issues/constants';
@@ -82,7 +83,7 @@ describe('RelatedIssuableInput', () => {
wrapper.find('li').trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(document.activeElement).toBe(wrapper.find({ ref: 'input' }).element);
});
diff --git a/spec/frontend/releases/components/app_edit_new_spec.js b/spec/frontend/releases/components/app_edit_new_spec.js
index 029d720f7b9..0a0a683b56d 100644
--- a/spec/frontend/releases/components/app_edit_new_spec.js
+++ b/spec/frontend/releases/components/app_edit_new_spec.js
@@ -3,6 +3,7 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { merge } from 'lodash';
import Vuex from 'vuex';
+import { nextTick } from 'vue';
import originalRelease from 'test_fixtures/api/releases/release.json';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
@@ -71,7 +72,7 @@ describe('Release edit/new component', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
wrapper.element.querySelectorAll('input').forEach((input) => jest.spyOn(input, 'focus'));
};
diff --git a/spec/frontend/releases/components/app_index_apollo_client_spec.js b/spec/frontend/releases/components/app_index_apollo_client_spec.js
index 32bbfd386f5..9881ef9bc9f 100644
--- a/spec/frontend/releases/components/app_index_apollo_client_spec.js
+++ b/spec/frontend/releases/components/app_index_apollo_client_spec.js
@@ -1,9 +1,10 @@
import { cloneDeep } from 'lodash';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import originalAllReleasesQueryResponse from 'test_fixtures/graphql/releases/graphql/queries/all_releases.query.graphql.json';
import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import allReleasesQuery from 'shared_queries/releases/all_releases.query.graphql';
import createFlash from '~/flash';
import { historyPushState } from '~/lib/utils/common_utils';
@@ -141,7 +142,8 @@ describe('app_index_apollo_client.vue', () => {
});
});
- it(`${toDescription(loadingIndicator)} render a loading indicator`, () => {
+ it(`${toDescription(loadingIndicator)} render a loading indicator`, async () => {
+ await waitForPromises();
expect(findLoadingIndicator().exists()).toBe(loadingIndicator);
});
@@ -294,7 +296,7 @@ describe('app_index_apollo_client.vue', () => {
mockQueryParams = { after };
findPagination().vm.$emit('next', after);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(queryMock.mock.calls).toEqual([
[expect.objectContaining({ before })],
@@ -319,7 +321,7 @@ describe('app_index_apollo_client.vue', () => {
it('requeries the GraphQL endpoint and updates the URL when the sort is changed', async () => {
findSort().vm.$emit('input', CREATED_ASC);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(queryMock.mock.calls).toEqual([
[expect.objectContaining({ sort: DEFAULT_SORT })],
@@ -335,7 +337,7 @@ describe('app_index_apollo_client.vue', () => {
it('does not requery the GraphQL endpoint or update the URL if the sort is updated to the same value', async () => {
findSort().vm.$emit('input', DEFAULT_SORT);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(queryMock.mock.calls).toEqual([
[expect.objectContaining({ sort: DEFAULT_SORT })],
@@ -368,7 +370,7 @@ describe('app_index_apollo_client.vue', () => {
findSort().vm.$emit('input', CREATED_ASC);
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it(`resets the page's "${paramName}" pagination cursor when the sort is changed`, () => {
diff --git a/spec/frontend/releases/components/app_show_spec.js b/spec/frontend/releases/components/app_show_spec.js
index a60b9bda66a..41c9746a363 100644
--- a/spec/frontend/releases/components/app_show_spec.js
+++ b/spec/frontend/releases/components/app_show_spec.js
@@ -3,6 +3,7 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import oneReleaseQueryResponse from 'test_fixtures/graphql/releases/graphql/queries/one_release.query.graphql.json';
import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import ReleaseShowApp from '~/releases/components/app_show.vue';
import ReleaseBlock from '~/releases/components/release_block.vue';
@@ -111,12 +112,13 @@ describe('Release show component', () => {
});
describe('when the component has successfully loaded the release', () => {
- beforeEach(() => {
+ beforeEach(async () => {
const apolloProvider = createMockApollo([
[oneReleaseQuery, jest.fn().mockResolvedValueOnce(oneReleaseQueryResponse)],
]);
createComponent({ apolloProvider });
+ await waitForPromises();
});
expectNoLoadingIndicator();
@@ -125,12 +127,13 @@ describe('Release show component', () => {
});
describe('when the request succeeded, but the returned "project" key was null', () => {
- beforeEach(() => {
+ beforeEach(async () => {
const apolloProvider = createMockApollo([
[oneReleaseQuery, jest.fn().mockResolvedValueOnce({ data: { project: null } })],
]);
createComponent({ apolloProvider });
+ await waitForPromises();
});
expectNoLoadingIndicator();
@@ -139,7 +142,7 @@ describe('Release show component', () => {
});
describe('when the request succeeded, but the returned "project.release" key was null', () => {
- beforeEach(() => {
+ beforeEach(async () => {
const apolloProvider = createMockApollo([
[
oneReleaseQuery,
@@ -148,6 +151,7 @@ describe('Release show component', () => {
]);
createComponent({ apolloProvider });
+ await waitForPromises();
});
expectNoLoadingIndicator();
@@ -156,12 +160,13 @@ describe('Release show component', () => {
});
describe('when an error occurs while loading the release', () => {
- beforeEach(() => {
+ beforeEach(async () => {
const apolloProvider = createMockApollo([
[oneReleaseQuery, jest.fn().mockRejectedValueOnce('An error occurred!')],
]);
createComponent({ apolloProvider });
+ await waitForPromises();
});
expectNoLoadingIndicator();
diff --git a/spec/frontend/releases/components/asset_links_form_spec.js b/spec/frontend/releases/components/asset_links_form_spec.js
index 839d127e00f..c0f7738bec5 100644
--- a/spec/frontend/releases/components/asset_links_form_spec.js
+++ b/spec/frontend/releases/components/asset_links_form_spec.js
@@ -1,4 +1,5 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import originalRelease from 'test_fixtures/api/releases/release.json';
import * as commonUtils from '~/lib/utils/common_utils';
@@ -6,8 +7,7 @@ import { ENTER_KEY } from '~/lib/utils/keys';
import AssetLinksForm from '~/releases/components/asset_links_form.vue';
import { ASSET_LINK_TYPE, DEFAULT_ASSET_LINK_TYPE } from '~/releases/constants';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Release edit component', () => {
let wrapper;
@@ -52,7 +52,6 @@ describe('Release edit component', () => {
});
wrapper = mount(AssetLinksForm, {
- localVue,
store,
});
};
diff --git a/spec/frontend/releases/components/evidence_block_spec.js b/spec/frontend/releases/components/evidence_block_spec.js
index 973428257b7..f0d02884305 100644
--- a/spec/frontend/releases/components/evidence_block_spec.js
+++ b/spec/frontend/releases/components/evidence_block_spec.js
@@ -1,5 +1,6 @@
import { GlLink, GlIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import originalRelease from 'test_fixtures/api/releases/release.json';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { truncateSha } from '~/lib/utils/text_utility';
@@ -51,12 +52,11 @@ describe('Evidence Block', () => {
expect(wrapper.find('.js-short').text()).toBe(truncateSha(release.evidences[0].sha));
});
- it('renders the long sha after expansion', () => {
+ it('renders the long sha after expansion', async () => {
wrapper.find('.js-text-expander-prepend').trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find('.js-expanded').text()).toBe(release.evidences[0].sha);
- });
+ await nextTick();
+ expect(wrapper.find('.js-expanded').text()).toBe(release.evidences[0].sha);
});
});
diff --git a/spec/frontend/releases/components/release_block_footer_spec.js b/spec/frontend/releases/components/release_block_footer_spec.js
index f645dc309d7..b095e9e1d78 100644
--- a/spec/frontend/releases/components/release_block_footer_spec.js
+++ b/spec/frontend/releases/components/release_block_footer_spec.js
@@ -1,6 +1,7 @@
import { GlLink, GlIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { cloneDeep } from 'lodash';
+import { nextTick } from 'vue';
import originalRelease from 'test_fixtures/api/releases/release.json';
import { trimText } from 'helpers/text_helper';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
@@ -14,7 +15,7 @@ describe('Release block footer', () => {
let wrapper;
let release;
- const factory = (props = {}) => {
+ const factory = async (props = {}) => {
wrapper = mount(ReleaseBlockFooter, {
propsData: {
...convertObjectPropsToCamelCase(release, { deep: true }),
@@ -22,7 +23,7 @@ describe('Release block footer', () => {
},
});
- return wrapper.vm.$nextTick();
+ await nextTick();
};
beforeEach(() => {
diff --git a/spec/frontend/releases/components/release_block_milestone_info_spec.js b/spec/frontend/releases/components/release_block_milestone_info_spec.js
index 146b2cc7490..84a0080965b 100644
--- a/spec/frontend/releases/components/release_block_milestone_info_spec.js
+++ b/spec/frontend/releases/components/release_block_milestone_info_spec.js
@@ -1,5 +1,6 @@
import { GlProgressBar, GlLink, GlBadge, GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import originalRelease from 'test_fixtures/api/releases/release.json';
import { trimText } from 'helpers/text_helper';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
@@ -12,12 +13,12 @@ describe('Release block milestone info', () => {
let wrapper;
let milestones;
- const factory = (props) => {
+ const factory = async (props) => {
wrapper = mount(ReleaseBlockMilestoneInfo, {
propsData: props,
});
- return wrapper.vm.$nextTick();
+ await nextTick();
};
beforeEach(() => {
@@ -105,10 +106,10 @@ describe('Release block milestone info', () => {
return factory({ milestones: lotsOfMilestones });
});
- const clickShowMoreFewerButton = () => {
+ const clickShowMoreFewerButton = async () => {
milestoneListContainer().find(GlButton).trigger('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
};
const milestoneListText = () => trimText(milestoneListContainer().text());
@@ -117,19 +118,16 @@ describe('Release block milestone info', () => {
expect(milestoneListText()).toContain(`Milestones ${abbreviatedListString} • show 10 more`);
});
- it('renders all milestones when "show more" is clicked', () =>
- clickShowMoreFewerButton().then(() => {
- expect(milestoneListText()).toContain(`Milestones ${fullListString} • show fewer`);
- }));
+ it('renders all milestones when "show more" is clicked', async () => {
+ await clickShowMoreFewerButton();
+ expect(milestoneListText()).toContain(`Milestones ${fullListString} • show fewer`);
+ });
- it('returns to the original view when "show fewer" is clicked', () =>
- clickShowMoreFewerButton()
- .then(clickShowMoreFewerButton)
- .then(() => {
- expect(milestoneListText()).toContain(
- `Milestones ${abbreviatedListString} • show 10 more`,
- );
- }));
+ it('returns to the original view when "show fewer" is clicked', async () => {
+ await clickShowMoreFewerButton();
+ await clickShowMoreFewerButton();
+ expect(milestoneListText()).toContain(`Milestones ${abbreviatedListString} • show 10 more`);
+ });
});
const expectAllZeros = () => {
diff --git a/spec/frontend/releases/components/release_block_spec.js b/spec/frontend/releases/components/release_block_spec.js
index a847c32b8f1..c4910ae9b2f 100644
--- a/spec/frontend/releases/components/release_block_spec.js
+++ b/spec/frontend/releases/components/release_block_spec.js
@@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils';
import $ from 'jquery';
+import { nextTick } from 'vue';
import originalRelease from 'test_fixtures/api/releases/release.json';
import * as commonUtils from '~/lib/utils/common_utils';
import * as urlUtility from '~/lib/utils/url_utility';
@@ -13,7 +14,7 @@ describe('Release block', () => {
let wrapper;
let release;
- const factory = (releaseProp, featureFlags = {}) => {
+ const factory = async (releaseProp, featureFlags = {}) => {
wrapper = mount(ReleaseBlock, {
propsData: {
release: releaseProp,
@@ -25,7 +26,7 @@ describe('Release block', () => {
},
});
- return wrapper.vm.$nextTick();
+ await nextTick();
};
const milestoneListLabel = () => wrapper.find('.js-milestone-list-label');
diff --git a/spec/frontend/releases/components/releases_pagination_spec.js b/spec/frontend/releases/components/releases_pagination_spec.js
index 2d08f72ad8b..b8c69b0ea70 100644
--- a/spec/frontend/releases/components/releases_pagination_spec.js
+++ b/spec/frontend/releases/components/releases_pagination_spec.js
@@ -1,5 +1,6 @@
import { GlKeysetPagination } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import { historyPushState } from '~/lib/utils/common_utils';
import ReleasesPagination from '~/releases/components/releases_pagination.vue';
@@ -11,8 +12,7 @@ jest.mock('~/lib/utils/common_utils', () => ({
historyPushState: jest.fn(),
}));
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('~/releases/components/releases_pagination.vue', () => {
let wrapper;
@@ -39,7 +39,6 @@ describe('~/releases/components/releases_pagination.vue', () => {
},
featureFlags: {},
}),
- localVue,
});
};
diff --git a/spec/frontend/releases/components/releases_sort_spec.js b/spec/frontend/releases/components/releases_sort_spec.js
index b16f80b9c73..7774532bc12 100644
--- a/spec/frontend/releases/components/releases_sort_spec.js
+++ b/spec/frontend/releases/components/releases_sort_spec.js
@@ -1,12 +1,12 @@
import { GlSorting, GlSortingItem } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import ReleasesSort from '~/releases/components/releases_sort.vue';
import createStore from '~/releases/stores';
import createIndexModule from '~/releases/stores/modules/index';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('~/releases/components/releases_sort.vue', () => {
let wrapper;
@@ -30,7 +30,6 @@ describe('~/releases/components/releases_sort.vue', () => {
stubs: {
GlSortingItem,
},
- localVue,
});
};
diff --git a/spec/frontend/releases/components/tag_field_exsting_spec.js b/spec/frontend/releases/components/tag_field_exsting_spec.js
index 294538086b4..f45a28392b7 100644
--- a/spec/frontend/releases/components/tag_field_exsting_spec.js
+++ b/spec/frontend/releases/components/tag_field_exsting_spec.js
@@ -1,5 +1,6 @@
import { GlFormInput } from '@gitlab/ui';
-import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
+import { shallowMount, mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import TagFieldExisting from '~/releases/components/tag_field_existing.vue';
import createStore from '~/releases/stores';
@@ -7,8 +8,7 @@ import createEditNewModule from '~/releases/stores/modules/edit_new';
const TEST_TAG_NAME = 'test-tag-name';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('releases/components/tag_field_existing', () => {
let store;
@@ -17,7 +17,6 @@ describe('releases/components/tag_field_existing', () => {
const createComponent = (mountFn = shallowMount) => {
wrapper = mountFn(TagFieldExisting, {
store,
- localVue,
});
};
diff --git a/spec/frontend/releases/components/tag_field_new_spec.js b/spec/frontend/releases/components/tag_field_new_spec.js
index 0f416e46dba..c13b513f87e 100644
--- a/spec/frontend/releases/components/tag_field_new_spec.js
+++ b/spec/frontend/releases/components/tag_field_new_spec.js
@@ -1,6 +1,6 @@
import { GlDropdownItem } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import { __ } from '~/locale';
import TagFieldNew from '~/releases/components/tag_field_new.vue';
import createStore from '~/releases/stores';
@@ -153,7 +153,7 @@ describe('releases/components/tag_field_new', () => {
* Should be passed either 'shown' or 'hidden'
*/
const expectValidationMessageToBe = async (state) => {
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findTagNameFormGroup().element).toHaveClass(
state === 'shown' ? 'is-invalid' : 'is-valid',
diff --git a/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js b/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js
index 794deca42ac..ddabb7194cb 100644
--- a/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js
+++ b/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js
@@ -1,3 +1,4 @@
+import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AccessibilityIssueBody from '~/reports/accessibility_report/components/accessibility_issue_body.vue';
@@ -29,7 +30,7 @@ describe('CustomMetricsForm', () => {
});
};
- const findIsNewBadge = () => wrapper.find({ ref: 'accessibility-issue-is-new-badge' });
+ const findIsNewBadge = () => wrapper.findComponent(GlBadge);
beforeEach(() => {
mountComponent(issue);
@@ -37,7 +38,6 @@ describe('CustomMetricsForm', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
it('Displays the issue message', () => {
@@ -52,7 +52,7 @@ describe('CustomMetricsForm', () => {
.find({ ref: 'accessibility-issue-learn-more' })
.attributes('href');
- expect(learnMoreUrl).toEqual(issue.learnMoreUrl);
+ expect(learnMoreUrl).toBe(issue.learnMoreUrl);
});
});
@@ -69,7 +69,7 @@ describe('CustomMetricsForm', () => {
.find({ ref: 'accessibility-issue-learn-more' })
.attributes('href');
- expect(learnMoreUrl).toEqual('https://www.w3.org/TR/WCAG20-TECHS/Overview.html');
+ expect(learnMoreUrl).toBe('https://www.w3.org/TR/WCAG20-TECHS/Overview.html');
});
});
@@ -86,7 +86,7 @@ describe('CustomMetricsForm', () => {
.find({ ref: 'accessibility-issue-learn-more' })
.attributes('href');
- expect(learnMoreUrl).toEqual('https://www.w3.org/TR/WCAG20-TECHS/Overview.html');
+ expect(learnMoreUrl).toBe('https://www.w3.org/TR/WCAG20-TECHS/Overview.html');
});
});
@@ -96,7 +96,7 @@ describe('CustomMetricsForm', () => {
});
it('Renders the new badge', () => {
- expect(findIsNewBadge().exists()).toEqual(true);
+ expect(findIsNewBadge().exists()).toBe(true);
});
});
@@ -106,7 +106,7 @@ describe('CustomMetricsForm', () => {
});
it('Does not render the new badge', () => {
- expect(findIsNewBadge().exists()).toEqual(false);
+ expect(findIsNewBadge().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js b/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js
index b716d54c9fc..34b1cdd92bc 100644
--- a/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js
+++ b/spec/frontend/reports/accessibility_report/grouped_accessibility_reports_app_spec.js
@@ -1,22 +1,20 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import AccessibilityIssueBody from '~/reports/accessibility_report/components/accessibility_issue_body.vue';
import GroupedAccessibilityReportsApp from '~/reports/accessibility_report/grouped_accessibility_reports_app.vue';
import { getStoreConfig } from '~/reports/accessibility_report/store';
import { mockReport } from './mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Grouped accessibility reports app', () => {
- const Component = localVue.extend(GroupedAccessibilityReportsApp);
let wrapper;
let mockStore;
const mountComponent = () => {
- wrapper = mount(Component, {
+ wrapper = mount(GroupedAccessibilityReportsApp, {
store: mockStore,
- localVue,
propsData: {
endpoint: 'endpoint.json',
},
diff --git a/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js b/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
index 685a1c50a46..1f923f41274 100644
--- a/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
+++ b/spec/frontend/reports/codequality_report/grouped_codequality_reports_app_spec.js
@@ -1,4 +1,5 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import CodequalityIssueBody from '~/reports/codequality_report/components/codequality_issue_body.vue';
import GroupedCodequalityReportsApp from '~/reports/codequality_report/grouped_codequality_reports_app.vue';
@@ -6,8 +7,7 @@ import { getStoreConfig } from '~/reports/codequality_report/store';
import { STATUS_NOT_FOUND } from '~/reports/constants';
import { parsedReportIssues } from './mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Grouped code quality reports app', () => {
let wrapper;
@@ -22,7 +22,6 @@ describe('Grouped code quality reports app', () => {
const mountComponent = (props = {}) => {
wrapper = mount(GroupedCodequalityReportsApp, {
store: mockStore,
- localVue,
propsData: {
...PATHS,
...props,
diff --git a/spec/frontend/reports/components/report_section_spec.js b/spec/frontend/reports/components/report_section_spec.js
index 39932b62dbb..f9eb6dd05f3 100644
--- a/spec/frontend/reports/components/report_section_spec.js
+++ b/spec/frontend/reports/components/report_section_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import mountComponent, { mountComponentWithSlots } from 'helpers/vue_mount_component_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import reportSection from '~/reports/components/report_section.vue';
@@ -71,16 +71,12 @@ describe('Report section', () => {
const issues = hasIssues ? 'has issues' : 'has no issues';
const open = alwaysOpen ? 'is always open' : 'is not always open';
- it(`is ${isCollapsible}, if the report ${issues} and ${open}`, (done) => {
+ it(`is ${isCollapsible}, if the report ${issues} and ${open}`, async () => {
vm.hasIssues = hasIssues;
vm.alwaysOpen = alwaysOpen;
- Vue.nextTick()
- .then(() => {
- expect(vm.isCollapsible).toBe(isCollapsible);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.isCollapsible).toBe(isCollapsible);
});
});
});
@@ -97,16 +93,12 @@ describe('Report section', () => {
const issues = isCollapsed ? 'is collapsed' : 'is not collapsed';
const open = alwaysOpen ? 'is always open' : 'is not always open';
- it(`is ${isExpanded}, if the report ${issues} and ${open}`, (done) => {
+ it(`is ${isExpanded}, if the report ${issues} and ${open}`, async () => {
vm.isCollapsed = isCollapsed;
vm.alwaysOpen = alwaysOpen;
- Vue.nextTick()
- .then(() => {
- expect(vm.isExpanded).toBe(isExpanded);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.isExpanded).toBe(isExpanded);
});
});
});
@@ -148,79 +140,55 @@ describe('Report section', () => {
describe('toggleCollapsed', () => {
const hiddenCss = { display: 'none' };
- it('toggles issues', (done) => {
+ it('toggles issues', async () => {
vm.$el.querySelector('button').click();
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss);
- expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Collapse');
-
- vm.$el.querySelector('button').click();
- })
- .then(Vue.nextTick)
- .then(() => {
- expect(vm.$el.querySelector('.js-report-section-container')).toHaveCss(hiddenCss);
- expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Expand');
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss);
+ expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Collapse');
+
+ vm.$el.querySelector('button').click();
+
+ await nextTick();
+ expect(vm.$el.querySelector('.js-report-section-container')).toHaveCss(hiddenCss);
+ expect(vm.$el.querySelector('button').textContent.trim()).toEqual('Expand');
});
- it('is always expanded, if always-open is set to true', (done) => {
+ it('is always expanded, if always-open is set to true', async () => {
vm.alwaysOpen = true;
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss);
- expect(vm.$el.querySelector('button')).toBeNull();
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.querySelector('.js-report-section-container')).not.toHaveCss(hiddenCss);
+ expect(vm.$el.querySelector('button')).toBeNull();
});
});
});
describe('snowplow events', () => {
- it('does emit an event on issue toggle if the shouldEmitToggleEvent prop does exist', (done) => {
+ it('does emit an event on issue toggle if the shouldEmitToggleEvent prop does exist', async () => {
createComponent({ hasIssues: true, shouldEmitToggleEvent: true });
expect(wrapper.emitted().toggleEvent).toBeUndefined();
findCollapseButton().trigger('click');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- expect(wrapper.emitted().toggleEvent).toHaveLength(1);
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(wrapper.emitted().toggleEvent).toHaveLength(1);
});
- it('does not emit an event on issue toggle if the shouldEmitToggleEvent prop does not exist', (done) => {
+ it('does not emit an event on issue toggle if the shouldEmitToggleEvent prop does not exist', async () => {
createComponent({ hasIssues: true });
expect(wrapper.emitted().toggleEvent).toBeUndefined();
findCollapseButton().trigger('click');
- return wrapper.vm
- .$nextTick()
- .then(() => {
- expect(wrapper.emitted().toggleEvent).toBeUndefined();
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(wrapper.emitted().toggleEvent).toBeUndefined();
});
- it('does not emit an event if always-open is set to true', (done) => {
+ it('does not emit an event if always-open is set to true', async () => {
createComponent({ alwaysOpen: true, hasIssues: true, shouldEmitToggleEvent: true });
- wrapper.vm
- .$nextTick()
- .then(() => {
- expect(wrapper.emitted().toggleEvent).toBeUndefined();
- })
- .then(done)
- .catch(done.fail);
+ await nextTick();
+ expect(wrapper.emitted().toggleEvent).toBeUndefined();
});
});
diff --git a/spec/frontend/reports/grouped_test_report/components/test_issue_body_spec.js b/spec/frontend/reports/grouped_test_report/components/test_issue_body_spec.js
index 2f6f62ca1d3..8a854a92ad7 100644
--- a/spec/frontend/reports/grouped_test_report/components/test_issue_body_spec.js
+++ b/spec/frontend/reports/grouped_test_report/components/test_issue_body_spec.js
@@ -1,13 +1,13 @@
import { GlBadge, GlButton } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import IssueStatusIcon from '~/reports/components/issue_status_icon.vue';
import TestIssueBody from '~/reports/grouped_test_report/components/test_issue_body.vue';
import { failedIssue, successIssue } from '../../mock_data/mock_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Test issue body', () => {
let wrapper;
@@ -29,7 +29,6 @@ describe('Test issue body', () => {
wrapper = extendedWrapper(
shallowMount(TestIssueBody, {
store,
- localVue,
propsData: {
issue,
},
diff --git a/spec/frontend/reports/grouped_test_report/grouped_test_reports_app_spec.js b/spec/frontend/reports/grouped_test_report/grouped_test_reports_app_spec.js
index c60c1f7b63c..90edb27d1d6 100644
--- a/spec/frontend/reports/grouped_test_report/grouped_test_reports_app_spec.js
+++ b/spec/frontend/reports/grouped_test_report/grouped_test_reports_app_spec.js
@@ -1,4 +1,5 @@
-import { mount, createLocalVue } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import Api from '~/api';
import GroupedTestReportsApp from '~/reports/grouped_test_report/grouped_test_reports_app.vue';
@@ -14,8 +15,7 @@ import resolvedFailures from '../mock_data/resolved_failures.json';
jest.mock('~/api.js');
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('Grouped test reports app', () => {
const endpoint = 'endpoint.json';
@@ -27,7 +27,6 @@ describe('Grouped test reports app', () => {
const mountComponent = ({ props = { pipelinePath } } = {}) => {
wrapper = mount(GroupedTestReportsApp, {
store: mockStore,
- localVue,
propsData: {
endpoint,
headBlobPath,
diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js
index d3b60ec3768..109e5cef49b 100644
--- a/spec/frontend/repository/components/blob_content_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_content_viewer_spec.js
@@ -1,8 +1,8 @@
import { GlLoadingIcon } from '@gitlab/ui';
-import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
+import { mount, shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
-import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -12,10 +12,10 @@ import BlobButtonGroup from '~/repository/components/blob_button_group.vue';
import BlobContentViewer from '~/repository/components/blob_content_viewer.vue';
import BlobEdit from '~/repository/components/blob_edit.vue';
import ForkSuggestion from '~/repository/components/fork_suggestion.vue';
-import { loadViewer, viewerProps } from '~/repository/components/blob_viewers';
+import { loadViewer } from '~/repository/components/blob_viewers';
import DownloadViewer from '~/repository/components/blob_viewers/download_viewer.vue';
import EmptyViewer from '~/repository/components/blob_viewers/empty_viewer.vue';
-import SourceViewer from '~/vue_shared/components/source_viewer.vue';
+import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vue';
import blobInfoQuery from '~/repository/queries/blob_info.query.graphql';
import { redirectTo } from '~/lib/utils/url_utility';
import { isLoggedIn } from '~/lib/utils/common_utils';
@@ -36,11 +36,10 @@ jest.mock('~/lib/utils/common_utils');
let wrapper;
let mockResolver;
-const localVue = createLocalVue();
const mockAxios = new MockAdapter(axios);
const createComponent = async (mockData = {}, mountFn = shallowMount) => {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const {
blob = simpleViewerMock,
@@ -51,6 +50,7 @@ const createComponent = async (mockData = {}, mountFn = shallowMount) => {
createMergeRequestIn = userPermissionsMock.createMergeRequestIn,
isBinary,
inject = {},
+ highlightJs = true,
} = mockData;
const project = {
@@ -75,11 +75,17 @@ const createComponent = async (mockData = {}, mountFn = shallowMount) => {
wrapper = extendedWrapper(
mountFn(BlobContentViewer, {
- localVue,
apolloProvider: fakeApollo,
propsData: propsMock,
mixins: [{ data: () => ({ ref: refMock }) }],
- provide: { ...inject },
+ provide: {
+ targetBranch: 'test',
+ originalBranch: 'default-ref',
+ ...inject,
+ glFeatures: {
+ highlightJs,
+ },
+ },
}),
);
@@ -100,7 +106,6 @@ describe('Blob content viewer component', () => {
const findForkSuggestion = () => wrapper.findComponent(ForkSuggestion);
beforeEach(() => {
- gon.features = { highlightJs: true };
isLoggedIn.mockReturnValue(true);
});
@@ -138,6 +143,15 @@ describe('Blob content viewer component', () => {
});
describe('legacy viewers', () => {
+ it('loads a legacy viewer when a the fileType is text and the highlightJs feature is turned off', async () => {
+ await createComponent({
+ blob: { ...simpleViewerMock, fileType: 'text', highlightJs: false },
+ });
+
+ expect(mockAxios.history.get).toHaveLength(1);
+ expect(mockAxios.history.get[0].url).toEqual('some_file.js?format=json&viewer=simple');
+ });
+
it('loads a legacy viewer when a viewer component is not available', async () => {
await createComponent({ blob: { ...simpleViewerMock, fileType: 'unknown' } });
@@ -203,44 +217,39 @@ describe('Blob content viewer component', () => {
describe('Blob viewer', () => {
afterEach(() => {
loadViewer.mockRestore();
- viewerProps.mockRestore();
});
it('does not render a BlobContent component if a Blob viewer is available', async () => {
loadViewer.mockReturnValue(() => true);
await createComponent({ blob: richViewerMock });
-
+ await waitForPromises();
expect(findBlobContent().exists()).toBe(false);
});
it.each`
- viewer | loadViewerReturnValue | viewerPropsReturnValue
- ${'empty'} | ${EmptyViewer} | ${{}}
- ${'download'} | ${DownloadViewer} | ${{ filePath: '/some/file/path', fileName: 'test.js', fileSize: 100 }}
- ${'text'} | ${SourceViewer} | ${{ content: 'test', autoDetect: true }}
- `(
- 'renders viewer component for $viewer files',
- async ({ viewer, loadViewerReturnValue, viewerPropsReturnValue }) => {
- loadViewer.mockReturnValue(loadViewerReturnValue);
- viewerProps.mockReturnValue(viewerPropsReturnValue);
-
- createComponent({
- blob: {
- ...simpleViewerMock,
- fileType: 'null',
- simpleViewer: {
- ...simpleViewerMock.simpleViewer,
- fileType: viewer,
- },
+ viewer | loadViewerReturnValue
+ ${'empty'} | ${EmptyViewer}
+ ${'download'} | ${DownloadViewer}
+ ${'text'} | ${SourceViewer}
+ `('renders viewer component for $viewer files', async ({ viewer, loadViewerReturnValue }) => {
+ loadViewer.mockReturnValue(loadViewerReturnValue);
+
+ createComponent({
+ blob: {
+ ...simpleViewerMock,
+ fileType: 'null',
+ simpleViewer: {
+ ...simpleViewerMock.simpleViewer,
+ fileType: viewer,
},
- });
+ },
+ });
- await nextTick();
+ await waitForPromises();
- expect(loadViewer).toHaveBeenCalledWith(viewer);
- expect(wrapper.findComponent(loadViewerReturnValue).exists()).toBe(true);
- },
- );
+ expect(loadViewer).toHaveBeenCalledWith(viewer, false);
+ expect(wrapper.findComponent(loadViewerReturnValue).exists()).toBe(true);
+ });
});
describe('BlobHeader action slot', () => {
@@ -354,6 +363,19 @@ describe('Blob content viewer component', () => {
});
describe('blob info query', () => {
+ it.each`
+ highlightJs | shouldFetchRawText
+ ${true} | ${true}
+ ${false} | ${false}
+ `(
+ 'calls blob info query with shouldFetchRawText: $shouldFetchRawText when highlightJs (feature flag): $highlightJs',
+ async ({ highlightJs, shouldFetchRawText }) => {
+ await createComponent({ highlightJs });
+
+ expect(mockResolver).toHaveBeenCalledWith(expect.objectContaining({ shouldFetchRawText }));
+ },
+ );
+
it('is called with originalBranch value if the prop has a value', async () => {
await createComponent({ inject: { originalBranch: 'some-branch' } });
diff --git a/spec/frontend/repository/components/blob_controls_spec.js b/spec/frontend/repository/components/blob_controls_spec.js
index 03e389ea5cb..6da1861ea7c 100644
--- a/spec/frontend/repository/components/blob_controls_spec.js
+++ b/spec/frontend/repository/components/blob_controls_spec.js
@@ -1,6 +1,6 @@
-import { createLocalVue } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-import { nextTick } from 'vue';
+
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import BlobControls from '~/repository/components/blob_controls.vue';
@@ -16,10 +16,8 @@ let router;
let wrapper;
let mockResolver;
-const localVue = createLocalVue();
-
const createComponent = async () => {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const project = { ...blobControlsDataMock };
const projectPath = 'some/project';
@@ -31,7 +29,6 @@ const createComponent = async () => {
mockResolver = jest.fn().mockResolvedValue({ data: { project } });
wrapper = shallowMountExtended(BlobControls, {
- localVue,
router,
apolloProvider: createMockApollo([[blobControlsQuery, mockResolver]]),
propsData: { projectPath },
diff --git a/spec/frontend/repository/components/blob_viewers/download_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/download_viewer_spec.js
index c71b2b3c55c..5fe25ced302 100644
--- a/spec/frontend/repository/components/blob_viewers/download_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_viewers/download_viewer_spec.js
@@ -6,42 +6,33 @@ import DownloadViewer from '~/repository/components/blob_viewers/download_viewer
describe('Text Viewer', () => {
let wrapper;
- const DEFAULT_PROPS = {
- fileName: 'file_name.js',
- filePath: '/some/file/path',
- fileSize: 2269674,
+ const DEFAULT_BLOB_DATA = {
+ name: 'file_name.js',
+ rawPath: '/some/file/path',
+ rawSize: 2269674,
};
- const createComponent = (props = {}) => {
+ const createComponent = (blobData = {}) => {
wrapper = shallowMount(DownloadViewer, {
propsData: {
- ...DEFAULT_PROPS,
- ...props,
+ blob: {
+ ...DEFAULT_BLOB_DATA,
+ ...blobData,
+ },
},
});
};
- it('renders component', () => {
- createComponent();
-
- const { fileName, filePath, fileSize } = DEFAULT_PROPS;
- expect(wrapper.props()).toMatchObject({
- fileName,
- filePath,
- fileSize,
- });
- });
-
it('renders download human readable file size text', () => {
createComponent();
- const downloadText = `Download (${numberToHumanSize(DEFAULT_PROPS.fileSize)})`;
+ const downloadText = `Download (${numberToHumanSize(DEFAULT_BLOB_DATA.rawSize)})`;
expect(wrapper.text()).toBe(downloadText);
});
it('renders download text', () => {
createComponent({
- fileSize: 0,
+ rawSize: 0,
});
expect(wrapper.text()).toBe('Download');
@@ -49,13 +40,13 @@ describe('Text Viewer', () => {
it('renders download link', () => {
createComponent();
- const { filePath, fileName } = DEFAULT_PROPS;
+ const { rawPath, name } = DEFAULT_BLOB_DATA;
expect(wrapper.findComponent(GlLink).attributes()).toMatchObject({
rel: 'nofollow',
target: '_blank',
- href: filePath,
- download: fileName,
+ href: rawPath,
+ download: name,
});
});
diff --git a/spec/frontend/repository/components/blob_viewers/image_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/image_viewer_spec.js
index 6735dddf51e..c23de0efdfd 100644
--- a/spec/frontend/repository/components/blob_viewers/image_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_viewers/image_viewer_spec.js
@@ -4,13 +4,13 @@ import ImageViewer from '~/repository/components/blob_viewers/image_viewer.vue';
describe('Image Viewer', () => {
let wrapper;
- const propsData = {
- url: 'some/image.png',
- alt: 'image.png',
+ const DEFAULT_BLOB_DATA = {
+ rawPath: 'some/image.png',
+ name: 'image.png',
};
const createComponent = () => {
- wrapper = shallowMount(ImageViewer, { propsData });
+ wrapper = shallowMount(ImageViewer, { propsData: { blob: DEFAULT_BLOB_DATA } });
};
const findImage = () => wrapper.find('[data-testid="image"]');
@@ -19,7 +19,7 @@ describe('Image Viewer', () => {
createComponent();
expect(findImage().exists()).toBe(true);
- expect(findImage().attributes('src')).toBe(propsData.url);
- expect(findImage().attributes('alt')).toBe(propsData.alt);
+ expect(findImage().attributes('src')).toBe(DEFAULT_BLOB_DATA.rawPath);
+ expect(findImage().attributes('alt')).toBe(DEFAULT_BLOB_DATA.name);
});
});
diff --git a/spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js
new file mode 100644
index 00000000000..5caeb85834d
--- /dev/null
+++ b/spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js
@@ -0,0 +1,41 @@
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import LfsViewer from '~/repository/components/blob_viewers/lfs_viewer.vue';
+
+describe('LFS Viewer', () => {
+ let wrapper;
+
+ const DEFAULT_BLOB_DATA = {
+ name: 'file_name.js',
+ rawPath: '/some/file/path',
+ };
+
+ const createComponent = () => {
+ wrapper = shallowMount(LfsViewer, {
+ propsData: { blob: { ...DEFAULT_BLOB_DATA } },
+ stubs: { GlSprintf },
+ });
+ };
+
+ const findLink = () => wrapper.findComponent(GlLink);
+
+ beforeEach(() => createComponent());
+
+ afterEach(() => wrapper.destroy());
+
+ it('renders the correct text', () => {
+ expect(wrapper.text()).toBe(
+ 'This content could not be displayed because it is stored in LFS. You can download it instead.',
+ );
+ });
+
+ it('renders download link', () => {
+ const { rawPath, name } = DEFAULT_BLOB_DATA;
+
+ expect(findLink().attributes()).toMatchObject({
+ target: '_blank',
+ href: rawPath,
+ download: name,
+ });
+ });
+});
diff --git a/spec/frontend/repository/components/blob_viewers/pdf_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/pdf_viewer_spec.js
index fd910002529..10eea691335 100644
--- a/spec/frontend/repository/components/blob_viewers/pdf_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_viewers/pdf_viewer_spec.js
@@ -6,10 +6,12 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
describe('PDF Viewer', () => {
let wrapper;
- const defaultPropsData = { url: 'some/pdf_blob.pdf' };
+ const DEFAULT_BLOB_DATA = { rawPath: 'some/pdf_blob.pdf' };
- const createComponent = (fileSize = 999) => {
- wrapper = shallowMountExtended(Component, { propsData: { ...defaultPropsData, fileSize } });
+ const createComponent = (rawSize = 999) => {
+ wrapper = shallowMountExtended(Component, {
+ propsData: { blob: { ...DEFAULT_BLOB_DATA, rawSize } },
+ });
};
const findPDFViewer = () => wrapper.findComponent(PdfViewer);
@@ -20,7 +22,7 @@ describe('PDF Viewer', () => {
createComponent();
expect(findPDFViewer().exists()).toBe(true);
- expect(findPDFViewer().props('pdf')).toBe(defaultPropsData.url);
+ expect(findPDFViewer().props('pdf')).toBe(DEFAULT_BLOB_DATA.rawPath);
});
describe('Too large', () => {
diff --git a/spec/frontend/repository/components/blob_viewers/video_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/video_viewer_spec.js
index 34448c03b31..2e79a1496ce 100644
--- a/spec/frontend/repository/components/blob_viewers/video_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_viewers/video_viewer_spec.js
@@ -4,10 +4,10 @@ import VideoViewer from '~/repository/components/blob_viewers/video_viewer.vue';
describe('Video Viewer', () => {
let wrapper;
- const propsData = { url: 'some/video.mp4' };
+ const DEFAULT_BLOB_DATA = { rawPath: 'some/video.mp4' };
const createComponent = () => {
- wrapper = shallowMountExtended(VideoViewer, { propsData });
+ wrapper = shallowMountExtended(VideoViewer, { propsData: { blob: { ...DEFAULT_BLOB_DATA } } });
};
const findVideo = () => wrapper.findByTestId('video');
@@ -16,7 +16,7 @@ describe('Video Viewer', () => {
createComponent();
expect(findVideo().exists()).toBe(true);
- expect(findVideo().attributes('src')).toBe(propsData.url);
+ expect(findVideo().attributes('src')).toBe(DEFAULT_BLOB_DATA.rawPath);
expect(findVideo().attributes('controls')).not.toBeUndefined();
});
});
diff --git a/spec/frontend/repository/components/breadcrumbs_spec.js b/spec/frontend/repository/components/breadcrumbs_spec.js
index ad2cbd70187..0e300291d05 100644
--- a/spec/frontend/repository/components/breadcrumbs_spec.js
+++ b/spec/frontend/repository/components/breadcrumbs_spec.js
@@ -1,5 +1,6 @@
import { GlDropdown } from '@gitlab/ui';
import { shallowMount, RouterLinkStub } from '@vue/test-utils';
+import { nextTick } from 'vue';
import Breadcrumbs from '~/repository/components/breadcrumbs.vue';
import UploadBlobModal from '~/repository/components/upload_blob_modal.vue';
import NewDirectoryModal from '~/repository/components/new_directory_modal.vue';
@@ -79,7 +80,7 @@ describe('Repository breadcrumbs component', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ userPermissions: { forkProject: false, createMergeRequestIn: false } });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlDropdown).exists()).toBe(false);
});
@@ -106,7 +107,7 @@ describe('Repository breadcrumbs component', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ userPermissions: { forkProject: true, createMergeRequestIn: true } });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlDropdown).exists()).toBe(true);
});
@@ -125,7 +126,7 @@ describe('Repository breadcrumbs component', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ $apollo: { queries: { userPermissions: { loading: false } } } });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findUploadBlobModal().exists()).toBe(true);
});
@@ -149,7 +150,7 @@ describe('Repository breadcrumbs component', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ $apollo: { queries: { userPermissions: { loading: false } } } });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findNewDirectoryModal().exists()).toBe(true);
});
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/repository/components/upload_blob_modal_spec.js b/spec/frontend/repository/components/upload_blob_modal_spec.js
index 6b8b0752485..bf024baa627 100644
--- a/spec/frontend/repository/components/upload_blob_modal_spec.js
+++ b/spec/frontend/repository/components/upload_blob_modal_spec.js
@@ -2,6 +2,7 @@ import { GlModal, GlFormInput, GlFormTextarea, GlToggle, GlAlert } from '@gitlab
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 createFlash from '~/flash';
import httpStatusCodes from '~/lib/utils/http_status';
@@ -113,7 +114,7 @@ describe('UploadBlobModal', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ target: 'Not main' });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findMrToggle().exists()).toBe(true);
});
@@ -202,7 +203,7 @@ describe('UploadBlobModal', () => {
wrapper.vm.uploadFile = jest.fn();
wrapper.vm.replaceFile = jest.fn();
wrapper.vm.submitForm();
- await wrapper.vm.$nextTick();
+ await nextTick();
};
const submitRequest = async () => {
diff --git a/spec/frontend/repository/mock_data.js b/spec/frontend/repository/mock_data.js
index a5ee17ba672..5a6551cb94a 100644
--- a/spec/frontend/repository/mock_data.js
+++ b/spec/frontend/repository/mock_data.js
@@ -5,16 +5,20 @@ export const simpleViewerMock = {
rawSize: 123,
rawTextBlob: 'raw content',
fileType: 'text',
+ language: 'javascript',
path: 'some_file.js',
webPath: 'some_file.js',
editBlobPath: 'some_file.js/edit',
ideEditPath: 'some_file.js/ide/edit',
forkAndEditPath: 'some_file.js/fork/edit',
ideForkAndEditPath: 'some_file.js/fork/ide',
+ environmentFormattedExternalUrl: '',
+ environmentExternalUrlForRouteMap: '',
canModifyBlob: true,
canCurrentUserPushToBranch: true,
archived: false,
storedExternally: false,
+ externalStorageUrl: '',
externalStorage: 'lfs',
rawPath: 'some_file.js',
replacePath: 'some_file.js/replace',
@@ -46,6 +50,7 @@ export const userPermissionsMock = {
};
export const projectMock = {
+ __typename: 'Project',
id: '1234',
userPermissions: userPermissionsMock,
pathLocks: {
diff --git a/spec/frontend/runner/admin_runner_edit/admin_runner_edit_app_spec.js b/spec/frontend/runner/admin_runner_edit/admin_runner_edit_app_spec.js
index ad0bce5c9af..ff6a632a4f8 100644
--- a/spec/frontend/runner/admin_runner_edit/admin_runner_edit_app_spec.js
+++ b/spec/frontend/runner/admin_runner_edit/admin_runner_edit_app_spec.js
@@ -1,4 +1,5 @@
-import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
+import { mount, shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -18,8 +19,7 @@ jest.mock('~/runner/sentry_utils');
const mockRunnerGraphqlId = runnerData.data.runner.id;
const mockRunnerId = `${getIdFromGraphQLId(mockRunnerGraphqlId)}`;
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
describe('AdminRunnerEditApp', () => {
let wrapper;
@@ -29,7 +29,6 @@ describe('AdminRunnerEditApp', () => {
const createComponentWithApollo = ({ props = {}, mountFn = shallowMount } = {}) => {
wrapper = mountFn(AdminRunnerEditApp, {
- localVue,
apolloProvider: createMockApollo([[getRunnerQuery, mockRunnerQuery]]),
propsData: {
runnerId: mockRunnerId,
@@ -55,10 +54,11 @@ describe('AdminRunnerEditApp', () => {
expect(mockRunnerQuery).toHaveBeenCalledWith({ id: mockRunnerGraphqlId });
});
- it('displays the runner id', async () => {
+ it('displays the runner id and creation date', async () => {
await createComponentWithApollo({ mountFn: mount });
- expect(findRunnerHeader().text()).toContain(`Runner #${mockRunnerId} created`);
+ expect(findRunnerHeader().text()).toContain(`Runner #${mockRunnerId}`);
+ expect(findRunnerHeader().text()).toContain('created');
});
it('displays the runner type and status', async () => {
@@ -76,7 +76,7 @@ describe('AdminRunnerEditApp', () => {
it('error is reported to sentry', () => {
expect(captureException).toHaveBeenCalledWith({
- error: new Error('Network error: Error!'),
+ error: new Error('Error!'),
component: 'AdminRunnerEditApp',
});
});
diff --git a/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js b/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js
new file mode 100644
index 00000000000..4b651961112
--- /dev/null
+++ b/spec/frontend/runner/admin_runner_show/admin_runner_show_app_spec.js
@@ -0,0 +1,146 @@
+import Vue from 'vue';
+import { mount, shallowMount } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { createAlert } from '~/flash';
+
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import RunnerHeader from '~/runner/components/runner_header.vue';
+import RunnerPauseButton from '~/runner/components/runner_pause_button.vue';
+import RunnerEditButton from '~/runner/components/runner_edit_button.vue';
+import getRunnerQuery from '~/runner/graphql/get_runner.query.graphql';
+import AdminRunnerShowApp from '~/runner/admin_runner_show/admin_runner_show_app.vue';
+import { captureException } from '~/runner/sentry_utils';
+
+import { runnerData } from '../mock_data';
+
+jest.mock('~/flash');
+jest.mock('~/runner/sentry_utils');
+
+const mockRunner = runnerData.data.runner;
+const mockRunnerGraphqlId = mockRunner.id;
+const mockRunnerId = `${getIdFromGraphQLId(mockRunnerGraphqlId)}`;
+
+Vue.use(VueApollo);
+
+describe('AdminRunnerShowApp', () => {
+ let wrapper;
+ let mockRunnerQuery;
+
+ const findRunnerHeader = () => wrapper.findComponent(RunnerHeader);
+ const findRunnerEditButton = () => wrapper.findComponent(RunnerEditButton);
+ const findRunnerPauseButton = () => wrapper.findComponent(RunnerPauseButton);
+
+ const mockRunnerQueryResult = (runner = {}) => {
+ mockRunnerQuery = jest.fn().mockResolvedValue({
+ data: {
+ runner: { ...mockRunner, ...runner },
+ },
+ });
+ };
+
+ const createComponent = ({ props = {}, mountFn = shallowMount } = {}) => {
+ wrapper = mountFn(AdminRunnerShowApp, {
+ apolloProvider: createMockApollo([[getRunnerQuery, mockRunnerQuery]]),
+ propsData: {
+ runnerId: mockRunnerId,
+ ...props,
+ },
+ });
+
+ return waitForPromises();
+ };
+
+ afterEach(() => {
+ mockRunnerQuery.mockReset();
+ wrapper.destroy();
+ });
+
+ describe('When showing runner details', () => {
+ beforeEach(async () => {
+ mockRunnerQueryResult();
+
+ await createComponent({ mountFn: mount });
+ });
+
+ it('expect GraphQL ID to be requested', async () => {
+ expect(mockRunnerQuery).toHaveBeenCalledWith({ id: mockRunnerGraphqlId });
+ });
+
+ it('displays the runner header', async () => {
+ expect(findRunnerHeader().text()).toContain(`Runner #${mockRunnerId}`);
+ });
+
+ it('displays the runner edit and pause buttons', async () => {
+ expect(findRunnerEditButton().exists()).toBe(true);
+ expect(findRunnerPauseButton().exists()).toBe(true);
+ });
+
+ it('shows basic runner details', async () => {
+ const expected = `Description Instance runner
+ Last contact Never contacted
+ Version 1.0.0
+ IP Address 127.0.0.1
+ Configuration Runs untagged jobs
+ Maximum job timeout None
+ Tags None`.replace(/\s+/g, ' ');
+
+ expect(wrapper.text().replace(/\s+/g, ' ')).toContain(expected);
+ });
+
+ describe('when runner cannot be updated', () => {
+ beforeEach(async () => {
+ mockRunnerQueryResult({
+ userPermissions: {
+ updateRunner: false,
+ },
+ });
+
+ await createComponent({
+ mountFn: mount,
+ });
+ });
+
+ it('does not display the runner edit and pause buttons', () => {
+ expect(findRunnerEditButton().exists()).toBe(false);
+ expect(findRunnerPauseButton().exists()).toBe(false);
+ });
+ });
+
+ describe('when runner does not have an edit url ', () => {
+ beforeEach(async () => {
+ mockRunnerQueryResult({
+ editAdminUrl: null,
+ });
+
+ await createComponent({
+ mountFn: mount,
+ });
+ });
+
+ it('does not display the runner edit button', () => {
+ expect(findRunnerEditButton().exists()).toBe(false);
+ expect(findRunnerPauseButton().exists()).toBe(true);
+ });
+ });
+ });
+
+ describe('When there is an error', () => {
+ beforeEach(async () => {
+ mockRunnerQuery = jest.fn().mockRejectedValueOnce(new Error('Error!'));
+ await createComponent();
+ });
+
+ it('error is reported to sentry', () => {
+ expect(captureException).toHaveBeenCalledWith({
+ error: new Error('Error!'),
+ component: 'AdminRunnerShowApp',
+ });
+ });
+
+ it('error is shown to the user', () => {
+ expect(createAlert).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js
index 42be691ba4c..995f0cf7ba1 100644
--- a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js
+++ b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js
@@ -1,9 +1,13 @@
+import Vue from 'vue';
import { GlLink } from '@gitlab/ui';
-import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import {
+ extendedWrapper,
+ shallowMountExtended,
+ mountExtended,
+} from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
@@ -46,8 +50,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
updateHistory: jest.fn(),
}));
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
describe('AdminRunnersApp', () => {
let wrapper;
@@ -65,22 +68,19 @@ describe('AdminRunnersApp', () => {
const findRunnerFilteredSearchBar = () => wrapper.findComponent(RunnerFilteredSearchBar);
const findFilteredSearch = () => wrapper.findComponent(FilteredSearch);
- const createComponent = ({ props = {}, mountFn = shallowMount } = {}) => {
+ const createComponent = ({ props = {}, mountFn = shallowMountExtended } = {}) => {
const handlers = [
[getRunnersQuery, mockRunnersQuery],
[getRunnersCountQuery, mockRunnersCountQuery],
];
- wrapper = extendedWrapper(
- mountFn(AdminRunnersApp, {
- localVue,
- apolloProvider: createMockApollo(handlers),
- propsData: {
- registrationToken: mockRegistrationToken,
- ...props,
- },
- }),
- );
+ wrapper = mountFn(AdminRunnersApp, {
+ apolloProvider: createMockApollo(handlers),
+ propsData: {
+ registrationToken: mockRegistrationToken,
+ ...props,
+ },
+ });
};
beforeEach(async () => {
@@ -98,7 +98,7 @@ describe('AdminRunnersApp', () => {
});
it('shows total runner counts', async () => {
- createComponent({ mountFn: mount });
+ createComponent({ mountFn: mountExtended });
await waitForPromises();
@@ -129,7 +129,7 @@ describe('AdminRunnersApp', () => {
return Promise.resolve({ data: { runners: { count } } });
});
- createComponent({ mountFn: mount });
+ createComponent({ mountFn: mountExtended });
await waitForPromises();
expect(findRunnerTypeTabs().text()).toMatchInterpolatedText(
@@ -157,7 +157,7 @@ describe('AdminRunnersApp', () => {
return Promise.resolve({ data: { runners: { count } } });
});
- createComponent({ mountFn: mount });
+ createComponent({ mountFn: mountExtended });
await waitForPromises();
expect(findRunnerTypeTabs().text()).toMatchInterpolatedText(
@@ -175,7 +175,7 @@ describe('AdminRunnersApp', () => {
});
it('runner item links to the runner admin page', async () => {
- createComponent({ mountFn: mount });
+ createComponent({ mountFn: mountExtended });
await waitForPromises();
@@ -198,7 +198,7 @@ describe('AdminRunnersApp', () => {
});
it('sets tokens in the filtered search', () => {
- createComponent({ mountFn: mount });
+ createComponent({ mountFn: mountExtended });
expect(findFilteredSearch().props('tokens')).toEqual([
expect.objectContaining({
@@ -281,6 +281,7 @@ describe('AdminRunnersApp', () => {
},
});
createComponent();
+ await waitForPromises();
});
it('shows a message for no results', async () => {
@@ -289,9 +290,10 @@ describe('AdminRunnersApp', () => {
});
describe('when runners query fails', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mockRunnersQuery = jest.fn().mockRejectedValue(new Error('Error!'));
createComponent();
+ await waitForPromises();
});
it('error is shown to the user', async () => {
@@ -300,17 +302,18 @@ describe('AdminRunnersApp', () => {
it('error is reported to sentry', async () => {
expect(captureException).toHaveBeenCalledWith({
- error: new Error('Network error: Error!'),
+ error: new Error('Error!'),
component: 'AdminRunnersApp',
});
});
});
describe('Pagination', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mockRunnersQuery = jest.fn().mockResolvedValue(runnersDataPaginated);
- createComponent({ mountFn: mount });
+ createComponent({ mountFn: mountExtended });
+ await waitForPromises();
});
it('more pages can be selected', () => {
diff --git a/spec/frontend/runner/components/cells/link_cell_spec.js b/spec/frontend/runner/components/cells/link_cell_spec.js
new file mode 100644
index 00000000000..a59a0eaa5d8
--- /dev/null
+++ b/spec/frontend/runner/components/cells/link_cell_spec.js
@@ -0,0 +1,72 @@
+import { GlLink } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import LinkCell from '~/runner/components/cells/link_cell.vue';
+
+describe('LinkCell', () => {
+ let wrapper;
+
+ const findGlLink = () => wrapper.find(GlLink);
+ const findSpan = () => wrapper.find('span');
+
+ const createComponent = ({ props = {}, ...options } = {}) => {
+ wrapper = shallowMountExtended(LinkCell, {
+ propsData: {
+ ...props,
+ },
+ ...options,
+ });
+ };
+
+ it('when an href is provided, renders a link', () => {
+ createComponent({ props: { href: '/url' } });
+ expect(findGlLink().exists()).toBe(true);
+ });
+
+ it('when an href is not provided, renders no link', () => {
+ createComponent();
+ expect(findGlLink().exists()).toBe(false);
+ });
+
+ describe.each`
+ href | findContent
+ ${null} | ${findSpan}
+ ${'/url'} | ${findGlLink}
+ `('When href is $href', ({ href, findContent }) => {
+ const content = 'My Text';
+ const attrs = { foo: 'bar' };
+ const listeners = {
+ click: jest.fn(),
+ };
+
+ beforeEach(() => {
+ createComponent({
+ props: { href },
+ slots: {
+ default: content,
+ },
+ attrs,
+ listeners,
+ });
+ });
+
+ afterAll(() => {
+ listeners.click.mockReset();
+ });
+
+ it('Renders content', () => {
+ expect(findContent().text()).toBe(content);
+ });
+
+ it('Passes attributes', () => {
+ expect(findContent().attributes()).toMatchObject(attrs);
+ });
+
+ it('Passes event listeners', () => {
+ expect(listeners.click).toHaveBeenCalledTimes(0);
+
+ findContent().vm.$emit('click');
+
+ expect(listeners.click).toHaveBeenCalledTimes(1);
+ });
+ });
+});
diff --git a/spec/frontend/runner/components/cells/runner_actions_cell_spec.js b/spec/frontend/runner/components/cells/runner_actions_cell_spec.js
index 4233d86c24c..dcb0af67784 100644
--- a/spec/frontend/runner/components/cells/runner_actions_cell_spec.js
+++ b/spec/frontend/runner/components/cells/runner_actions_cell_spec.js
@@ -1,7 +1,7 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { createAlert } from '~/flash';
@@ -9,11 +9,12 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { captureException } from '~/runner/sentry_utils';
import RunnerActionCell from '~/runner/components/cells/runner_actions_cell.vue';
+import RunnerPauseButton from '~/runner/components/runner_pause_button.vue';
+import RunnerEditButton from '~/runner/components/runner_edit_button.vue';
import RunnerDeleteModal from '~/runner/components/runner_delete_modal.vue';
import getGroupRunnersQuery from '~/runner/graphql/get_group_runners.query.graphql';
import getRunnersQuery from '~/runner/graphql/get_runners.query.graphql';
import runnerDeleteMutation from '~/runner/graphql/runner_delete.mutation.graphql';
-import runnerActionsUpdateMutation from '~/runner/graphql/runner_actions_update.mutation.graphql';
import { runnersData } from '../../mock_data';
const mockRunner = runnersData.data.runners.nodes[0];
@@ -21,8 +22,7 @@ const mockRunner = runnersData.data.runners.nodes[0];
const getRunnersQueryName = getRunnersQuery.definitions[0].name.value;
const getGroupRunnersQueryName = getGroupRunnersQuery.definitions[0].name.value;
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
jest.mock('~/flash');
jest.mock('~/runner/sentry_utils');
@@ -32,44 +32,37 @@ describe('RunnerTypeCell', () => {
const mockToastShow = jest.fn();
const runnerDeleteMutationHandler = jest.fn();
- const runnerActionsUpdateMutationHandler = jest.fn();
- const findEditBtn = () => wrapper.findByTestId('edit-runner');
- const findToggleActiveBtn = () => wrapper.findByTestId('toggle-active-runner');
+ const findEditBtn = () => wrapper.findComponent(RunnerEditButton);
+ const findRunnerPauseBtn = () => wrapper.findComponent(RunnerPauseButton);
const findRunnerDeleteModal = () => wrapper.findComponent(RunnerDeleteModal);
const findDeleteBtn = () => wrapper.findByTestId('delete-runner');
const getTooltip = (w) => getBinding(w.element, 'gl-tooltip')?.value;
const createComponent = (runner = {}, options) => {
- wrapper = extendedWrapper(
- shallowMount(RunnerActionCell, {
- propsData: {
- runner: {
- id: mockRunner.id,
- shortSha: mockRunner.shortSha,
- editAdminUrl: mockRunner.editAdminUrl,
- userPermissions: mockRunner.userPermissions,
- active: mockRunner.active,
- ...runner,
- },
+ wrapper = shallowMountExtended(RunnerActionCell, {
+ propsData: {
+ runner: {
+ id: mockRunner.id,
+ shortSha: mockRunner.shortSha,
+ editAdminUrl: mockRunner.editAdminUrl,
+ userPermissions: mockRunner.userPermissions,
+ active: mockRunner.active,
+ ...runner,
},
- localVue,
- apolloProvider: createMockApollo([
- [runnerDeleteMutation, runnerDeleteMutationHandler],
- [runnerActionsUpdateMutation, runnerActionsUpdateMutationHandler],
- ]),
- directives: {
- GlTooltip: createMockDirective(),
- GlModal: createMockDirective(),
- },
- mocks: {
- $toast: {
- show: mockToastShow,
- },
+ },
+ apolloProvider: createMockApollo([[runnerDeleteMutation, runnerDeleteMutationHandler]]),
+ directives: {
+ GlTooltip: createMockDirective(),
+ GlModal: createMockDirective(),
+ },
+ mocks: {
+ $toast: {
+ show: mockToastShow,
},
- ...options,
- }),
- );
+ },
+ ...options,
+ });
};
beforeEach(() => {
@@ -80,21 +73,11 @@ describe('RunnerTypeCell', () => {
},
},
});
-
- runnerActionsUpdateMutationHandler.mockResolvedValue({
- data: {
- runnerUpdate: {
- runner: mockRunner,
- errors: [],
- },
- },
- });
});
afterEach(() => {
mockToastShow.mockReset();
runnerDeleteMutationHandler.mockReset();
- runnerActionsUpdateMutationHandler.mockReset();
wrapper.destroy();
});
@@ -126,116 +109,14 @@ describe('RunnerTypeCell', () => {
});
});
- describe('Toggle active action', () => {
- describe.each`
- state | label | icon | isActive | newActiveValue
- ${'active'} | ${'Pause'} | ${'pause'} | ${true} | ${false}
- ${'paused'} | ${'Resume'} | ${'play'} | ${false} | ${true}
- `('When the runner is $state', ({ label, icon, isActive, newActiveValue }) => {
- beforeEach(() => {
- createComponent({ active: isActive });
- });
-
- it(`Displays a ${icon} button`, () => {
- expect(findToggleActiveBtn().props('loading')).toBe(false);
- expect(findToggleActiveBtn().props('icon')).toBe(icon);
- expect(getTooltip(findToggleActiveBtn())).toBe(label);
- expect(findToggleActiveBtn().attributes('aria-label')).toBe(label);
- });
-
- it(`After clicking the ${icon} button, the button has a loading state`, async () => {
- await findToggleActiveBtn().vm.$emit('click');
-
- expect(findToggleActiveBtn().props('loading')).toBe(true);
- });
-
- it(`After the ${icon} button is clicked, stale tooltip is removed`, async () => {
- await findToggleActiveBtn().vm.$emit('click');
-
- expect(getTooltip(findToggleActiveBtn())).toBe('');
- expect(findToggleActiveBtn().attributes('aria-label')).toBe('');
- });
-
- describe(`When clicking on the ${icon} button`, () => {
- it(`The apollo mutation to set active to ${newActiveValue} is called`, async () => {
- expect(runnerActionsUpdateMutationHandler).toHaveBeenCalledTimes(0);
-
- await findToggleActiveBtn().vm.$emit('click');
-
- expect(runnerActionsUpdateMutationHandler).toHaveBeenCalledTimes(1);
- expect(runnerActionsUpdateMutationHandler).toHaveBeenCalledWith({
- input: {
- id: mockRunner.id,
- active: newActiveValue,
- },
- });
- });
-
- it('The button does not have a loading state after the mutation occurs', async () => {
- await findToggleActiveBtn().vm.$emit('click');
-
- expect(findToggleActiveBtn().props('loading')).toBe(true);
-
- await waitForPromises();
-
- expect(findToggleActiveBtn().props('loading')).toBe(false);
- });
- });
-
- describe('When update fails', () => {
- describe('On a network error', () => {
- const mockErrorMsg = 'Update error!';
-
- beforeEach(async () => {
- runnerActionsUpdateMutationHandler.mockRejectedValueOnce(new Error(mockErrorMsg));
-
- await findToggleActiveBtn().vm.$emit('click');
- });
-
- it('error is reported to sentry', () => {
- expect(captureException).toHaveBeenCalledWith({
- error: new Error(`Network error: ${mockErrorMsg}`),
- component: 'RunnerActionsCell',
- });
- });
-
- it('error is shown to the user', () => {
- expect(createAlert).toHaveBeenCalledTimes(1);
- });
- });
-
- describe('On a validation error', () => {
- const mockErrorMsg = 'Runner not found!';
- const mockErrorMsg2 = 'User not allowed!';
-
- beforeEach(async () => {
- runnerActionsUpdateMutationHandler.mockResolvedValue({
- data: {
- runnerUpdate: {
- runner: mockRunner,
- errors: [mockErrorMsg, mockErrorMsg2],
- },
- },
- });
-
- await findToggleActiveBtn().vm.$emit('click');
- });
-
- it('error is reported to sentry', () => {
- expect(captureException).toHaveBeenCalledWith({
- error: new Error(`${mockErrorMsg} ${mockErrorMsg2}`),
- component: 'RunnerActionsCell',
- });
- });
+ describe('Pause action', () => {
+ it('Renders a compact pause button', () => {
+ createComponent();
- it('error is shown to the user', () => {
- expect(createAlert).toHaveBeenCalledTimes(1);
- });
- });
- });
+ expect(findRunnerPauseBtn().props('compact')).toBe(true);
});
- it('Does not render the runner toggle active button when user cannot update', () => {
+ it('Does not render the runner pause button when user cannot update', () => {
createComponent({
userPermissions: {
...mockRunner.userPermissions,
@@ -243,7 +124,7 @@ describe('RunnerTypeCell', () => {
},
});
- expect(findToggleActiveBtn().exists()).toBe(false);
+ expect(findRunnerPauseBtn().exists()).toBe(false);
});
});
@@ -308,8 +189,9 @@ describe('RunnerTypeCell', () => {
});
describe('When delete is clicked', () => {
- beforeEach(() => {
+ beforeEach(async () => {
findRunnerDeleteModal().vm.$emit('primary');
+ await waitForPromises();
});
it('The delete mutation is called correctly', () => {
@@ -324,7 +206,8 @@ describe('RunnerTypeCell', () => {
expect(getTooltip(findDeleteBtn())).toBe('');
});
- it('The toast notification is shown', () => {
+ it('The toast notification is shown', async () => {
+ await waitForPromises();
expect(mockToastShow).toHaveBeenCalledTimes(1);
expect(mockToastShow).toHaveBeenCalledWith(
expect.stringContaining(`#${getIdFromGraphQLId(mockRunner.id)} (${mockRunner.shortSha})`),
@@ -336,15 +219,16 @@ describe('RunnerTypeCell', () => {
describe('On a network error', () => {
const mockErrorMsg = 'Delete error!';
- beforeEach(() => {
+ beforeEach(async () => {
runnerDeleteMutationHandler.mockRejectedValueOnce(new Error(mockErrorMsg));
findRunnerDeleteModal().vm.$emit('primary');
+ await waitForPromises();
});
it('error is reported to sentry', () => {
expect(captureException).toHaveBeenCalledWith({
- error: new Error(`Network error: ${mockErrorMsg}`),
+ error: new Error(mockErrorMsg),
component: 'RunnerActionsCell',
});
});
@@ -362,7 +246,7 @@ describe('RunnerTypeCell', () => {
const mockErrorMsg = 'Runner not found!';
const mockErrorMsg2 = 'User not allowed!';
- beforeEach(() => {
+ beforeEach(async () => {
runnerDeleteMutationHandler.mockResolvedValue({
data: {
runnerDelete: {
@@ -372,6 +256,7 @@ describe('RunnerTypeCell', () => {
});
findRunnerDeleteModal().vm.$emit('primary');
+ await waitForPromises();
});
it('error is reported to sentry', () => {
diff --git a/spec/frontend/runner/components/registration/registration_dropdown_spec.js b/spec/frontend/runner/components/registration/registration_dropdown_spec.js
index d18d2bec18e..da8ef7c3af0 100644
--- a/spec/frontend/runner/components/registration/registration_dropdown_spec.js
+++ b/spec/frontend/runner/components/registration/registration_dropdown_spec.js
@@ -1,9 +1,11 @@
import { GlDropdown, GlDropdownItem, GlDropdownForm } from '@gitlab/ui';
-import { createLocalVue, mount, shallowMount, createWrapper } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { mount, shallowMount, createWrapper } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+
import VueApollo from 'vue-apollo';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue';
import RegistrationTokenResetDropdownItem from '~/runner/components/registration/registration_token_reset_dropdown_item.vue';
@@ -73,8 +75,7 @@ describe('RegistrationDropdown', () => {
});
describe('When the dropdown item is clicked', () => {
- const localVue = createLocalVue();
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
const requestHandlers = [
[getRunnerPlatformsQuery, jest.fn().mockResolvedValue(mockGraphqlRunnerPlatforms)],
@@ -84,10 +85,9 @@ describe('RegistrationDropdown', () => {
const findModalInBody = () =>
createWrapper(document.body).find('[data-testid="runner-instructions-modal"]');
- beforeEach(() => {
+ beforeEach(async () => {
createComponent(
{
- localVue,
// Mock load modal contents from API
apolloProvider: createMockApollo(requestHandlers),
// Use `attachTo` to find the modal
@@ -96,7 +96,8 @@ describe('RegistrationDropdown', () => {
mount,
);
- findRegistrationInstructionsDropdownItem().trigger('click');
+ await findRegistrationInstructionsDropdownItem().trigger('click');
+ await waitForPromises();
});
afterEach(() => {
diff --git a/spec/frontend/runner/components/registration/registration_token_reset_dropdown_item_spec.js b/spec/frontend/runner/components/registration/registration_token_reset_dropdown_item_spec.js
index e75decddf70..d2deb49a5f7 100644
--- a/spec/frontend/runner/components/registration/registration_token_reset_dropdown_item_spec.js
+++ b/spec/frontend/runner/components/registration/registration_token_reset_dropdown_item_spec.js
@@ -1,6 +1,7 @@
import { GlDropdownItem, GlLoadingIcon, GlToast, GlModal } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -14,9 +15,8 @@ import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
jest.mock('~/flash');
jest.mock('~/runner/sentry_utils');
-const localVue = createLocalVue();
-localVue.use(VueApollo);
-localVue.use(GlToast);
+Vue.use(VueApollo);
+Vue.use(GlToast);
const mockNewToken = 'NEW_TOKEN';
const modalID = 'token-reset-modal';
@@ -34,7 +34,6 @@ describe('RegistrationTokenResetDropdownItem', () => {
const createComponent = ({ props, provide = {} } = {}) => {
wrapper = shallowMount(RegistrationTokenResetDropdownItem, {
- localVue,
provide,
propsData: {
type: INSTANCE_TYPE,
@@ -163,10 +162,10 @@ describe('RegistrationTokenResetDropdownItem', () => {
await waitForPromises();
expect(createAlert).toHaveBeenLastCalledWith({
- message: `Network error: ${mockErrorMsg}`,
+ message: mockErrorMsg,
});
expect(captureException).toHaveBeenCalledWith({
- error: new Error(`Network error: ${mockErrorMsg}`),
+ error: new Error(mockErrorMsg),
component: 'RunnerRegistrationTokenReset',
});
});
diff --git a/spec/frontend/runner/components/registration/registration_token_spec.js b/spec/frontend/runner/components/registration/registration_token_spec.js
index f53ae165344..6b9708cc525 100644
--- a/spec/frontend/runner/components/registration/registration_token_spec.js
+++ b/spec/frontend/runner/components/registration/registration_token_spec.js
@@ -1,7 +1,7 @@
import { nextTick } from 'vue';
import { GlToast } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { createLocalVue } from '@vue/test-utils';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RegistrationToken from '~/runner/components/registration/registration_token.vue';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
@@ -25,15 +25,13 @@ describe('RegistrationToken', () => {
const createComponent = ({ props = {}, withGlToast = true } = {}) => {
const localVue = withGlToast ? vueWithGlToast() : undefined;
- wrapper = extendedWrapper(
- shallowMount(RegistrationToken, {
- propsData: {
- value: mockToken,
- ...props,
- },
- localVue,
- }),
- );
+ wrapper = shallowMountExtended(RegistrationToken, {
+ propsData: {
+ value: mockToken,
+ ...props,
+ },
+ localVue,
+ });
showToast = wrapper.vm.$toast ? jest.spyOn(wrapper.vm.$toast, 'show') : null;
};
diff --git a/spec/frontend/runner/components/runner_assigned_item_spec.js b/spec/frontend/runner/components/runner_assigned_item_spec.js
new file mode 100644
index 00000000000..c6156c16d4a
--- /dev/null
+++ b/spec/frontend/runner/components/runner_assigned_item_spec.js
@@ -0,0 +1,53 @@
+import { GlAvatar } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import RunnerAssignedItem from '~/runner/components/runner_assigned_item.vue';
+
+const mockHref = '/group/project';
+const mockName = 'Project';
+const mockFullName = 'Group / Project';
+const mockAvatarUrl = '/avatar.png';
+
+describe('RunnerAssignedItem', () => {
+ let wrapper;
+
+ const findAvatar = () => wrapper.findByTestId('item-avatar');
+
+ const createComponent = ({ props = {} } = {}) => {
+ wrapper = shallowMountExtended(RunnerAssignedItem, {
+ propsData: {
+ href: mockHref,
+ name: mockName,
+ fullName: mockFullName,
+ avatarUrl: mockAvatarUrl,
+ ...props,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('Shows an avatar', () => {
+ const avatar = findAvatar();
+
+ expect(avatar.attributes('href')).toBe(mockHref);
+ expect(avatar.findComponent(GlAvatar).props()).toMatchObject({
+ alt: mockName,
+ entityName: mockName,
+ src: mockAvatarUrl,
+ shape: 'rect',
+ size: 48,
+ });
+ });
+
+ it('Shows an item link', () => {
+ const groupFullName = wrapper.findByText(mockFullName);
+
+ expect(groupFullName.attributes('href')).toBe(mockHref);
+ });
+});
diff --git a/spec/frontend/runner/components/runner_details_spec.js b/spec/frontend/runner/components/runner_details_spec.js
new file mode 100644
index 00000000000..6bf4a52a799
--- /dev/null
+++ b/spec/frontend/runner/components/runner_details_spec.js
@@ -0,0 +1,189 @@
+import { GlSprintf, GlIntersperse, GlTab } from '@gitlab/ui';
+import { createWrapper, ErrorWrapper } from '@vue/test-utils';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import { useFakeDate } from 'helpers/fake_date';
+import { ACCESS_LEVEL_REF_PROTECTED, ACCESS_LEVEL_NOT_PROTECTED } from '~/runner/constants';
+
+import RunnerDetails from '~/runner/components/runner_details.vue';
+import RunnerDetail from '~/runner/components/runner_detail.vue';
+import RunnerGroups from '~/runner/components/runner_groups.vue';
+import RunnersJobs from '~/runner/components/runner_jobs.vue';
+import RunnerTags from '~/runner/components/runner_tags.vue';
+import RunnerTag from '~/runner/components/runner_tag.vue';
+
+import { runnerData, runnerWithGroupData } from '../mock_data';
+
+const mockRunner = runnerData.data.runner;
+const mockGroupRunner = runnerWithGroupData.data.runner;
+
+describe('RunnerDetails', () => {
+ let wrapper;
+ const mockNow = '2021-01-15T12:00:00Z';
+ const mockOneHourAgo = '2021-01-15T11:00:00Z';
+
+ useFakeDate(mockNow);
+
+ /**
+ * Find the definition (<dd>) that corresponds to this term (<dt>)
+ * @param {string} dtLabel - Label for this value
+ * @returns Wrapper
+ */
+ const findDd = (dtLabel) => {
+ const dt = wrapper.findByText(dtLabel).element;
+ const dd = dt.nextElementSibling;
+ if (dt.tagName === 'DT' && dd.tagName === 'DD') {
+ return createWrapper(dd, {});
+ }
+ return ErrorWrapper(dtLabel);
+ };
+
+ const findDetailGroups = () => wrapper.findComponent(RunnerGroups);
+ const findRunnersJobs = () => wrapper.findComponent(RunnersJobs);
+ const findJobCountBadge = () => wrapper.findByTestId('job-count-badge');
+
+ const createComponent = ({ props = {}, mountFn = shallowMountExtended, stubs } = {}) => {
+ wrapper = mountFn(RunnerDetails, {
+ propsData: {
+ ...props,
+ },
+ stubs: {
+ RunnerDetail,
+ ...stubs,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('when no runner is present, no contents are shown', () => {
+ createComponent({
+ props: {
+ runner: null,
+ },
+ });
+
+ expect(wrapper.text()).toBe('');
+ });
+
+ describe('Details tab', () => {
+ describe.each`
+ field | runner | expectedValue
+ ${'Description'} | ${{ description: 'My runner' }} | ${'My runner'}
+ ${'Description'} | ${{ description: null }} | ${'None'}
+ ${'Last contact'} | ${{ contactedAt: mockOneHourAgo }} | ${'1 hour ago'}
+ ${'Last contact'} | ${{ contactedAt: null }} | ${'Never contacted'}
+ ${'Version'} | ${{ version: '12.3' }} | ${'12.3'}
+ ${'Version'} | ${{ version: null }} | ${'None'}
+ ${'IP Address'} | ${{ ipAddress: '127.0.0.1' }} | ${'127.0.0.1'}
+ ${'IP Address'} | ${{ ipAddress: null }} | ${'None'}
+ ${'Configuration'} | ${{ accessLevel: ACCESS_LEVEL_REF_PROTECTED, runUntagged: true }} | ${'Protected, Runs untagged jobs'}
+ ${'Configuration'} | ${{ accessLevel: ACCESS_LEVEL_REF_PROTECTED, runUntagged: false }} | ${'Protected'}
+ ${'Configuration'} | ${{ accessLevel: ACCESS_LEVEL_NOT_PROTECTED, runUntagged: true }} | ${'Runs untagged jobs'}
+ ${'Configuration'} | ${{ accessLevel: ACCESS_LEVEL_NOT_PROTECTED, runUntagged: false }} | ${'None'}
+ ${'Maximum job timeout'} | ${{ maximumTimeout: null }} | ${'None'}
+ ${'Maximum job timeout'} | ${{ maximumTimeout: 0 }} | ${'0 seconds'}
+ ${'Maximum job timeout'} | ${{ maximumTimeout: 59 }} | ${'59 seconds'}
+ ${'Maximum job timeout'} | ${{ maximumTimeout: 10 * 60 + 5 }} | ${'10 minutes 5 seconds'}
+ `('"$field" field', ({ field, runner, expectedValue }) => {
+ beforeEach(() => {
+ createComponent({
+ props: {
+ runner: {
+ ...mockRunner,
+ ...runner,
+ },
+ },
+ stubs: {
+ GlIntersperse,
+ GlSprintf,
+ TimeAgo,
+ },
+ });
+ });
+
+ it(`displays expected value "${expectedValue}"`, () => {
+ expect(findDd(field).text()).toBe(expectedValue);
+ });
+ });
+
+ describe('"Tags" field', () => {
+ const stubs = { RunnerTags, RunnerTag };
+
+ it('displays expected value "tag-1 tag-2"', () => {
+ createComponent({
+ props: {
+ runner: { ...mockRunner, tagList: ['tag-1', 'tag-2'] },
+ },
+ stubs,
+ });
+
+ expect(findDd('Tags').text().replace(/\s+/g, ' ')).toBe('tag-1 tag-2');
+ });
+
+ it('displays "None" when runner has no tags', () => {
+ createComponent({
+ props: {
+ runner: { ...mockRunner, tagList: [] },
+ },
+ stubs,
+ });
+
+ expect(findDd('Tags').text().replace(/\s+/g, ' ')).toBe('None');
+ });
+ });
+
+ describe('Group runners', () => {
+ beforeEach(() => {
+ createComponent({
+ props: {
+ runner: mockGroupRunner,
+ },
+ });
+ });
+
+ it('Shows a group runner details', () => {
+ expect(findDetailGroups().props('runner')).toEqual(mockGroupRunner);
+ });
+ });
+ });
+
+ describe('Jobs tab', () => {
+ const stubs = { GlTab };
+
+ it('without a runner, shows no jobs', () => {
+ createComponent({
+ props: { runner: null },
+ stubs,
+ });
+
+ expect(findJobCountBadge().exists()).toBe(false);
+ expect(findRunnersJobs().exists()).toBe(false);
+ });
+
+ it('without a job count, shows no jobs count', () => {
+ createComponent({
+ props: {
+ runner: { ...mockRunner, jobCount: undefined },
+ },
+ stubs,
+ });
+
+ expect(findJobCountBadge().exists()).toBe(false);
+ });
+
+ it('with a job count, shows jobs count', () => {
+ const runner = { ...mockRunner, jobCount: 3 };
+
+ createComponent({
+ props: { runner },
+ stubs,
+ });
+
+ expect(findJobCountBadge().text()).toBe('3');
+ expect(findRunnersJobs().props('runner')).toBe(runner);
+ });
+ });
+});
diff --git a/spec/frontend/runner/components/runner_edit_button_spec.js b/spec/frontend/runner/components/runner_edit_button_spec.js
new file mode 100644
index 00000000000..428c1ef07e9
--- /dev/null
+++ b/spec/frontend/runner/components/runner_edit_button_spec.js
@@ -0,0 +1,41 @@
+import { shallowMount, mount } from '@vue/test-utils';
+import RunnerEditButton from '~/runner/components/runner_edit_button.vue';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+
+describe('RunnerEditButton', () => {
+ let wrapper;
+
+ const getTooltipValue = () => getBinding(wrapper.element, 'gl-tooltip').value;
+
+ const createComponent = ({ attrs = {}, mountFn = shallowMount } = {}) => {
+ wrapper = mountFn(RunnerEditButton, {
+ attrs,
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('Displays Edit text', () => {
+ expect(wrapper.attributes('aria-label')).toBe('Edit');
+ });
+
+ it('Displays Edit tooltip', () => {
+ expect(getTooltipValue()).toBe('Edit');
+ });
+
+ it('Renders a link and adds an href attribute', () => {
+ createComponent({ attrs: { href: '/edit' }, mountFn: mount });
+
+ expect(wrapper.element.tagName).toBe('A');
+ expect(wrapper.attributes('href')).toBe('/edit');
+ });
+});
diff --git a/spec/frontend/runner/components/runner_filtered_search_bar_spec.js b/spec/frontend/runner/components/runner_filtered_search_bar_spec.js
index 5ab0db019a3..fda96e5918e 100644
--- a/spec/frontend/runner/components/runner_filtered_search_bar_spec.js
+++ b/spec/frontend/runner/components/runner_filtered_search_bar_spec.js
@@ -1,6 +1,5 @@
import { GlFilteredSearch, GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import { statusTokenConfig } from '~/runner/components/search_tokens/status_token_config';
import TagToken from '~/runner/components/search_tokens/tag_token.vue';
@@ -29,27 +28,25 @@ describe('RunnerList', () => {
};
const createComponent = ({ props = {}, options = {} } = {}) => {
- wrapper = extendedWrapper(
- shallowMount(RunnerFilteredSearchBar, {
- propsData: {
- namespace: 'runners',
- tokens: [],
- value: {
- runnerType: null,
- filters: [],
- sort: mockDefaultSort,
- },
- ...props,
+ wrapper = shallowMountExtended(RunnerFilteredSearchBar, {
+ propsData: {
+ namespace: 'runners',
+ tokens: [],
+ value: {
+ runnerType: null,
+ filters: [],
+ sort: mockDefaultSort,
},
- stubs: {
- FilteredSearch,
- GlFilteredSearch,
- GlDropdown,
- GlDropdownItem,
- },
- ...options,
- }),
- );
+ ...props,
+ },
+ stubs: {
+ FilteredSearch,
+ GlFilteredSearch,
+ GlDropdown,
+ GlDropdownItem,
+ },
+ ...options,
+ });
};
beforeEach(() => {
diff --git a/spec/frontend/runner/components/runner_groups_spec.js b/spec/frontend/runner/components/runner_groups_spec.js
new file mode 100644
index 00000000000..b83733b9972
--- /dev/null
+++ b/spec/frontend/runner/components/runner_groups_spec.js
@@ -0,0 +1,67 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+
+import RunnerGroups from '~/runner/components/runner_groups.vue';
+import RunnerAssignedItem from '~/runner/components/runner_assigned_item.vue';
+
+import { runnerData, runnerWithGroupData } from '../mock_data';
+
+const mockInstanceRunner = runnerData.data.runner;
+const mockGroupRunner = runnerWithGroupData.data.runner;
+const mockGroup = mockGroupRunner.groups.nodes[0];
+
+describe('RunnerGroups', () => {
+ let wrapper;
+
+ const findHeading = () => wrapper.find('h3');
+ const findRunnerAssignedItems = () => wrapper.findAllComponents(RunnerAssignedItem);
+
+ const createComponent = ({ runner = mockGroupRunner, mountFn = shallowMountExtended } = {}) => {
+ wrapper = mountFn(RunnerGroups, {
+ propsData: {
+ runner,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('Shows a heading', () => {
+ createComponent();
+
+ expect(findHeading().text()).toBe('Assigned Group');
+ });
+
+ describe('When there is a group runner', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('Shows a project', () => {
+ createComponent();
+
+ const item = findRunnerAssignedItems().at(0);
+ const { webUrl, name, fullName, avatarUrl } = mockGroup;
+
+ expect(item.props()).toMatchObject({
+ href: webUrl,
+ name,
+ fullName,
+ avatarUrl,
+ });
+ });
+ });
+
+ describe('When there are no groups', () => {
+ beforeEach(() => {
+ createComponent({
+ runner: mockInstanceRunner,
+ });
+ });
+
+ it('Shows a "None" label', () => {
+ expect(wrapper.findByText('None').exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/runner/components/runner_header_spec.js b/spec/frontend/runner/components/runner_header_spec.js
index 50699df3a44..8799c218b06 100644
--- a/spec/frontend/runner/components/runner_header_spec.js
+++ b/spec/frontend/runner/components/runner_header_spec.js
@@ -1,5 +1,5 @@
import { GlSprintf } from '@gitlab/ui';
-import { mount, shallowMount } from '@vue/test-utils';
+import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { GROUP_TYPE, STATUS_ONLINE } from '~/runner/constants';
import { TYPE_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
@@ -18,9 +18,10 @@ describe('RunnerHeader', () => {
const findRunnerTypeBadge = () => wrapper.findComponent(RunnerTypeBadge);
const findRunnerStatusBadge = () => wrapper.findComponent(RunnerStatusBadge);
+ const findRunnerLockedIcon = () => wrapper.findByTestId('lock-icon');
const findTimeAgo = () => wrapper.findComponent(TimeAgo);
- const createComponent = ({ runner = {}, mountFn = shallowMount } = {}) => {
+ const createComponent = ({ runner = {}, options = {}, mountFn = shallowMountExtended } = {}) => {
wrapper = mountFn(RunnerHeader, {
propsData: {
runner: {
@@ -32,6 +33,7 @@ describe('RunnerHeader', () => {
GlSprintf,
TimeAgo,
},
+ ...options,
});
};
@@ -41,24 +43,24 @@ describe('RunnerHeader', () => {
it('displays the runner status', () => {
createComponent({
- mountFn: mount,
+ mountFn: mountExtended,
runner: {
status: STATUS_ONLINE,
},
});
- expect(findRunnerStatusBadge().text()).toContain(`online`);
+ expect(findRunnerStatusBadge().text()).toContain('online');
});
it('displays the runner type', () => {
createComponent({
- mountFn: mount,
+ mountFn: mountExtended,
runner: {
runnerType: GROUP_TYPE,
},
});
- expect(findRunnerTypeBadge().text()).toContain(`group`);
+ expect(findRunnerTypeBadge().text()).toContain('group');
});
it('displays the runner id', () => {
@@ -68,7 +70,18 @@ describe('RunnerHeader', () => {
},
});
- expect(wrapper.text()).toContain(`Runner #99`);
+ expect(wrapper.text()).toContain('Runner #99');
+ });
+
+ it('displays the runner locked icon', () => {
+ createComponent({
+ runner: {
+ locked: true,
+ },
+ mountFn: mountExtended,
+ });
+
+ expect(findRunnerLockedIcon().exists()).toBe(true);
});
it('displays the runner creation time', () => {
@@ -78,7 +91,7 @@ describe('RunnerHeader', () => {
expect(findTimeAgo().props('time')).toBe(mockRunner.createdAt);
});
- it('does not display runner creation time if createdAt missing', () => {
+ it('does not display runner creation time if "createdAt" is missing', () => {
createComponent({
runner: {
id: convertToGraphQLId(TYPE_CI_RUNNER, 99),
@@ -86,8 +99,21 @@ describe('RunnerHeader', () => {
},
});
- expect(wrapper.text()).toContain(`Runner #99`);
+ expect(wrapper.text()).toContain('Runner #99');
expect(wrapper.text()).not.toMatch(/created .+/);
expect(findTimeAgo().exists()).toBe(false);
});
+
+ it('displays actions in a slot', () => {
+ createComponent({
+ options: {
+ slots: {
+ actions: '<div data-testid="actions-content">My Actions</div>',
+ },
+ mountFn: mountExtended,
+ },
+ });
+
+ expect(wrapper.findByTestId('actions-content').text()).toBe('My Actions');
+ });
});
diff --git a/spec/frontend/runner/components/runner_jobs_spec.js b/spec/frontend/runner/components/runner_jobs_spec.js
new file mode 100644
index 00000000000..97339056370
--- /dev/null
+++ b/spec/frontend/runner/components/runner_jobs_spec.js
@@ -0,0 +1,156 @@
+import { GlSkeletonLoading } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { createAlert } from '~/flash';
+import RunnerJobs from '~/runner/components/runner_jobs.vue';
+import RunnerJobsTable from '~/runner/components/runner_jobs_table.vue';
+import RunnerPagination from '~/runner/components/runner_pagination.vue';
+import { captureException } from '~/runner/sentry_utils';
+import { I18N_NO_JOBS_FOUND, RUNNER_DETAILS_JOBS_PAGE_SIZE } from '~/runner/constants';
+
+import getRunnerJobsQuery from '~/runner/graphql/get_runner_jobs.query.graphql';
+
+import { runnerData, runnerJobsData } from '../mock_data';
+
+jest.mock('~/flash');
+jest.mock('~/runner/sentry_utils');
+
+const mockRunner = runnerData.data.runner;
+const mockRunnerWithJobs = runnerJobsData.data.runner;
+const mockJobs = mockRunnerWithJobs.jobs.nodes;
+
+Vue.use(VueApollo);
+
+describe('RunnerJobs', () => {
+ let wrapper;
+ let mockRunnerJobsQuery;
+
+ const findGlSkeletonLoading = () => wrapper.findComponent(GlSkeletonLoading);
+ const findRunnerJobsTable = () => wrapper.findComponent(RunnerJobsTable);
+ const findRunnerPagination = () => wrapper.findComponent(RunnerPagination);
+
+ const createComponent = ({ mountFn = shallowMountExtended } = {}) => {
+ wrapper = mountFn(RunnerJobs, {
+ apolloProvider: createMockApollo([[getRunnerJobsQuery, mockRunnerJobsQuery]]),
+ propsData: {
+ runner: mockRunner,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ mockRunnerJobsQuery = jest.fn();
+ });
+
+ afterEach(() => {
+ mockRunnerJobsQuery.mockReset();
+ wrapper.destroy();
+ });
+
+ it('Requests runner jobs', async () => {
+ createComponent();
+
+ await waitForPromises();
+
+ expect(mockRunnerJobsQuery).toHaveBeenCalledTimes(1);
+ expect(mockRunnerJobsQuery).toHaveBeenCalledWith({
+ id: mockRunner.id,
+ first: RUNNER_DETAILS_JOBS_PAGE_SIZE,
+ });
+ });
+
+ describe('When there are jobs assigned', () => {
+ beforeEach(async () => {
+ mockRunnerJobsQuery.mockResolvedValueOnce(runnerJobsData);
+
+ createComponent();
+ await waitForPromises();
+ });
+
+ it('Shows jobs', () => {
+ const jobs = findRunnerJobsTable().props('jobs');
+
+ expect(jobs).toHaveLength(mockJobs.length);
+ expect(jobs[0]).toMatchObject(mockJobs[0]);
+ });
+
+ describe('When "Next" page is clicked', () => {
+ beforeEach(async () => {
+ findRunnerPagination().vm.$emit('input', { page: 2, after: 'AFTER_CURSOR' });
+
+ await waitForPromises();
+ });
+
+ it('A new page is requested', () => {
+ expect(mockRunnerJobsQuery).toHaveBeenCalledTimes(2);
+ expect(mockRunnerJobsQuery).toHaveBeenLastCalledWith({
+ id: mockRunner.id,
+ first: RUNNER_DETAILS_JOBS_PAGE_SIZE,
+ after: 'AFTER_CURSOR',
+ });
+ });
+ });
+ });
+
+ describe('When loading', () => {
+ it('shows loading indicator and no other content', () => {
+ createComponent();
+
+ expect(findGlSkeletonLoading().exists()).toBe(true);
+ expect(findRunnerJobsTable().exists()).toBe(false);
+ expect(findRunnerPagination().attributes('disabled')).toBe('true');
+ });
+ });
+
+ describe('When there are no jobs', () => {
+ beforeEach(async () => {
+ mockRunnerJobsQuery.mockResolvedValueOnce({
+ data: {
+ runner: {
+ id: mockRunner.id,
+ projectCount: 0,
+ jobs: {
+ nodes: [],
+ pageInfo: {
+ hasNextPage: false,
+ hasPreviousPage: false,
+ startCursor: '',
+ endCursor: '',
+ },
+ },
+ },
+ },
+ });
+
+ createComponent();
+ await waitForPromises();
+ });
+
+ it('Shows a "None" label', () => {
+ expect(wrapper.text()).toBe(I18N_NO_JOBS_FOUND);
+ });
+ });
+
+ describe('When an error occurs', () => {
+ beforeEach(async () => {
+ mockRunnerJobsQuery.mockRejectedValue(new Error('Error!'));
+
+ createComponent();
+ await waitForPromises();
+ });
+
+ it('shows an error', () => {
+ expect(createAlert).toHaveBeenCalled();
+ });
+
+ it('reports an error', () => {
+ expect(captureException).toHaveBeenCalledWith({
+ component: 'RunnerJobs',
+ error: expect.any(Error),
+ });
+ });
+ });
+});
diff --git a/spec/frontend/runner/components/runner_jobs_table_spec.js b/spec/frontend/runner/components/runner_jobs_table_spec.js
new file mode 100644
index 00000000000..5f4905ad2a8
--- /dev/null
+++ b/spec/frontend/runner/components/runner_jobs_table_spec.js
@@ -0,0 +1,119 @@
+import { GlTableLite } from '@gitlab/ui';
+import {
+ extendedWrapper,
+ shallowMountExtended,
+ mountExtended,
+} from 'helpers/vue_test_utils_helper';
+import { __, s__ } from '~/locale';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import RunnerJobsTable from '~/runner/components/runner_jobs_table.vue';
+import { useFakeDate } from 'helpers/fake_date';
+import { runnerJobsData } from '../mock_data';
+
+const mockJobs = runnerJobsData.data.runner.jobs.nodes;
+
+describe('RunnerJobsTable', () => {
+ let wrapper;
+ const mockNow = '2021-01-15T12:00:00Z';
+ const mockOneHourAgo = '2021-01-15T11:00:00Z';
+
+ useFakeDate(mockNow);
+
+ const findTable = () => wrapper.findComponent(GlTableLite);
+ const findHeaders = () => wrapper.findAll('th');
+ const findRows = () => wrapper.findAll('[data-testid^="job-row-"]');
+ const findCell = ({ field }) =>
+ extendedWrapper(findRows().at(0).find(`[data-testid="td-${field}"]`));
+
+ const createComponent = ({ props = {} } = {}, mountFn = shallowMountExtended) => {
+ wrapper = mountFn(RunnerJobsTable, {
+ propsData: {
+ jobs: mockJobs,
+ ...props,
+ },
+ stubs: {
+ GlTableLite,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('Sets job id as a row key', () => {
+ createComponent();
+
+ expect(findTable().attributes('primarykey')).toBe('id');
+ });
+
+ describe('Table data', () => {
+ beforeEach(() => {
+ createComponent({}, mountExtended);
+ });
+
+ it('Displays headers', () => {
+ const headerLabels = findHeaders().wrappers.map((w) => w.text());
+
+ expect(headerLabels).toEqual([
+ s__('Job|Status'),
+ __('Job'),
+ __('Project'),
+ __('Commit'),
+ s__('Job|Finished at'),
+ s__('Runners|Tags'),
+ ]);
+ });
+
+ it('Displays a list of jobs', () => {
+ expect(findRows()).toHaveLength(1);
+ });
+
+ it('Displays details of a job', () => {
+ const { id, detailedStatus, pipeline, shortSha, commitPath } = mockJobs[0];
+
+ expect(findCell({ field: 'status' }).text()).toMatchInterpolatedText(detailedStatus.text);
+
+ expect(findCell({ field: 'job' }).text()).toContain(`#${getIdFromGraphQLId(id)}`);
+ expect(findCell({ field: 'job' }).find('a').attributes('href')).toBe(
+ detailedStatus.detailsPath,
+ );
+
+ expect(findCell({ field: 'project' }).text()).toBe(pipeline.project.name);
+ expect(findCell({ field: 'project' }).find('a').attributes('href')).toBe(
+ pipeline.project.webUrl,
+ );
+
+ expect(findCell({ field: 'commit' }).text()).toBe(shortSha);
+ expect(findCell({ field: 'commit' }).find('a').attributes('href')).toBe(commitPath);
+ });
+ });
+
+ describe('Table data formatting', () => {
+ let mockJobsCopy;
+
+ beforeEach(() => {
+ mockJobsCopy = [
+ {
+ ...mockJobs[0],
+ },
+ ];
+ });
+
+ it('Formats finishedAt time', () => {
+ mockJobsCopy[0].finishedAt = mockOneHourAgo;
+
+ createComponent({ props: { jobs: mockJobsCopy } }, mountExtended);
+
+ expect(findCell({ field: 'finished_at' }).text()).toBe('1 hour ago');
+ });
+
+ it('Formats tags', () => {
+ mockJobsCopy[0].tags = ['tag-1', 'tag-2'];
+
+ createComponent({ props: { jobs: mockJobsCopy } }, mountExtended);
+
+ expect(findCell({ field: 'tags' }).text()).toMatchInterpolatedText('tag-1 tag-2');
+ });
+ });
+});
diff --git a/spec/frontend/runner/components/runner_list_spec.js b/spec/frontend/runner/components/runner_list_spec.js
index 452430b7237..42d6ecca09e 100644
--- a/spec/frontend/runner/components/runner_list_spec.js
+++ b/spec/frontend/runner/components/runner_list_spec.js
@@ -1,8 +1,13 @@
import { GlTable, GlSkeletonLoader } from '@gitlab/ui';
-import { mount, shallowMount } from '@vue/test-utils';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import {
+ extendedWrapper,
+ shallowMountExtended,
+ mountExtended,
+} from 'helpers/vue_test_utils_helper';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerList from '~/runner/components/runner_list.vue';
+import RunnerEditButton from '~/runner/components/runner_edit_button.vue';
+import RunnerPauseButton from '~/runner/components/runner_pause_button.vue';
import { runnersData } from '../mock_data';
const mockRunners = runnersData.data.runners.nodes;
@@ -18,20 +23,18 @@ describe('RunnerList', () => {
const findCell = ({ row = 0, fieldKey }) =>
extendedWrapper(findRows().at(row).find(`[data-testid="td-${fieldKey}"]`));
- const createComponent = ({ props = {} } = {}, mountFn = shallowMount) => {
- wrapper = extendedWrapper(
- mountFn(RunnerList, {
- propsData: {
- runners: mockRunners,
- activeRunnersCount: mockActiveRunnersCount,
- ...props,
- },
- }),
- );
+ const createComponent = ({ props = {} } = {}, mountFn = shallowMountExtended) => {
+ wrapper = mountFn(RunnerList, {
+ propsData: {
+ runners: mockRunners,
+ activeRunnersCount: mockActiveRunnersCount,
+ ...props,
+ },
+ });
};
beforeEach(() => {
- createComponent({}, mount);
+ createComponent({}, mountExtended);
});
afterEach(() => {
@@ -43,9 +46,9 @@ describe('RunnerList', () => {
expect(headerLabels).toEqual([
'Status',
- 'Runner ID',
+ 'Runner',
'Version',
- 'IP Address',
+ 'IP',
'Jobs',
'Tags',
'Last contact',
@@ -54,7 +57,7 @@ describe('RunnerList', () => {
});
it('Sets runner id as a row key', () => {
- createComponent({}, shallowMount);
+ createComponent({});
expect(findTable().attributes('primary-key')).toBe('id');
});
@@ -89,8 +92,9 @@ describe('RunnerList', () => {
// Actions
const actions = findCell({ fieldKey: 'actions' });
- expect(actions.findByTestId('edit-runner').exists()).toBe(true);
- expect(actions.findByTestId('toggle-active-runner').exists()).toBe(true);
+ expect(actions.findComponent(RunnerEditButton).exists()).toBe(true);
+ expect(actions.findComponent(RunnerPauseButton).exists()).toBe(true);
+ expect(actions.findByTestId('delete-runner').exists()).toBe(true);
});
describe('Table data formatting', () => {
@@ -107,7 +111,7 @@ describe('RunnerList', () => {
it('Formats job counts', () => {
mockRunnersCopy[0].jobCount = 1;
- createComponent({ props: { runners: mockRunnersCopy } }, mount);
+ createComponent({ props: { runners: mockRunnersCopy } }, mountExtended);
expect(findCell({ fieldKey: 'jobCount' }).text()).toBe('1');
});
@@ -115,7 +119,7 @@ describe('RunnerList', () => {
it('Formats large job counts', () => {
mockRunnersCopy[0].jobCount = 1000;
- createComponent({ props: { runners: mockRunnersCopy } }, mount);
+ createComponent({ props: { runners: mockRunnersCopy } }, mountExtended);
expect(findCell({ fieldKey: 'jobCount' }).text()).toBe('1,000');
});
@@ -123,7 +127,7 @@ describe('RunnerList', () => {
it('Formats large job counts with a plus symbol', () => {
mockRunnersCopy[0].jobCount = 1001;
- createComponent({ props: { runners: mockRunnersCopy } }, mount);
+ createComponent({ props: { runners: mockRunnersCopy } }, mountExtended);
expect(findCell({ fieldKey: 'jobCount' }).text()).toBe('1,000+');
});
@@ -143,13 +147,13 @@ describe('RunnerList', () => {
});
it('when there are no runners, shows an skeleton loader', () => {
- createComponent({ props: { runners: [], loading: true } }, mount);
+ createComponent({ props: { runners: [], loading: true } }, mountExtended);
expect(findSkeletonLoader().exists()).toBe(true);
});
it('when there are runners, shows a busy indicator skeleton loader', () => {
- createComponent({ props: { loading: true } }, mount);
+ createComponent({ props: { loading: true } }, mountExtended);
expect(findSkeletonLoader().exists()).toBe(false);
});
diff --git a/spec/frontend/runner/components/runner_pagination_spec.js b/spec/frontend/runner/components/runner_pagination_spec.js
index 59feb32dd2a..ecd6e6bd7f9 100644
--- a/spec/frontend/runner/components/runner_pagination_spec.js
+++ b/spec/frontend/runner/components/runner_pagination_spec.js
@@ -104,7 +104,6 @@ describe('RunnerPagination', () => {
expect(wrapper.emitted('input')[0]).toEqual([
{
- before: mockStartCursor,
page: 1,
},
]);
diff --git a/spec/frontend/runner/components/runner_pause_button_spec.js b/spec/frontend/runner/components/runner_pause_button_spec.js
new file mode 100644
index 00000000000..278f3dec2ee
--- /dev/null
+++ b/spec/frontend/runner/components/runner_pause_button_spec.js
@@ -0,0 +1,239 @@
+import Vue from 'vue';
+import { GlButton } from '@gitlab/ui';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
+import runnerToggleActiveMutation from '~/runner/graphql/runner_toggle_active.mutation.graphql';
+import waitForPromises from 'helpers/wait_for_promises';
+import { captureException } from '~/runner/sentry_utils';
+import { createAlert } from '~/flash';
+
+import RunnerPauseButton from '~/runner/components/runner_pause_button.vue';
+import { runnersData } from '../mock_data';
+
+const mockRunner = runnersData.data.runners.nodes[0];
+
+Vue.use(VueApollo);
+
+jest.mock('~/flash');
+jest.mock('~/runner/sentry_utils');
+
+describe('RunnerPauseButton', () => {
+ let wrapper;
+ let runnerToggleActiveHandler;
+
+ const getTooltip = () => getBinding(wrapper.element, 'gl-tooltip').value;
+ const findBtn = () => wrapper.findComponent(GlButton);
+
+ const createComponent = ({ props = {}, mountFn = shallowMountExtended } = {}) => {
+ const { runner, ...propsData } = props;
+
+ wrapper = mountFn(RunnerPauseButton, {
+ propsData: {
+ runner: {
+ id: mockRunner.id,
+ active: mockRunner.active,
+ ...runner,
+ },
+ ...propsData,
+ },
+ apolloProvider: createMockApollo([[runnerToggleActiveMutation, runnerToggleActiveHandler]]),
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
+ });
+ };
+
+ const clickAndWait = async () => {
+ findBtn().vm.$emit('click');
+ await waitForPromises();
+ };
+
+ beforeEach(() => {
+ runnerToggleActiveHandler = jest.fn().mockImplementation(({ input }) => {
+ return Promise.resolve({
+ data: {
+ runnerUpdate: {
+ runner: {
+ id: input.id,
+ active: input.active,
+ },
+ errors: [],
+ },
+ },
+ });
+ });
+
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('Pause/Resume action', () => {
+ describe.each`
+ runnerState | icon | content | isActive | newActiveValue
+ ${'paused'} | ${'play'} | ${'Resume'} | ${false} | ${true}
+ ${'active'} | ${'pause'} | ${'Pause'} | ${true} | ${false}
+ `('When the runner is $runnerState', ({ icon, content, isActive, newActiveValue }) => {
+ beforeEach(() => {
+ createComponent({
+ props: {
+ runner: {
+ active: isActive,
+ },
+ },
+ });
+ });
+
+ it(`Displays a ${icon} button`, () => {
+ expect(findBtn().props('loading')).toBe(false);
+ expect(findBtn().props('icon')).toBe(icon);
+ expect(findBtn().text()).toBe(content);
+ });
+
+ it('Does not display redundant text for screen readers', () => {
+ expect(findBtn().attributes('aria-label')).toBe(undefined);
+ });
+
+ describe(`Before the ${icon} button is clicked`, () => {
+ it('The mutation has not been called', () => {
+ expect(runnerToggleActiveHandler).toHaveBeenCalledTimes(0);
+ });
+ });
+
+ describe(`Immediately after the ${icon} button is clicked`, () => {
+ beforeEach(async () => {
+ findBtn().vm.$emit('click');
+ });
+
+ it('The button has a loading state', async () => {
+ expect(findBtn().props('loading')).toBe(true);
+ });
+
+ it('The stale tooltip is removed', async () => {
+ expect(getTooltip()).toBe('');
+ });
+ });
+
+ describe(`After clicking on the ${icon} button`, () => {
+ beforeEach(async () => {
+ await clickAndWait();
+ });
+
+ it(`The mutation to that sets active to ${newActiveValue} is called`, async () => {
+ expect(runnerToggleActiveHandler).toHaveBeenCalledTimes(1);
+ expect(runnerToggleActiveHandler).toHaveBeenCalledWith({
+ input: {
+ id: mockRunner.id,
+ active: newActiveValue,
+ },
+ });
+ });
+
+ it('The button does not have a loading state', () => {
+ expect(findBtn().props('loading')).toBe(false);
+ });
+ });
+
+ describe('When update fails', () => {
+ describe('On a network error', () => {
+ const mockErrorMsg = 'Update error!';
+
+ beforeEach(async () => {
+ runnerToggleActiveHandler.mockRejectedValueOnce(new Error(mockErrorMsg));
+
+ await clickAndWait();
+ });
+
+ it('error is reported to sentry', () => {
+ expect(captureException).toHaveBeenCalledWith({
+ error: new Error(mockErrorMsg),
+ component: 'RunnerPauseButton',
+ });
+ });
+
+ it('error is shown to the user', () => {
+ expect(createAlert).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('On a validation error', () => {
+ const mockErrorMsg = 'Runner not found!';
+ const mockErrorMsg2 = 'User not allowed!';
+
+ beforeEach(async () => {
+ runnerToggleActiveHandler.mockResolvedValueOnce({
+ data: {
+ runnerUpdate: {
+ runner: {
+ id: mockRunner.id,
+ active: isActive,
+ },
+ errors: [mockErrorMsg, mockErrorMsg2],
+ },
+ },
+ });
+
+ await clickAndWait();
+ });
+
+ it('error is reported to sentry', () => {
+ expect(captureException).toHaveBeenCalledWith({
+ error: new Error(`${mockErrorMsg} ${mockErrorMsg2}`),
+ component: 'RunnerPauseButton',
+ });
+ });
+
+ it('error is shown to the user', () => {
+ expect(createAlert).toHaveBeenCalledTimes(1);
+ });
+ });
+ });
+ });
+ });
+
+ describe('When displaying a compact button for an active runner', () => {
+ beforeEach(() => {
+ createComponent({
+ props: {
+ runner: {
+ active: true,
+ },
+ compact: true,
+ },
+ mountFn: mountExtended,
+ });
+ });
+
+ it('Displays no text', () => {
+ expect(findBtn().text()).toBe('');
+
+ // Note: Use <template v-if> to ensure rendering a
+ // text-less button. Ensure we don't send even empty an
+ // content slot to prevent a distorted/rectangular button.
+ expect(wrapper.find('.gl-button-text').exists()).toBe(false);
+ });
+
+ it('Display correctly for screen readers', () => {
+ expect(findBtn().attributes('aria-label')).toBe('Pause');
+ expect(getTooltip()).toBe('Pause');
+ });
+
+ describe('Immediately after the button is clicked', () => {
+ beforeEach(async () => {
+ findBtn().vm.$emit('click');
+ });
+
+ it('The button has a loading state', async () => {
+ expect(findBtn().props('loading')).toBe(true);
+ });
+
+ it('The stale tooltip is removed', async () => {
+ expect(getTooltip()).toBe('');
+ });
+ });
+ });
+});
diff --git a/spec/frontend/runner/components/runner_projects_spec.js b/spec/frontend/runner/components/runner_projects_spec.js
new file mode 100644
index 00000000000..68a2130d6d9
--- /dev/null
+++ b/spec/frontend/runner/components/runner_projects_spec.js
@@ -0,0 +1,193 @@
+import { GlSkeletonLoading } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { createAlert } from '~/flash';
+import { sprintf } from '~/locale';
+import {
+ I18N_ASSIGNED_PROJECTS,
+ I18N_NONE,
+ RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
+} from '~/runner/constants';
+import RunnerProjects from '~/runner/components/runner_projects.vue';
+import RunnerAssignedItem from '~/runner/components/runner_assigned_item.vue';
+import RunnerPagination from '~/runner/components/runner_pagination.vue';
+import { captureException } from '~/runner/sentry_utils';
+
+import getRunnerProjectsQuery from '~/runner/graphql/get_runner_projects.query.graphql';
+
+import { runnerData, runnerProjectsData } from '../mock_data';
+
+jest.mock('~/flash');
+jest.mock('~/runner/sentry_utils');
+
+const mockRunner = runnerData.data.runner;
+const mockRunnerWithProjects = runnerProjectsData.data.runner;
+const mockProjects = mockRunnerWithProjects.projects.nodes;
+
+Vue.use(VueApollo);
+
+describe('RunnerProjects', () => {
+ let wrapper;
+ let mockRunnerProjectsQuery;
+
+ const findHeading = () => wrapper.find('h3');
+ const findGlSkeletonLoading = () => wrapper.findComponent(GlSkeletonLoading);
+ const findRunnerAssignedItems = () => wrapper.findAllComponents(RunnerAssignedItem);
+ const findRunnerPagination = () => wrapper.findComponent(RunnerPagination);
+
+ const createComponent = ({ mountFn = shallowMountExtended } = {}) => {
+ wrapper = mountFn(RunnerProjects, {
+ apolloProvider: createMockApollo([[getRunnerProjectsQuery, mockRunnerProjectsQuery]]),
+ propsData: {
+ runner: mockRunner,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ mockRunnerProjectsQuery = jest.fn();
+ });
+
+ afterEach(() => {
+ mockRunnerProjectsQuery.mockReset();
+ wrapper.destroy();
+ });
+
+ it('Requests runner projects', async () => {
+ createComponent();
+
+ await waitForPromises();
+
+ expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(1);
+ expect(mockRunnerProjectsQuery).toHaveBeenCalledWith({
+ id: mockRunner.id,
+ first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
+ });
+ });
+
+ describe('When there are projects assigned', () => {
+ beforeEach(async () => {
+ mockRunnerProjectsQuery.mockResolvedValueOnce(runnerProjectsData);
+
+ createComponent();
+ await waitForPromises();
+ });
+
+ it('Shows a heading', async () => {
+ const expected = sprintf(I18N_ASSIGNED_PROJECTS, { projectCount: mockProjects.length });
+
+ expect(findHeading().text()).toBe(expected);
+ });
+
+ it('Shows projects', () => {
+ expect(findRunnerAssignedItems().length).toBe(mockProjects.length);
+ });
+
+ it('Shows a project', () => {
+ const item = findRunnerAssignedItems().at(0);
+ const { webUrl, name, nameWithNamespace, avatarUrl } = mockProjects[0];
+
+ expect(item.props()).toMatchObject({
+ href: webUrl,
+ name,
+ fullName: nameWithNamespace,
+ avatarUrl,
+ });
+ });
+
+ describe('When "Next" page is clicked', () => {
+ beforeEach(async () => {
+ findRunnerPagination().vm.$emit('input', { page: 3, after: 'AFTER_CURSOR' });
+
+ await waitForPromises();
+ });
+
+ it('A new page is requested', () => {
+ expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(2);
+ expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({
+ id: mockRunner.id,
+ first: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
+ after: 'AFTER_CURSOR',
+ });
+ });
+
+ it('When "Prev" page is clicked, the previous page is requested', async () => {
+ findRunnerPagination().vm.$emit('input', { page: 2, before: 'BEFORE_CURSOR' });
+
+ await waitForPromises();
+
+ expect(mockRunnerProjectsQuery).toHaveBeenCalledTimes(3);
+ expect(mockRunnerProjectsQuery).toHaveBeenLastCalledWith({
+ id: mockRunner.id,
+ last: RUNNER_DETAILS_PROJECTS_PAGE_SIZE,
+ before: 'BEFORE_CURSOR',
+ });
+ });
+ });
+ });
+
+ describe('When loading', () => {
+ it('shows loading indicator and no other content', () => {
+ createComponent();
+
+ expect(findGlSkeletonLoading().exists()).toBe(true);
+
+ expect(wrapper.findByText(I18N_NONE).exists()).toBe(false);
+ expect(findRunnerAssignedItems().length).toBe(0);
+
+ expect(findRunnerPagination().attributes('disabled')).toBe('true');
+ });
+ });
+
+ describe('When there are no projects', () => {
+ beforeEach(async () => {
+ mockRunnerProjectsQuery.mockResolvedValueOnce({
+ data: {
+ runner: {
+ id: mockRunner.id,
+ projectCount: 0,
+ projects: {
+ nodes: [],
+ pageInfo: {
+ hasNextPage: false,
+ hasPreviousPage: false,
+ startCursor: '',
+ endCursor: '',
+ },
+ },
+ },
+ },
+ });
+
+ createComponent();
+ await waitForPromises();
+ });
+
+ it('Shows a "None" label', () => {
+ expect(wrapper.findByText(I18N_NONE).exists()).toBe(true);
+ });
+ });
+
+ describe('When an error occurs', () => {
+ beforeEach(async () => {
+ mockRunnerProjectsQuery.mockRejectedValue(new Error('Error!'));
+
+ createComponent();
+ await waitForPromises();
+ });
+
+ it('shows an error', () => {
+ expect(createAlert).toHaveBeenCalled();
+ });
+
+ it('reports an error', () => {
+ expect(captureException).toHaveBeenCalledWith({
+ component: 'RunnerProjects',
+ error: expect.any(Error),
+ });
+ });
+ });
+});
diff --git a/spec/frontend/runner/components/runner_type_tabs_spec.js b/spec/frontend/runner/components/runner_type_tabs_spec.js
index 4871d9c470a..9da5d842d8f 100644
--- a/spec/frontend/runner/components/runner_type_tabs_spec.js
+++ b/spec/frontend/runner/components/runner_type_tabs_spec.js
@@ -1,7 +1,7 @@
import { GlTab } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
-import { INSTANCE_TYPE, GROUP_TYPE } from '~/runner/constants';
+import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/runner/constants';
const mockSearch = { runnerType: null, filters: [], pagination: { page: 1 }, sort: 'CREATED_DESC' };
@@ -13,6 +13,7 @@ describe('RunnerTypeTabs', () => {
findTabs()
.filter((tab) => tab.attributes('active') === 'true')
.at(0);
+ const getTabsTitles = () => findTabs().wrappers.map((tab) => tab.text());
const createComponent = ({ props, ...options } = {}) => {
wrapper = shallowMount(RunnerTypeTabs, {
@@ -35,13 +36,18 @@ describe('RunnerTypeTabs', () => {
wrapper.destroy();
});
- it('Renders options to filter runners', () => {
- expect(findTabs().wrappers.map((tab) => tab.text())).toEqual([
- 'All',
- 'Instance',
- 'Group',
- 'Project',
- ]);
+ it('Renders all options to filter runners by default', () => {
+ expect(getTabsTitles()).toEqual(['All', 'Instance', 'Group', 'Project']);
+ });
+
+ it('Renders fewer options to filter runners', () => {
+ createComponent({
+ props: {
+ runnerTypes: [GROUP_TYPE, PROJECT_TYPE],
+ },
+ });
+
+ expect(getTabsTitles()).toEqual(['All', 'Group', 'Project']);
});
it('"All" is selected by default', () => {
diff --git a/spec/frontend/runner/components/runner_update_form_spec.js b/spec/frontend/runner/components/runner_update_form_spec.js
index ebb2e67d1e2..8b76be396ef 100644
--- a/spec/frontend/runner/components/runner_update_form_spec.js
+++ b/spec/frontend/runner/components/runner_update_form_spec.js
@@ -1,9 +1,8 @@
+import Vue, { nextTick } from 'vue';
import { GlForm } from '@gitlab/ui';
-import { createLocalVue, mount } from '@vue/test-utils';
-import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert, VARIANT_SUCCESS } from '~/flash';
import RunnerUpdateForm from '~/runner/components/runner_update_form.vue';
@@ -23,8 +22,7 @@ jest.mock('~/runner/sentry_utils');
const mockRunner = runnerData.data.runner;
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
describe('RunnerUpdateForm', () => {
let wrapper;
@@ -61,16 +59,13 @@ describe('RunnerUpdateForm', () => {
});
const createComponent = ({ props } = {}) => {
- wrapper = extendedWrapper(
- mount(RunnerUpdateForm, {
- localVue,
- propsData: {
- runner: mockRunner,
- ...props,
- },
- apolloProvider: createMockApollo([[runnerUpdateMutation, runnerUpdateHandler]]),
- }),
- );
+ wrapper = mountExtended(RunnerUpdateForm, {
+ propsData: {
+ runner: mockRunner,
+ ...props,
+ },
+ apolloProvider: createMockApollo([[runnerUpdateMutation, runnerUpdateHandler]]),
+ });
};
const expectToHaveSubmittedRunnerContaining = (submittedRunner) => {
@@ -126,8 +121,21 @@ describe('RunnerUpdateForm', () => {
it('Updates runner with no changes', async () => {
await submitFormAndWait();
- // Some fields are not submitted
- const { ipAddress, runnerType, createdAt, status, ...submitted } = mockRunner;
+ // Some read-only fields are not submitted
+ const {
+ __typename,
+ ipAddress,
+ runnerType,
+ createdAt,
+ status,
+ editAdminUrl,
+ contactedAt,
+ userPermissions,
+ version,
+ groups,
+ jobCount,
+ ...submitted
+ } = mockRunner;
expectToHaveSubmittedRunnerContaining(submitted);
});
@@ -239,11 +247,11 @@ describe('RunnerUpdateForm', () => {
await submitFormAndWait();
expect(createAlert).toHaveBeenLastCalledWith({
- message: `Network error: ${mockErrorMsg}`,
+ message: mockErrorMsg,
});
expect(captureException).toHaveBeenCalledWith({
component: 'RunnerUpdateForm',
- error: new Error(`Network error: ${mockErrorMsg}`),
+ error: new Error(mockErrorMsg),
});
expect(findSubmitDisabledAttr()).toBeUndefined();
});
diff --git a/spec/frontend/runner/group_runners/group_runners_app_spec.js b/spec/frontend/runner/group_runners/group_runners_app_spec.js
index 034b7848f35..7cb1f49d4f7 100644
--- a/spec/frontend/runner/group_runners/group_runners_app_spec.js
+++ b/spec/frontend/runner/group_runners/group_runners_app_spec.js
@@ -1,15 +1,19 @@
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
import { GlLink } from '@gitlab/ui';
-import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import {
+ extendedWrapper,
+ shallowMountExtended,
+ mountExtended,
+} from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { updateHistory } from '~/lib/utils/url_utility';
+import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import RunnerList from '~/runner/components/runner_list.vue';
import RunnerStats from '~/runner/components/stat/runner_stats.vue';
@@ -22,6 +26,7 @@ import {
DEFAULT_SORT,
INSTANCE_TYPE,
GROUP_TYPE,
+ PROJECT_TYPE,
PARAM_KEY_STATUS,
STATUS_ACTIVE,
RUNNER_PAGE_SIZE,
@@ -33,8 +38,7 @@ import { captureException } from '~/runner/sentry_utils';
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import { groupRunnersData, groupRunnersDataPaginated, groupRunnersCountData } from '../mock_data';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
const mockGroupFullPath = 'group1';
const mockRegistrationToken = 'AABBCC';
@@ -54,6 +58,7 @@ describe('GroupRunnersApp', () => {
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown);
+ const findRunnerTypeTabs = () => wrapper.findComponent(RunnerTypeTabs);
const findRunnerList = () => wrapper.findComponent(RunnerList);
const findRunnerPagination = () => extendedWrapper(wrapper.findComponent(RunnerPagination));
const findRunnerPaginationPrev = () =>
@@ -62,14 +67,18 @@ describe('GroupRunnersApp', () => {
const findRunnerFilteredSearchBar = () => wrapper.findComponent(RunnerFilteredSearchBar);
const findFilteredSearch = () => wrapper.findComponent(FilteredSearch);
- const createComponent = ({ props = {}, mountFn = shallowMount } = {}) => {
+ const mockCountQueryResult = (count) =>
+ Promise.resolve({
+ data: { group: { id: groupRunnersCountData.data.group.id, runners: { count } } },
+ });
+
+ const createComponent = ({ props = {}, mountFn = shallowMountExtended } = {}) => {
const handlers = [
[getGroupRunnersQuery, mockGroupRunnersQuery],
[getGroupRunnersCountQuery, mockGroupRunnersCountQuery],
];
wrapper = mountFn(GroupRunnersApp, {
- localVue,
apolloProvider: createMockApollo(handlers),
propsData: {
registrationToken: mockRegistrationToken,
@@ -91,7 +100,7 @@ describe('GroupRunnersApp', () => {
});
it('shows total runner counts', async () => {
- createComponent({ mountFn: mount });
+ createComponent({ mountFn: mountExtended });
await waitForPromises();
@@ -102,6 +111,44 @@ describe('GroupRunnersApp', () => {
expect(stats).toMatch('Stale runners 2');
});
+ it('shows the runner tabs with a runner count for each type', async () => {
+ mockGroupRunnersCountQuery.mockImplementation(({ type }) => {
+ switch (type) {
+ case GROUP_TYPE:
+ return mockCountQueryResult(2);
+ case PROJECT_TYPE:
+ return mockCountQueryResult(1);
+ default:
+ return mockCountQueryResult(4);
+ }
+ });
+
+ createComponent({ mountFn: mountExtended });
+ await waitForPromises();
+
+ expect(findRunnerTypeTabs().text()).toMatchInterpolatedText('All 4 Group 2 Project 1');
+ });
+
+ it('shows the runner tabs with a formatted runner count', async () => {
+ mockGroupRunnersCountQuery.mockImplementation(({ type }) => {
+ switch (type) {
+ case GROUP_TYPE:
+ return mockCountQueryResult(2000);
+ case PROJECT_TYPE:
+ return mockCountQueryResult(1000);
+ default:
+ return mockCountQueryResult(3000);
+ }
+ });
+
+ createComponent({ mountFn: mountExtended });
+ await waitForPromises();
+
+ expect(findRunnerTypeTabs().text()).toMatchInterpolatedText(
+ 'All 3,000 Group 2,000 Project 1,000',
+ );
+ });
+
it('shows the runner setup instructions', () => {
expect(findRegistrationDropdown().props('registrationToken')).toBe(mockRegistrationToken);
expect(findRegistrationDropdown().props('type')).toBe(GROUP_TYPE);
@@ -116,7 +163,7 @@ describe('GroupRunnersApp', () => {
const { webUrl, node } = groupRunnersData.data.group.runners.edges[0];
const { id, shortSha } = node;
- createComponent({ mountFn: mount });
+ createComponent({ mountFn: mountExtended });
await waitForPromises();
@@ -136,7 +183,7 @@ describe('GroupRunnersApp', () => {
});
it('sets tokens in the filtered search', () => {
- createComponent({ mountFn: mount });
+ createComponent({ mountFn: mountExtended });
const tokens = findFilteredSearch().props('tokens');
@@ -215,11 +262,13 @@ describe('GroupRunnersApp', () => {
mockGroupRunnersQuery = jest.fn().mockResolvedValue({
data: {
group: {
+ id: '1',
runners: { nodes: [] },
},
},
});
createComponent();
+ await waitForPromises();
});
it('shows a message for no results', async () => {
@@ -228,9 +277,10 @@ describe('GroupRunnersApp', () => {
});
describe('when runners query fails', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mockGroupRunnersQuery = jest.fn().mockRejectedValue(new Error('Error!'));
createComponent();
+ await waitForPromises();
});
it('error is shown to the user', async () => {
@@ -239,17 +289,18 @@ describe('GroupRunnersApp', () => {
it('error is reported to sentry', async () => {
expect(captureException).toHaveBeenCalledWith({
- error: new Error('Network error: Error!'),
+ error: new Error('Error!'),
component: 'GroupRunnersApp',
});
});
});
describe('Pagination', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mockGroupRunnersQuery = jest.fn().mockResolvedValue(groupRunnersDataPaginated);
- createComponent({ mountFn: mount });
+ createComponent({ mountFn: mountExtended });
+ await waitForPromises();
});
it('more pages can be selected', () => {
diff --git a/spec/frontend/runner/mock_data.js b/spec/frontend/runner/mock_data.js
index 9c430e205ea..d80caa47752 100644
--- a/spec/frontend/runner/mock_data.js
+++ b/spec/frontend/runner/mock_data.js
@@ -5,6 +5,9 @@ import runnersData from 'test_fixtures/graphql/runner/get_runners.query.graphql.
import runnersCountData from 'test_fixtures/graphql/runner/get_runners_count.query.graphql.json';
import runnersDataPaginated from 'test_fixtures/graphql/runner/get_runners.query.graphql.paginated.json';
import runnerData from 'test_fixtures/graphql/runner/get_runner.query.graphql.json';
+import runnerWithGroupData from 'test_fixtures/graphql/runner/get_runner.query.graphql.with_group.json';
+import runnerProjectsData from 'test_fixtures/graphql/runner/get_runner_projects.query.graphql.json';
+import runnerJobsData from 'test_fixtures/graphql/runner/get_runner_jobs.query.graphql.json';
// Group queries
import groupRunnersData from 'test_fixtures/graphql/runner/get_group_runners.query.graphql.json';
@@ -12,10 +15,13 @@ import groupRunnersCountData from 'test_fixtures/graphql/runner/get_group_runner
import groupRunnersDataPaginated from 'test_fixtures/graphql/runner/get_group_runners.query.graphql.paginated.json';
export {
- runnerData,
+ runnersData,
runnersCountData,
runnersDataPaginated,
- runnersData,
+ runnerData,
+ runnerWithGroupData,
+ runnerProjectsData,
+ runnerJobsData,
groupRunnersData,
groupRunnersCountData,
groupRunnersDataPaginated,
diff --git a/spec/frontend/runner/utils_spec.js b/spec/frontend/runner/utils_spec.js
new file mode 100644
index 00000000000..3fa9784ecdf
--- /dev/null
+++ b/spec/frontend/runner/utils_spec.js
@@ -0,0 +1,65 @@
+import { formatJobCount, tableField, getPaginationVariables } from '~/runner/utils';
+
+describe('~/runner/utils', () => {
+ describe('formatJobCount', () => {
+ it('formats a number', () => {
+ expect(formatJobCount(1)).toBe('1');
+ expect(formatJobCount(99)).toBe('99');
+ });
+
+ it('formats a large count', () => {
+ expect(formatJobCount(1000)).toBe('1,000');
+ expect(formatJobCount(1001)).toBe('1,000+');
+ });
+
+ it('returns an empty string for non-numeric values', () => {
+ expect(formatJobCount(undefined)).toBe('');
+ expect(formatJobCount(null)).toBe('');
+ expect(formatJobCount('number')).toBe('');
+ });
+ });
+
+ describe('tableField', () => {
+ it('a field with options', () => {
+ expect(tableField({ key: 'name' })).toEqual({
+ key: 'name',
+ label: '',
+ tdAttr: { 'data-testid': 'td-name' },
+ thClass: expect.any(Array),
+ });
+ });
+
+ it('a field with a label', () => {
+ const label = 'A field name';
+
+ expect(tableField({ key: 'name', label })).toMatchObject({
+ label,
+ });
+ });
+
+ it('a field with custom classes', () => {
+ const mockClasses = ['foo', 'bar'];
+
+ expect(tableField({ thClasses: mockClasses })).toMatchObject({
+ thClass: expect.arrayContaining(mockClasses),
+ });
+ });
+ });
+
+ describe('getPaginationVariables', () => {
+ const after = 'AFTER_CURSOR';
+ const before = 'BEFORE_CURSOR';
+
+ it.each`
+ case | pagination | pageSize | variables
+ ${'next page'} | ${{ after }} | ${undefined} | ${{ after, first: 10 }}
+ ${'prev page'} | ${{ before }} | ${undefined} | ${{ before, last: 10 }}
+ ${'first page'} | ${{}} | ${undefined} | ${{ first: 10 }}
+ ${'next page with N items'} | ${{ after }} | ${20} | ${{ after, first: 20 }}
+ ${'prev page with N items'} | ${{ before }} | ${20} | ${{ before, last: 20 }}
+ ${'first page with N items'} | ${{}} | ${20} | ${{ first: 20 }}
+ `('navigates to $case', ({ pagination, pageSize, variables }) => {
+ expect(getPaginationVariables(pagination, pageSize)).toEqual(variables);
+ });
+ });
+});
diff --git a/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js b/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js
index 3713e1d414f..a377ddae0eb 100644
--- a/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/confidentiality_filter_spec.js
@@ -1,11 +1,11 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import { MOCK_QUERY } from 'jest/search/mock_data';
import ConfidentialityFilter from '~/search/sidebar/components/confidentiality_filter.vue';
import RadioFilter from '~/search/sidebar/components/radio_filter.vue';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('ConfidentialityFilter', () => {
let wrapper;
@@ -25,7 +25,6 @@ describe('ConfidentialityFilter', () => {
});
wrapper = shallowMount(ConfidentialityFilter, {
- localVue,
store,
});
};
diff --git a/spec/frontend/search/sidebar/components/radio_filter_spec.js b/spec/frontend/search/sidebar/components/radio_filter_spec.js
index 4c81312e479..39d5ee581ec 100644
--- a/spec/frontend/search/sidebar/components/radio_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/radio_filter_spec.js
@@ -1,13 +1,13 @@
import { GlFormRadioGroup, GlFormRadio } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import { MOCK_QUERY } from 'jest/search/mock_data';
import RadioFilter from '~/search/sidebar/components/radio_filter.vue';
import { confidentialFilterData } from '~/search/sidebar/constants/confidential_filter_data';
import { stateFilterData } from '~/search/sidebar/constants/state_filter_data';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('RadioFilter', () => {
let wrapper;
@@ -30,7 +30,6 @@ describe('RadioFilter', () => {
});
wrapper = shallowMount(RadioFilter, {
- localVue,
store,
propsData: {
...defaultProps,
diff --git a/spec/frontend/search/sidebar/components/status_filter_spec.js b/spec/frontend/search/sidebar/components/status_filter_spec.js
index 08ce57b206b..5d8ecd8733a 100644
--- a/spec/frontend/search/sidebar/components/status_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/status_filter_spec.js
@@ -1,11 +1,11 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import { MOCK_QUERY } from 'jest/search/mock_data';
import RadioFilter from '~/search/sidebar/components/radio_filter.vue';
import StatusFilter from '~/search/sidebar/components/status_filter.vue';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('StatusFilter', () => {
let wrapper;
@@ -25,7 +25,6 @@ describe('StatusFilter', () => {
});
wrapper = shallowMount(StatusFilter, {
- localVue,
store,
});
};
diff --git a/spec/frontend/search/sort/components/app_spec.js b/spec/frontend/search/sort/components/app_spec.js
index 5806d6b51d2..04520a3e704 100644
--- a/spec/frontend/search/sort/components/app_spec.js
+++ b/spec/frontend/search/sort/components/app_spec.js
@@ -1,12 +1,12 @@
import { GlButtonGroup, GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import { MOCK_QUERY, MOCK_SORT_OPTIONS } from 'jest/search/mock_data';
import GlobalSearchSort from '~/search/sort/components/app.vue';
import { SORT_DIRECTION_UI } from '~/search/sort/constants';
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('GlobalSearchSort', () => {
let wrapper;
@@ -30,7 +30,6 @@ describe('GlobalSearchSort', () => {
});
wrapper = shallowMount(GlobalSearchSort, {
- localVue,
store,
propsData: {
...defaultProps,
diff --git a/spec/frontend/security_configuration/components/app_spec.js b/spec/frontend/security_configuration/components/app_spec.js
index cbdf7f53913..963577fa763 100644
--- a/spec/frontend/security_configuration/components/app_spec.js
+++ b/spec/frontend/security_configuration/components/app_spec.js
@@ -32,7 +32,7 @@ const upgradePath = '/upgrade';
const autoDevopsHelpPagePath = '/autoDevopsHelpPagePath';
const autoDevopsPath = '/autoDevopsPath';
const gitlabCiHistoryPath = 'test/historyPath';
-const projectPath = 'namespace/project';
+const projectFullPath = 'namespace/project';
useLocalStorageSpy();
@@ -54,7 +54,7 @@ describe('App component', () => {
upgradePath,
autoDevopsHelpPagePath,
autoDevopsPath,
- projectPath,
+ projectFullPath,
glFeatures: {
secureVulnerabilityTraining,
},
@@ -274,11 +274,11 @@ describe('App component', () => {
describe('Auto DevOps enabled alert', () => {
describe.each`
- context | autoDevopsEnabled | localStorageValue | shouldRender
- ${'enabled'} | ${true} | ${null} | ${true}
- ${'enabled, alert dismissed on other project'} | ${true} | ${['foo/bar']} | ${true}
- ${'enabled, alert dismissed on this project'} | ${true} | ${[projectPath]} | ${false}
- ${'not enabled'} | ${false} | ${null} | ${false}
+ context | autoDevopsEnabled | localStorageValue | shouldRender
+ ${'enabled'} | ${true} | ${null} | ${true}
+ ${'enabled, alert dismissed on other project'} | ${true} | ${['foo/bar']} | ${true}
+ ${'enabled, alert dismissed on this project'} | ${true} | ${[projectFullPath]} | ${false}
+ ${'not enabled'} | ${false} | ${null} | ${false}
`('given Auto DevOps is $context', ({ autoDevopsEnabled, localStorageValue, shouldRender }) => {
beforeEach(() => {
if (localStorageValue !== null) {
@@ -302,11 +302,11 @@ describe('App component', () => {
describe('dismissing', () => {
describe.each`
- dismissedProjects | expectedWrittenValue
- ${null} | ${[projectPath]}
- ${[]} | ${[projectPath]}
- ${['foo/bar']} | ${['foo/bar', projectPath]}
- ${[projectPath]} | ${[projectPath]}
+ dismissedProjects | expectedWrittenValue
+ ${null} | ${[projectFullPath]}
+ ${[]} | ${[projectFullPath]}
+ ${['foo/bar']} | ${['foo/bar', projectFullPath]}
+ ${[projectFullPath]} | ${[projectFullPath]}
`(
'given dismissed projects $dismissedProjects',
({ dismissedProjects, expectedWrittenValue }) => {
diff --git a/spec/frontend/security_configuration/components/feature_card_spec.js b/spec/frontend/security_configuration/components/feature_card_spec.js
index 0eca2c27075..2b74be19480 100644
--- a/spec/frontend/security_configuration/components/feature_card_spec.js
+++ b/spec/frontend/security_configuration/components/feature_card_spec.js
@@ -113,7 +113,6 @@ describe('FeatureCard component', () => {
context | available | configured | expectedStatus
${'a configured feature'} | ${true} | ${true} | ${'Enabled'}
${'an unconfigured feature'} | ${true} | ${false} | ${'Not enabled'}
- ${'an available feature with unknown status'} | ${true} | ${undefined} | ${''}
${'an unavailable feature'} | ${false} | ${false} | ${'Available with Ultimate'}
${'an unavailable feature with unknown status'} | ${false} | ${undefined} | ${'Available with Ultimate'}
`('given $context', ({ available, configured, expectedStatus }) => {
diff --git a/spec/frontend/security_configuration/components/training_provider_list_spec.js b/spec/frontend/security_configuration/components/training_provider_list_spec.js
index 578248e696f..18c9ada6bde 100644
--- a/spec/frontend/security_configuration/components/training_provider_list_spec.js
+++ b/spec/frontend/security_configuration/components/training_provider_list_spec.js
@@ -1,14 +1,26 @@
+import * as Sentry from '@sentry/browser';
import { GlAlert, GlLink, GlToggle, GlCard, GlSkeletonLoader } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
+import {
+ TRACK_TOGGLE_TRAINING_PROVIDER_ACTION,
+ TRACK_TOGGLE_TRAINING_PROVIDER_LABEL,
+} from '~/security_configuration/constants';
import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue';
+import securityTrainingProvidersQuery from '~/security_configuration/graphql/security_training_providers.query.graphql';
import configureSecurityTrainingProvidersMutation from '~/security_configuration/graphql/configure_security_training_providers.mutation.graphql';
+import dismissUserCalloutMutation from '~/graphql_shared/mutations/dismiss_user_callout.mutation.graphql';
import waitForPromises from 'helpers/wait_for_promises';
import {
+ dismissUserCalloutResponse,
+ dismissUserCalloutErrorResponse,
securityTrainingProviders,
- createMockResolvers,
+ securityTrainingProvidersResponse,
+ updateSecurityTrainingProvidersResponse,
+ updateSecurityTrainingProvidersErrorResponse,
testProjectPath,
textProviderIds,
} from '../mock_data';
@@ -19,14 +31,28 @@ describe('TrainingProviderList component', () => {
let wrapper;
let apolloProvider;
- const createApolloProvider = ({ resolvers } = {}) => {
- apolloProvider = createMockApollo([], createMockResolvers({ resolvers }));
+ const createApolloProvider = ({ handlers = [] } = {}) => {
+ const defaultHandlers = [
+ [
+ securityTrainingProvidersQuery,
+ jest.fn().mockResolvedValue(securityTrainingProvidersResponse),
+ ],
+ [
+ configureSecurityTrainingProvidersMutation,
+ jest.fn().mockResolvedValue(updateSecurityTrainingProvidersResponse),
+ ],
+ ];
+
+ // make sure we don't have any duplicate handlers to avoid 'Request handler already defined for query` errors
+ const mergedHandlers = [...new Map([...defaultHandlers, ...handlers])];
+
+ apolloProvider = createMockApollo(mergedHandlers);
};
const createComponent = () => {
wrapper = shallowMount(TrainingProviderList, {
provide: {
- projectPath: testProjectPath,
+ projectFullPath: testProjectPath,
},
apolloProvider,
});
@@ -42,27 +68,49 @@ describe('TrainingProviderList component', () => {
const findLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findErrorAlert = () => wrapper.findComponent(GlAlert);
- const toggleFirstProvider = () => findFirstToggle().vm.$emit('change');
+ const toggleFirstProvider = () => findFirstToggle().vm.$emit('change', textProviderIds[0]);
afterEach(() => {
wrapper.destroy();
apolloProvider = null;
});
- describe('with a successful response', () => {
+ describe('when loading', () => {
beforeEach(() => {
- createApolloProvider();
+ const pendingHandler = () => new Promise(() => {});
+
+ createApolloProvider({
+ handlers: [[securityTrainingProvidersQuery, pendingHandler]],
+ });
createComponent();
});
- describe('when loading', () => {
- it('shows the loader', () => {
- expect(findLoader().exists()).toBe(true);
- });
+ it('shows the loader', () => {
+ expect(findLoader().exists()).toBe(true);
+ });
- it('does not show the cards', () => {
- expect(findCards().exists()).toBe(false);
+ it('does not show the cards', () => {
+ expect(findCards().exists()).toBe(false);
+ });
+ });
+
+ describe('with a successful response', () => {
+ beforeEach(() => {
+ createApolloProvider({
+ handlers: [
+ [dismissUserCalloutMutation, jest.fn().mockResolvedValue(dismissUserCalloutResponse)],
+ ],
+ resolvers: {
+ Mutation: {
+ configureSecurityTrainingProviders: () => ({
+ errors: [],
+ securityTrainingProviders: [],
+ }),
+ },
+ },
});
+
+ createComponent();
});
describe('basic structure', () => {
@@ -104,9 +152,9 @@ describe('TrainingProviderList component', () => {
beforeEach(async () => {
jest.spyOn(apolloProvider.defaultClient, 'mutate');
- await waitForMutationToBeLoaded();
+ await waitForQueryToBeLoaded();
- toggleFirstProvider();
+ await toggleFirstProvider();
});
it.each`
@@ -124,10 +172,78 @@ describe('TrainingProviderList component', () => {
expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith(
expect.objectContaining({
mutation: configureSecurityTrainingProvidersMutation,
- variables: { input: { enabledProviders: textProviderIds, fullPath: testProjectPath } },
+ variables: {
+ input: {
+ providerId: textProviderIds[0],
+ isEnabled: true,
+ isPrimary: false,
+ projectPath: testProjectPath,
+ },
+ },
}),
);
});
+
+ it('dismisses the callout when the feature gets first enabled', async () => {
+ // wait for configuration update mutation to complete
+ await waitForMutationToBeLoaded();
+
+ // both the config and dismiss mutations have been called
+ expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledTimes(2);
+ expect(apolloProvider.defaultClient.mutate).toHaveBeenNthCalledWith(
+ 2,
+ expect.objectContaining({
+ mutation: dismissUserCalloutMutation,
+ variables: {
+ input: {
+ featureName: 'security_training_feature_promotion',
+ },
+ },
+ }),
+ );
+
+ toggleFirstProvider();
+ await waitForMutationToBeLoaded();
+
+ // the config mutation has been called again but not the dismiss mutation
+ expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledTimes(3);
+ expect(apolloProvider.defaultClient.mutate).toHaveBeenNthCalledWith(
+ 3,
+ expect.objectContaining({
+ mutation: configureSecurityTrainingProvidersMutation,
+ }),
+ );
+ });
+ });
+
+ describe('metrics', () => {
+ let trackingSpy;
+
+ beforeEach(() => {
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ });
+
+ afterEach(() => {
+ unmockTracking();
+ });
+
+ it('tracks when a provider gets toggled', () => {
+ expect(trackingSpy).not.toHaveBeenCalled();
+
+ toggleFirstProvider();
+
+ // Note: Ideally we also want to test that the tracking event is called correctly when a
+ // provider gets disabled, but that's a bit tricky to do with the current implementation
+ // Once https://gitlab.com/gitlab-org/gitlab/-/issues/348985 and https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79492
+ // are merged this will be much easer to do and should be tackled then.
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, TRACK_TOGGLE_TRAINING_PROVIDER_ACTION, {
+ property: securityTrainingProviders[0].id,
+ label: TRACK_TOGGLE_TRAINING_PROVIDER_LABEL,
+ extra: {
+ providerIsEnabled: true,
+ },
+ });
+ });
});
});
@@ -142,11 +258,7 @@ describe('TrainingProviderList component', () => {
describe('when fetching training providers', () => {
beforeEach(async () => {
createApolloProvider({
- resolvers: {
- Query: {
- securityTrainingProviders: jest.fn().mockReturnValue(new Error()),
- },
- },
+ handlers: [[securityTrainingProvidersQuery, jest.fn().mockRejectedValue()]],
});
createComponent();
@@ -165,10 +277,43 @@ describe('TrainingProviderList component', () => {
describe('when storing training provider configurations', () => {
beforeEach(async () => {
createApolloProvider({
+ handlers: [
+ [
+ configureSecurityTrainingProvidersMutation,
+ jest.fn().mockReturnValue(updateSecurityTrainingProvidersErrorResponse),
+ ],
+ ],
+ });
+ createComponent();
+
+ await waitForQueryToBeLoaded();
+ toggleFirstProvider();
+ await waitForMutationToBeLoaded();
+ });
+
+ it('shows an non-dismissible error alert', () => {
+ expectErrorAlertToExist();
+ });
+
+ it('shows an error description', () => {
+ expect(findErrorAlert().text()).toBe(TrainingProviderList.i18n.configMutationErrorMessage);
+ });
+ });
+
+ describe.each`
+ errorType | mutationHandler
+ ${'backend error'} | ${jest.fn().mockReturnValue(dismissUserCalloutErrorResponse)}
+ ${'network error'} | ${jest.fn().mockRejectedValue()}
+ `('when dismissing the callout and a "$errorType" happens', ({ mutationHandler }) => {
+ beforeEach(async () => {
+ jest.spyOn(Sentry, 'captureException').mockImplementation();
+
+ createApolloProvider({
+ handlers: [[dismissUserCalloutMutation, mutationHandler]],
resolvers: {
Mutation: {
configureSecurityTrainingProviders: () => ({
- errors: ['something went wrong!'],
+ errors: [],
securityTrainingProviders: [],
}),
},
@@ -178,15 +323,14 @@ describe('TrainingProviderList component', () => {
await waitForQueryToBeLoaded();
toggleFirstProvider();
- await waitForMutationToBeLoaded();
});
- it('shows an non-dismissible error alert', () => {
- expectErrorAlertToExist();
- });
+ it('logs the error to sentry', async () => {
+ expect(Sentry.captureException).not.toHaveBeenCalled();
- it('shows an error description', () => {
- expect(findErrorAlert().text()).toBe(TrainingProviderList.i18n.configMutationErrorMessage);
+ await waitForMutationToBeLoaded();
+
+ expect(Sentry.captureException).toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/security_configuration/components/upgrade_banner_spec.js b/spec/frontend/security_configuration/components/upgrade_banner_spec.js
index a35fded72fb..ff44acfc4f9 100644
--- a/spec/frontend/security_configuration/components/upgrade_banner_spec.js
+++ b/spec/frontend/security_configuration/components/upgrade_banner_spec.js
@@ -1,15 +1,22 @@
import { GlBanner } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import UpgradeBanner from '~/security_configuration/components/upgrade_banner.vue';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
+import UpgradeBanner, {
+ SECURITY_UPGRADE_BANNER,
+ UPGRADE_OR_FREE_TRIAL,
+} from '~/security_configuration/components/upgrade_banner.vue';
const upgradePath = '/upgrade';
describe('UpgradeBanner component', () => {
let wrapper;
let closeSpy;
+ let primarySpy;
+ let trackingSpy;
const createComponent = (propsData) => {
closeSpy = jest.fn();
+ primarySpy = jest.fn();
wrapper = shallowMountExtended(UpgradeBanner, {
provide: {
@@ -18,43 +25,83 @@ describe('UpgradeBanner component', () => {
propsData,
listeners: {
close: closeSpy,
+ primary: primarySpy,
},
});
};
const findGlBanner = () => wrapper.findComponent(GlBanner);
+ const expectTracking = (action, label) => {
+ return expect(trackingSpy).toHaveBeenCalledWith(undefined, action, {
+ label,
+ property: SECURITY_UPGRADE_BANNER,
+ });
+ };
+
beforeEach(() => {
- createComponent();
+ trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
});
afterEach(() => {
wrapper.destroy();
+ unmockTracking();
});
- it('passes the expected props to GlBanner', () => {
- expect(findGlBanner().props()).toMatchObject({
- title: UpgradeBanner.i18n.title,
- buttonText: UpgradeBanner.i18n.buttonText,
- buttonLink: upgradePath,
+ describe('when the component renders', () => {
+ it('tracks an event', () => {
+ expect(trackingSpy).not.toHaveBeenCalled();
+
+ createComponent();
+
+ expectTracking('render', SECURITY_UPGRADE_BANNER);
});
});
- it('renders the list of benefits', () => {
- const wrapperText = wrapper.text();
+ describe('when ready', () => {
+ beforeEach(() => {
+ createComponent();
+ trackingSpy.mockClear();
+ });
- expect(wrapperText).toContain('Immediately begin risk analysis and remediation');
- expect(wrapperText).toContain('statistics in the merge request');
- expect(wrapperText).toContain('statistics across projects');
- expect(wrapperText).toContain('Runtime security metrics');
- expect(wrapperText).toContain('More scan types, including Container Scanning,');
- });
+ it('passes the expected props to GlBanner', () => {
+ expect(findGlBanner().props()).toMatchObject({
+ title: UpgradeBanner.i18n.title,
+ buttonText: UpgradeBanner.i18n.buttonText,
+ buttonLink: upgradePath,
+ });
+ });
- it(`re-emits GlBanner's close event`, () => {
- expect(closeSpy).not.toHaveBeenCalled();
+ it('renders the list of benefits', () => {
+ const wrapperText = wrapper.text();
- wrapper.findComponent(GlBanner).vm.$emit('close');
+ expect(wrapperText).toContain('Immediately begin risk analysis and remediation');
+ expect(wrapperText).toContain('statistics in the merge request');
+ expect(wrapperText).toContain('statistics across projects');
+ expect(wrapperText).toContain('Runtime security metrics');
+ expect(wrapperText).toContain('More scan types, including Container Scanning,');
+ });
+
+ describe('when user interacts', () => {
+ it(`re-emits GlBanner's close event & tracks an event`, () => {
+ expect(closeSpy).not.toHaveBeenCalled();
+ expect(trackingSpy).not.toHaveBeenCalled();
+
+ wrapper.findComponent(GlBanner).vm.$emit('close');
+
+ expect(closeSpy).toHaveBeenCalledTimes(1);
+ expectTracking('dismiss_banner', SECURITY_UPGRADE_BANNER);
+ });
- expect(closeSpy).toHaveBeenCalledTimes(1);
+ it(`re-emits GlBanner's primary event & tracks an event`, () => {
+ expect(primarySpy).not.toHaveBeenCalled();
+ expect(trackingSpy).not.toHaveBeenCalled();
+
+ wrapper.findComponent(GlBanner).vm.$emit('primary');
+
+ expect(primarySpy).toHaveBeenCalledTimes(1);
+ expectTracking('click_button', UPGRADE_OR_FREE_TRIAL);
+ });
+ });
});
});
diff --git a/spec/frontend/security_configuration/mock_data.js b/spec/frontend/security_configuration/mock_data.js
index 37ecce3886d..b042e870467 100644
--- a/spec/frontend/security_configuration/mock_data.js
+++ b/spec/frontend/security_configuration/mock_data.js
@@ -9,6 +9,7 @@ export const securityTrainingProviders = [
description: 'Interactive developer security education',
url: 'https://www.example.org/security/training',
isEnabled: false,
+ isPrimary: false,
},
{
id: textProviderIds[1],
@@ -16,24 +17,62 @@ export const securityTrainingProviders = [
description: 'Security training with guide and learning pathways.',
url: 'https://www.vendornametwo.com/',
isEnabled: true,
+ isPrimary: false,
},
];
export const securityTrainingProvidersResponse = {
data: {
- securityTrainingProviders,
+ project: {
+ id: 1,
+ securityTrainingProviders,
+ },
+ },
+};
+
+export const dismissUserCalloutResponse = {
+ data: {
+ userCalloutCreate: {
+ errors: [],
+ userCallout: {
+ dismissedAt: '2022-02-02T04:36:57Z',
+ featureName: 'SECURITY_TRAINING_FEATURE_PROMOTION',
+ },
+ },
+ },
+};
+
+export const dismissUserCalloutErrorResponse = {
+ data: {
+ userCalloutCreate: {
+ errors: ['Something went wrong'],
+ userCallout: {
+ dismissedAt: '',
+ featureName: 'SECURITY_TRAINING_FEATURE_PROMOTION',
+ },
+ },
},
};
-const defaultMockResolvers = {
- Query: {
- securityTrainingProviders() {
- return securityTrainingProviders;
+export const updateSecurityTrainingProvidersResponse = {
+ data: {
+ securityTrainingUpdate: {
+ errors: [],
+ training: {
+ id: 101,
+ name: 'Acme',
+ isEnabled: true,
+ isPrimary: false,
+ },
},
},
};
-export const createMockResolvers = ({ resolvers: customMockResolvers = {} } = {}) => ({
- ...defaultMockResolvers,
- ...customMockResolvers,
-});
+export const updateSecurityTrainingProvidersErrorResponse = {
+ data: {
+ securityTrainingUpdate: {
+ errors: ['something went wrong!'],
+ training: null,
+ },
+ },
+};
diff --git a/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap b/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap
index 350055cb935..f57b9418be5 100644
--- a/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap
+++ b/spec/frontend/serverless/components/__snapshots__/empty_state_spec.js.snap
@@ -10,7 +10,7 @@ exports[`EmptyStateComponent should render content 1`] = `
<h1 class=\\"gl-font-size-h-display gl-line-height-36 h4\\">
Getting started with serverless
</h1>
- <p class=\\"gl-mt-3\\">In order to start using functions as a service, you must first install Knative on your Kubernetes cluster. <gl-link-stub href=\\"/help\\">More information</gl-link-stub>
+ <p class=\\"gl-mt-3\\">Serverless was <gl-link-stub target=\\"_blank\\" href=\\"https://about.gitlab.com/releases/2021/09/22/gitlab-14-3-released/#gitlab-serverless\\">deprecated</gl-link-stub>. But if you opt to use it, you must install Knative in your Kubernetes cluster first. <gl-link-stub href=\\"/help\\">Learn more.</gl-link-stub>
</p>
<div class=\\"gl-display-flex gl-flex-wrap gl-justify-content-center\\">
<!---->
diff --git a/spec/frontend/serverless/components/function_details_spec.js b/spec/frontend/serverless/components/function_details_spec.js
index d2b8de71e01..0c9b2498589 100644
--- a/spec/frontend/serverless/components/function_details_spec.js
+++ b/spec/frontend/serverless/components/function_details_spec.js
@@ -1,17 +1,16 @@
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import Vuex from 'vuex';
import functionDetailsComponent from '~/serverless/components/function_details.vue';
import { createStore } from '~/serverless/store';
describe('functionDetailsComponent', () => {
- let localVue;
let component;
let store;
beforeEach(() => {
- localVue = createLocalVue();
- localVue.use(Vuex);
+ Vue.use(Vuex);
store = createStore({ clustersPath: '/clusters', helpPath: '/help' });
});
@@ -33,7 +32,6 @@ describe('functionDetailsComponent', () => {
it('has a name, description, URL, and no pods loaded', () => {
component = shallowMount(functionDetailsComponent, {
- localVue,
store,
propsData: {
func: serviceStub,
@@ -58,7 +56,6 @@ describe('functionDetailsComponent', () => {
serviceStub.podcount = 1;
component = shallowMount(functionDetailsComponent, {
- localVue,
store,
propsData: {
func: serviceStub,
@@ -73,7 +70,6 @@ describe('functionDetailsComponent', () => {
serviceStub.podcount = 3;
component = shallowMount(functionDetailsComponent, {
- localVue,
store,
propsData: {
func: serviceStub,
@@ -88,7 +84,6 @@ describe('functionDetailsComponent', () => {
serviceStub.description = null;
component = shallowMount(functionDetailsComponent, {
- localVue,
store,
propsData: {
func: serviceStub,
diff --git a/spec/frontend/serverless/components/functions_spec.js b/spec/frontend/serverless/components/functions_spec.js
index 01dd512c5d3..846fd63e918 100644
--- a/spec/frontend/serverless/components/functions_spec.js
+++ b/spec/frontend/serverless/components/functions_spec.js
@@ -1,5 +1,6 @@
-import { GlLoadingIcon } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon, GlAlert, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import AxiosMockAdapter from 'axios-mock-adapter';
import Vuex from 'vuex';
import { TEST_HOST } from 'helpers/test_constants';
@@ -15,17 +16,16 @@ describe('functionsComponent', () => {
let component;
let store;
- let localVue;
let axiosMock;
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
axiosMock.onGet(statusPath).reply(200);
- localVue = createLocalVue();
- localVue.use(Vuex);
+ Vue.use(Vuex);
store = createStore({});
+ component = shallowMount(functionsComponent, { store, stubs: { GlSprintf } });
});
afterEach(() => {
@@ -33,23 +33,26 @@ describe('functionsComponent', () => {
axiosMock.restore();
});
- it('should render empty state when Knative is not installed', () => {
- store.dispatch('receiveFunctionsSuccess', { knative_installed: false });
- component = shallowMount(functionsComponent, { localVue, store });
+ it('should render deprecation notice', () => {
+ expect(component.findComponent(GlAlert).text()).toBe(
+ 'Serverless was deprecated in GitLab 14.3.',
+ );
+ });
+
+ it('should render empty state when Knative is not installed', async () => {
+ await store.dispatch('receiveFunctionsSuccess', { knative_installed: false });
- expect(component.find(EmptyState).exists()).toBe(true);
+ expect(component.findComponent(EmptyState).exists()).toBe(true);
});
- it('should render a loading component', () => {
- store.dispatch('requestFunctionsLoading');
- component = shallowMount(functionsComponent, { localVue, store });
+ it('should render a loading component', async () => {
+ await store.dispatch('requestFunctionsLoading');
- expect(component.find(GlLoadingIcon).exists()).toBe(true);
+ expect(component.findComponent(GlLoadingIcon).exists()).toBe(true);
});
- it('should render empty state when there is no function data', () => {
- store.dispatch('receiveFunctionsNoDataSuccess', { knative_installed: true });
- component = shallowMount(functionsComponent, { localVue, store });
+ it('should render empty state when there is no function data', async () => {
+ await store.dispatch('receiveFunctionsNoDataSuccess', { knative_installed: true });
expect(
component.vm.$el
@@ -62,27 +65,22 @@ describe('functionsComponent', () => {
);
});
- it('should render functions and a loader when functions are partially fetched', () => {
- store.dispatch('receiveFunctionsPartial', {
+ it('should render functions and a loader when functions are partially fetched', async () => {
+ await store.dispatch('receiveFunctionsPartial', {
...mockServerlessFunctions,
knative_installed: 'checking',
});
- component = shallowMount(functionsComponent, { localVue, store });
-
expect(component.find('.js-functions-wrapper').exists()).toBe(true);
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, { localVue, 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.findComponent(EnvironmentRow).exists()).toBe(true);
});
});
diff --git a/spec/frontend/serverless/survey_banner_spec.js b/spec/frontend/serverless/survey_banner_spec.js
deleted file mode 100644
index 4682c2328c3..00000000000
--- a/spec/frontend/serverless/survey_banner_spec.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import { GlBanner } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import Cookies from 'js-cookie';
-import SurveyBanner from '~/serverless/survey_banner.vue';
-
-describe('Knative survey banner', () => {
- let wrapper;
-
- function mountBanner() {
- wrapper = shallowMount(SurveyBanner, {
- propsData: {
- surveyUrl: 'http://somesurvey.com/',
- },
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- it('should render the banner when the cookie is absent', () => {
- jest.spyOn(Cookies, 'get').mockReturnValue(undefined);
- mountBanner();
-
- expect(Cookies.get).toHaveBeenCalled();
- expect(wrapper.find(GlBanner).exists()).toBe(true);
- });
-
- it('should close the banner and set a cookie when close button is clicked', () => {
- jest.spyOn(Cookies, 'get').mockReturnValue(undefined);
- jest.spyOn(Cookies, 'set');
- mountBanner();
-
- expect(wrapper.find(GlBanner).exists()).toBe(true);
- wrapper.find(GlBanner).vm.$emit('close');
-
- return wrapper.vm.$nextTick().then(() => {
- expect(Cookies.set).toHaveBeenCalledWith('hide_serverless_survey', 'true', { expires: 3650 });
- expect(wrapper.find(GlBanner).exists()).toBe(false);
- });
- });
-
- it('should not render the banner when the cookie is set', () => {
- jest.spyOn(Cookies, 'get').mockReturnValue('true');
- mountBanner();
-
- expect(Cookies.get).toHaveBeenCalled();
- expect(wrapper.find(GlBanner).exists()).toBe(false);
- });
-});
diff --git a/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js b/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js
index 0c6ed998747..c105810e11c 100644
--- a/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js
+++ b/spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js
@@ -1,5 +1,6 @@
import { GlModal, GlFormCheckbox } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { initEmojiMock, clearEmojiMock } from 'helpers/emoji';
import * as UserApi from '~/api/user_api';
import EmojiPicker from '~/emoji/components/picker.vue';
@@ -48,7 +49,7 @@ describe('SetStatusModalWrapper', () => {
const findAvailabilityCheckbox = () => wrapper.find(GlFormCheckbox);
const findClearStatusAtMessage = () => wrapper.find('[data-testid="clear-status-at-message"]');
- const initModal = ({ mockOnUpdateSuccess = true, mockOnUpdateFailure = true } = {}) => {
+ const initModal = async ({ mockOnUpdateSuccess = true, mockOnUpdateFailure = true } = {}) => {
const modal = findModal();
// mock internal emoji methods
wrapper.vm.showEmojiMenu = jest.fn();
@@ -57,7 +58,7 @@ describe('SetStatusModalWrapper', () => {
if (mockOnUpdateFailure) wrapper.vm.onUpdateFail = jest.fn();
modal.vm.$emit('shown');
- return wrapper.vm.$nextTick();
+ await nextTick();
};
afterEach(() => {
@@ -207,7 +208,7 @@ describe('SetStatusModalWrapper', () => {
it('clicking "removeStatus" clears the emoji and message fields', async () => {
findModal().vm.$emit('secondary');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findFormField('message').element.value).toBe('');
expect(findFormField('emoji').element.value).toBe('');
@@ -215,7 +216,7 @@ describe('SetStatusModalWrapper', () => {
it('clicking "setStatus" submits the user status', async () => {
findModal().vm.$emit('primary');
- await wrapper.vm.$nextTick();
+ await nextTick();
// set the availability status
findAvailabilityCheckbox().vm.$emit('input', true);
@@ -224,7 +225,7 @@ describe('SetStatusModalWrapper', () => {
wrapper.find('[data-testid="thirtyMinutes"]').vm.$emit('click');
findModal().vm.$emit('primary');
- await wrapper.vm.$nextTick();
+ await nextTick();
const commonParams = {
emoji: defaultEmoji,
@@ -246,7 +247,7 @@ describe('SetStatusModalWrapper', () => {
it('calls the "onUpdateSuccess" handler', async () => {
findModal().vm.$emit('primary');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.onUpdateSuccess).toHaveBeenCalled();
});
@@ -262,7 +263,7 @@ describe('SetStatusModalWrapper', () => {
it('displays a toast success message', async () => {
findModal().vm.$emit('primary');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect($toast.show).toHaveBeenCalledWith('Status updated');
});
@@ -279,7 +280,7 @@ describe('SetStatusModalWrapper', () => {
it('calls the "onUpdateFail" handler', async () => {
findModal().vm.$emit('primary');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.onUpdateFail).toHaveBeenCalled();
});
@@ -295,7 +296,7 @@ describe('SetStatusModalWrapper', () => {
it('flashes an error message', async () => {
findModal().vm.$emit('primary');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(createFlash).toHaveBeenCalledWith({
message: "Sorry, we weren't able to set your status. Please try again later.",
diff --git a/spec/frontend/settings_panels_spec.js b/spec/frontend/settings_panels_spec.js
index 6b739617b97..3a62cd703ab 100644
--- a/spec/frontend/settings_panels_spec.js
+++ b/spec/frontend/settings_panels_spec.js
@@ -24,6 +24,20 @@ describe('Settings Panels', () => {
expect(isExpanded(panel)).toBe(true);
});
+
+ it('should expand panel containing linked hash', () => {
+ window.location.hash = '#group_description';
+
+ const panel = document.querySelector('#js-general-settings');
+ // Our test environment automatically expands everything so we need to clear that out first
+ panel.classList.remove('expanded');
+
+ expect(isExpanded(panel)).toBe(false);
+
+ initSettingsPanels();
+
+ expect(isExpanded(panel)).toBe(true);
+ });
});
it('does not change the text content of triggers', () => {
diff --git a/spec/frontend/sidebar/assignees_realtime_spec.js b/spec/frontend/sidebar/assignees_realtime_spec.js
index ecf33d6de37..2249a1c08b8 100644
--- a/spec/frontend/sidebar/assignees_realtime_spec.js
+++ b/spec/frontend/sidebar/assignees_realtime_spec.js
@@ -1,4 +1,5 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import AssigneesRealtime from '~/sidebar/components/assignees/assignees_realtime.vue';
@@ -7,8 +8,7 @@ import SidebarMediator from '~/sidebar/sidebar_mediator';
import getIssueAssigneesQuery from '~/vue_shared/components/sidebar/queries/get_issue_assignees.query.graphql';
import Mock, { issuableQueryResponse, subscriptionNullResponse } from './mock_data';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
describe('Assignees Realtime', () => {
let wrapper;
@@ -38,7 +38,6 @@ describe('Assignees Realtime', () => {
mediator,
},
apolloProvider: fakeApollo,
- localVue,
});
};
diff --git a/spec/frontend/sidebar/assignees_spec.js b/spec/frontend/sidebar/assignees_spec.js
index b3a67f18f82..a4474ead956 100644
--- a/spec/frontend/sidebar/assignees_spec.js
+++ b/spec/frontend/sidebar/assignees_spec.js
@@ -1,5 +1,6 @@
import { GlIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { trimText } from 'helpers/text_helper';
import UsersMockHelper from 'helpers/user_mock_data_helper';
import Assignee from '~/sidebar/components/assignees/assignees.vue';
@@ -59,7 +60,7 @@ describe('Assignee component', () => {
expect(componentTextNoUsers).toContain('assign yourself');
});
- it('emits the assign-self event when "assign yourself" is clicked', () => {
+ it('emits the assign-self event when "assign yourself" is clicked', async () => {
createWrapper({
...getDefaultProps(),
editable: true,
@@ -68,9 +69,8 @@ describe('Assignee component', () => {
jest.spyOn(wrapper.vm, '$emit');
wrapper.find('[data-testid="assign-yourself"]').trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('assign-self')).toBeTruthy();
- });
+ await nextTick();
+ expect(wrapper.emitted('assign-self')).toBeTruthy();
});
});
diff --git a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
index 07da4acef8c..def46255994 100644
--- a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
+++ b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
@@ -1,6 +1,7 @@
import { GlSearchBoxByType, GlDropdown } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -23,8 +24,7 @@ const updateIssueAssigneesMutationSuccess = jest
.mockResolvedValue(updateIssueAssigneesMutationResponse);
const mockError = jest.fn().mockRejectedValue('Error!');
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
const initialAssignees = [
{
@@ -59,7 +59,6 @@ describe('Sidebar assignees widget', () => {
[updateIssueAssigneesMutation, updateIssueAssigneesMutationHandler],
]);
wrapper = shallowMount(SidebarAssigneesWidget, {
- localVue,
apolloProvider: fakeApollo,
propsData: {
iid: '1',
@@ -138,9 +137,17 @@ describe('Sidebar assignees widget', () => {
createComponent();
await waitForPromises();
- expect(findAssignees().props('users')).toEqual(
- issuableQueryResponse.data.workspace.issuable.assignees.nodes,
- );
+ expect(findAssignees().props('users')).toEqual([
+ {
+ id: 'gid://gitlab/User/2',
+ avatarUrl:
+ 'https://www.gravatar.com/avatar/a95e5b71488f4b9d69ce5ff58bfd28d6?s=80\u0026d=identicon',
+ name: 'Jacki Kub',
+ username: 'francina.skiles',
+ webUrl: '/franc',
+ status: null,
+ },
+ ]);
});
it('renders an error when issuable query is rejected', async () => {
@@ -196,7 +203,7 @@ describe('Sidebar assignees widget', () => {
{
assignees: [
{
- __typename: 'User',
+ __typename: 'UserCore',
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
id: 'gid://gitlab/User/1',
@@ -322,9 +329,10 @@ describe('Sidebar assignees widget', () => {
});
describe('when user is not signed in', () => {
- beforeEach(() => {
+ beforeEach(async () => {
gon.current_username = undefined;
createComponent();
+ await waitForPromises();
});
it('passes signedIn prop as false to IssuableAssignees', () => {
@@ -353,6 +361,7 @@ describe('Sidebar assignees widget', () => {
describe('when making changes to participants list', () => {
beforeEach(async () => {
createComponent();
+ await waitForPromises();
});
it('passes falsy `isDirty` prop to editable item if no changes to selected users were made', () => {
diff --git a/spec/frontend/sidebar/components/assignees/sidebar_editable_item_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_editable_item_spec.js
index 84b192aaf41..c870bbecd76 100644
--- a/spec/frontend/sidebar/components/assignees/sidebar_editable_item_spec.js
+++ b/spec/frontend/sidebar/components/assignees/sidebar_editable_item_spec.js
@@ -1,5 +1,6 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
describe('boards sidebar remove issue', () => {
@@ -71,7 +72,7 @@ describe('boards sidebar remove issue', () => {
createComponent({ canUpdate: true, slots });
findEditButton().vm.$emit('click');
- await wrapper.vm.$nextTick;
+ await nextTick;
expect(findCollapsed().isVisible()).toBe(false);
expect(findExpanded().isVisible()).toBe(true);
@@ -82,14 +83,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);
@@ -101,7 +102,7 @@ describe('boards sidebar remove issue', () => {
findEditButton().vm.$emit('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted().open.length).toBe(1);
});
@@ -111,7 +112,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/sidebar/components/assignees/uncollapsed_assignee_list_spec.js b/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js
index c72c23a3a60..90aae85e1ca 100644
--- a/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js
+++ b/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import UsersMockHelper from 'helpers/user_mock_data_helper';
import AssigneeAvatarLink from '~/sidebar/components/assignees/assignee_avatar_link.vue';
@@ -84,10 +85,10 @@ describe('UncollapsedAssigneeList component', () => {
});
describe('when more button is clicked', () => {
- beforeEach(() => {
+ beforeEach(async () => {
findMoreButton().trigger('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('shows "show less" label', () => {
diff --git a/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js b/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js
index 707215d0739..1de71e52264 100644
--- a/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js
+++ b/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js
@@ -1,5 +1,6 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -15,8 +16,7 @@ import { issueConfidentialityResponse } from '../../mock_data';
jest.mock('~/flash');
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
describe('Sidebar Confidentiality Widget', () => {
let wrapper;
@@ -32,7 +32,6 @@ describe('Sidebar Confidentiality Widget', () => {
fakeApollo = createMockApollo([[issueConfidentialQuery, confidentialQueryHandler]]);
wrapper = shallowMount(SidebarConfidentialityWidget, {
- localVue,
apolloProvider: fakeApollo,
provide: {
canUpdate: true,
diff --git a/spec/frontend/sidebar/components/mock_data.js b/spec/frontend/sidebar/components/mock_data.js
index 70c3f8a3012..a9a00b3cfdf 100644
--- a/spec/frontend/sidebar/components/mock_data.js
+++ b/spec/frontend/sidebar/components/mock_data.js
@@ -1,6 +1,7 @@
export const getIssueCrmContactsQueryResponse = {
data: {
issue: {
+ __typename: 'Issue',
id: 'gid://gitlab/Issue/123',
customerRelationsContacts: {
nodes: [
@@ -37,6 +38,7 @@ export const issueCrmContactsUpdateNullResponse = {
export const issueCrmContactsUpdateResponse = {
data: {
issueCrmContactsUpdated: {
+ __typename: 'Issue',
id: 'gid://gitlab/Issue/123',
customerRelationsContacts: {
nodes: [
diff --git a/spec/frontend/sidebar/components/participants/sidebar_participants_widget_spec.js b/spec/frontend/sidebar/components/participants/sidebar_participants_widget_spec.js
index 859e63b3df6..338ecf944f3 100644
--- a/spec/frontend/sidebar/components/participants/sidebar_participants_widget_spec.js
+++ b/spec/frontend/sidebar/components/participants/sidebar_participants_widget_spec.js
@@ -1,6 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
+import { stripTypenames } from 'helpers/graphql_helpers';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import Participants from '~/sidebar/components/participants/participants.vue';
@@ -66,9 +67,11 @@ describe('Sidebar Participants Widget', () => {
});
it('passes participants to child component', () => {
- expect(findParticipants().props('participants')).toEqual(
+ const participantsWithoutTypename = stripTypenames(
epicParticipantsResponse().data.workspace.issuable.participants.nodes,
);
+
+ expect(findParticipants().props('participants')).toEqual(participantsWithoutTypename);
});
});
diff --git a/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js b/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js
index 6116bc68927..5d80a221d8e 100644
--- a/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js
+++ b/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js
@@ -97,14 +97,14 @@ describe('SidebarSeverity', () => {
});
});
- it('shows error alert when severity update fails ', () => {
+ it('shows error alert when severity update fails ', async () => {
const errorMsg = 'Something went wrong';
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValueOnce(errorMsg);
findCriticalSeverityDropdownItem().vm.$emit('click');
- setImmediate(() => {
- expect(createFlash).toHaveBeenCalled();
- });
+ await waitForPromises();
+
+ expect(createFlash).toHaveBeenCalled();
});
it('shows loading icon while updating', async () => {
diff --git a/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js b/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js
index d7471d99477..3ddd41c0bd4 100644
--- a/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js
+++ b/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js
@@ -8,9 +8,9 @@ import {
GlLoadingIcon,
} from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
-import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
+import { shallowMount, mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-
import createMockApollo from 'helpers/mock_apollo_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
@@ -37,8 +37,6 @@ import {
jest.mock('~/flash');
-const localVue = createLocalVue();
-
describe('SidebarDropdownWidget', () => {
let wrapper;
let mockApollo;
@@ -78,7 +76,7 @@ describe('SidebarDropdownWidget', () => {
// It then emits `shown` event in a watcher for `visible`
// Hence we need both of these:
await waitForPromises();
- await wrapper.vm.$nextTick();
+ await nextTick();
};
const waitForApollo = async () => {
@@ -108,7 +106,7 @@ describe('SidebarDropdownWidget', () => {
projectMilestonesSpy = jest.fn().mockResolvedValue(mockProjectMilestonesResponse),
currentMilestoneSpy = jest.fn().mockResolvedValue(noCurrentMilestoneResponse),
} = {}) => {
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
mockApollo = createMockApollo([
[projectMilestonesQuery, projectMilestonesSpy],
[projectIssueMilestoneQuery, currentMilestoneSpy],
@@ -117,7 +115,6 @@ describe('SidebarDropdownWidget', () => {
wrapper = extendedWrapper(
mount(SidebarDropdownWidget, {
- localVue,
provide: { canUpdate: true },
apolloProvider: mockApollo,
propsData: {
@@ -354,7 +351,7 @@ describe('SidebarDropdownWidget', () => {
});
it(`calls createFlash with "${expectedMsg}"`, async () => {
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(createFlash).toHaveBeenCalledWith({
message: expectedMsg,
captureError: true,
@@ -377,7 +374,7 @@ describe('SidebarDropdownWidget', () => {
findSearchBox().vm.$emit('input', 'non existing milestones');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findDropdownText().text()).toBe('No milestone found');
});
@@ -482,7 +479,7 @@ describe('SidebarDropdownWidget', () => {
it('sends a projectMilestones query with the entered search term "foo"', async () => {
findSearchBox().vm.$emit('input', mockSearchTerm);
- await wrapper.vm.$nextTick();
+ await nextTick();
// Account for debouncing
jest.runAllTimers();
diff --git a/spec/frontend/sidebar/components/time_tracking/report_spec.js b/spec/frontend/sidebar/components/time_tracking/report_spec.js
index 64d143615a0..2b17e6dd6c3 100644
--- a/spec/frontend/sidebar/components/time_tracking/report_spec.js
+++ b/spec/frontend/sidebar/components/time_tracking/report_spec.js
@@ -1,6 +1,7 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { getAllByRole, getByRole } from '@testing-library/dom';
-import { shallowMount, createLocalVue, mount } from '@vue/test-utils';
+import { shallowMount, mount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -13,8 +14,7 @@ import { getIssueTimelogsQueryResponse, getMrTimelogsQueryResponse } from './moc
jest.mock('~/flash');
describe('Issuable Time Tracking Report', () => {
- const localVue = createLocalVue();
- localVue.use(VueApollo);
+ Vue.use(VueApollo);
let wrapper;
let fakeApollo;
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
@@ -37,7 +37,6 @@ describe('Issuable Time Tracking Report', () => {
issuableType,
},
propsData: { limitToHours, issuableId: '1' },
- localVue,
apolloProvider: fakeApollo,
});
};
diff --git a/spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js b/spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js
index eb202a8cfcc..835e700e63c 100644
--- a/spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js
+++ b/spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js
@@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { stubTransition } from 'helpers/stub_transition';
import { createMockDirective } from 'helpers/vue_mock_directive';
import TimeTracker from '~/sidebar/components/time_tracking/time_tracker.vue';
@@ -161,7 +162,7 @@ describe('Issuable Time Tracker', () => {
it('should show the correct tooltip text', async () => {
expect(findByTestId('timeTrackingComparisonPane').exists()).toBe(true);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findComparisonMeter()).toBe('Time remaining: 26h 23m');
});
@@ -179,7 +180,7 @@ describe('Issuable Time Tracker', () => {
},
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('should display the human readable version of time estimated', () => {
@@ -282,7 +283,7 @@ describe('Issuable Time Tracker', () => {
},
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('should not show the "Help" pane by default', () => {
@@ -292,19 +293,19 @@ describe('Issuable Time Tracker', () => {
it('should show the "Help" pane when help button is clicked', async () => {
findHelpButton().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findByTestId('helpPane').exists()).toBe(true);
});
it('should not show the "Help" pane when help button is clicked and then closed', async () => {
findHelpButton().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findByTestId('helpPane').exists()).toBe(true);
findCloseHelpButton().trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findByTestId('helpPane').exists()).toBe(false);
});
@@ -315,7 +316,7 @@ describe('Issuable Time Tracker', () => {
it('refetches issuableTimeTracking query when eventHub emits `timeTracker:refresh` event', async () => {
SidebarEventHub.$emit('timeTracker:refresh');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(issuableTimeTrackingRefetchSpy).toHaveBeenCalled();
});
diff --git a/spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js b/spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js
index 23f1753c4bf..ea931782d1e 100644
--- a/spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js
+++ b/spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js
@@ -1,6 +1,6 @@
-import { GlIcon } from '@gitlab/ui';
+import { GlIcon, GlButton } 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 waitForPromises from 'helpers/wait_for_promises';
@@ -103,7 +103,7 @@ describe('Sidebar Todo Widget', () => {
});
it('sets default tooltip title', () => {
- expect(wrapper.find(GlIcon).attributes('title')).toBe('Add a to do');
+ expect(wrapper.find(GlButton).attributes('title')).toBe('Add a to do');
});
it('when user has a to do', async () => {
@@ -113,13 +113,13 @@ describe('Sidebar Todo Widget', () => {
await waitForPromises();
expect(wrapper.find(GlIcon).props('name')).toBe('todo-done');
- expect(wrapper.find(GlIcon).attributes('title')).toBe('Mark as done');
+ expect(wrapper.find(GlButton).attributes('title')).toBe('Mark as done');
});
it('emits `todoUpdated` event on click on icon', async () => {
wrapper.find(GlIcon).vm.$emit('click', event);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.emitted('todoUpdated')).toEqual([[false]]);
});
});
diff --git a/spec/frontend/sidebar/lock/edit_form_buttons_spec.js b/spec/frontend/sidebar/lock/edit_form_buttons_spec.js
index 1673425947e..971744edb0f 100644
--- a/spec/frontend/sidebar/lock/edit_form_buttons_spec.js
+++ b/spec/frontend/sidebar/lock/edit_form_buttons_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import createFlash from '~/flash';
import { createStore as createMrStore } from '~/mr_notes/stores';
import createStore from '~/notes/stores';
@@ -118,15 +119,13 @@ describe('EditFormButtons', () => {
});
it('resets loading', async () => {
- await wrapper.vm.$nextTick().then(() => {
- expect(findLockToggle().props('loading')).toBe(false);
- });
+ await nextTick();
+ expect(findLockToggle().props('loading')).toBe(false);
});
- it('emits close form', () => {
- return wrapper.vm.$nextTick().then(() => {
- expect(eventHub.$emit).toHaveBeenCalledWith('closeLockForm');
- });
+ it('emits close form', async () => {
+ await nextTick();
+ expect(eventHub.$emit).toHaveBeenCalledWith('closeLockForm');
});
it('does not flash an error message', () => {
@@ -153,15 +152,13 @@ describe('EditFormButtons', () => {
});
it('resets loading', async () => {
- await wrapper.vm.$nextTick().then(() => {
- expect(findLockToggle().props('loading')).toBe(false);
- });
+ await nextTick();
+ expect(findLockToggle().props('loading')).toBe(false);
});
- it('emits close form', () => {
- return wrapper.vm.$nextTick().then(() => {
- expect(eventHub.$emit).toHaveBeenCalledWith('closeLockForm');
- });
+ it('emits close form', async () => {
+ await nextTick();
+ expect(eventHub.$emit).toHaveBeenCalledWith('closeLockForm');
});
it('calls flash with the correct message', () => {
diff --git a/spec/frontend/sidebar/lock/issuable_lock_form_spec.js b/spec/frontend/sidebar/lock/issuable_lock_form_spec.js
index 1743e114bb0..7bf7e563a01 100644
--- a/spec/frontend/sidebar/lock/issuable_lock_form_spec.js
+++ b/spec/frontend/sidebar/lock/issuable_lock_form_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { mockTracking, triggerEvent } from 'helpers/tracking_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { createStore as createMrStore } from '~/mr_notes/stores';
@@ -80,13 +81,12 @@ describe('IssuableLockForm', () => {
});
describe('when not editable', () => {
- it('does not display the edit form when opened if not editable', () => {
+ it('does not display the edit form when opened if not editable', async () => {
expect(findEditForm().exists()).toBe(false);
findSidebarCollapseIcon().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(findEditForm().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findEditForm().exists()).toBe(false);
});
});
@@ -102,13 +102,12 @@ describe('IssuableLockForm', () => {
});
describe("when 'Edit' is clicked", () => {
- it('displays the edit form when editable', () => {
+ it('displays the edit form when editable', async () => {
expect(findEditForm().exists()).toBe(false);
findEditLink().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(findEditForm().exists()).toBe(true);
- });
+ await nextTick();
+ expect(findEditForm().exists()).toBe(true);
});
it('tracks the event ', () => {
@@ -123,13 +122,12 @@ describe('IssuableLockForm', () => {
});
describe('When sidebar is collapsed', () => {
- it('displays the edit form when opened', () => {
+ it('displays the edit form when opened', async () => {
expect(findEditForm().exists()).toBe(false);
findSidebarCollapseIcon().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(findEditForm().exists()).toBe(true);
- });
+ await nextTick();
+ expect(findEditForm().exists()).toBe(true);
});
it('renders a tooltip with the lock status text', () => {
diff --git a/spec/frontend/sidebar/mock_data.js b/spec/frontend/sidebar/mock_data.js
index 42e89a3ba84..30972484a08 100644
--- a/spec/frontend/sidebar/mock_data.js
+++ b/spec/frontend/sidebar/mock_data.js
@@ -276,6 +276,7 @@ export const epicParticipantsResponse = () => ({
participants: {
nodes: [
{
+ __typename: 'UserCore',
id: 'gid://gitlab/User/2',
avatarUrl:
'https://www.gravatar.com/avatar/a95e5b71488f4b9d69ce5ff58bfd28d6?s=80\u0026d=identicon',
@@ -332,6 +333,7 @@ export const issuableQueryResponse = {
assignees: {
nodes: [
{
+ __typename: 'UserCore',
id: 'gid://gitlab/User/2',
avatarUrl:
'https://www.gravatar.com/avatar/a95e5b71488f4b9d69ce5ff58bfd28d6?s=80\u0026d=identicon',
@@ -389,7 +391,7 @@ export const updateIssueAssigneesMutationResponse = {
assignees: {
nodes: [
{
- __typename: 'User',
+ __typename: 'UserCore',
id: 'gid://gitlab/User/1',
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
@@ -414,6 +416,7 @@ export const subscriptionNullResponse = {
};
const mockUser1 = {
+ __typename: 'UserCore',
id: 'gid://gitlab/User/1',
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
@@ -424,6 +427,7 @@ const mockUser1 = {
};
export const mockUser2 = {
+ __typename: 'UserCore',
id: 'gid://gitlab/User/4',
avatarUrl: '/avatar2',
name: 'rookie',
@@ -470,6 +474,7 @@ export const projectMembersResponse = {
{
id: 'user-4',
user: {
+ __typename: 'UserCore',
id: 'gid://gitlab/User/2',
avatarUrl:
'https://www.gravatar.com/avatar/a95e5b71488f4b9d69ce5ff58bfd28d6?s=80\u0026d=identicon',
@@ -503,6 +508,7 @@ export const groupMembersResponse = {
{
id: 'user-3',
user: {
+ __typename: 'UserCore',
id: 'gid://gitlab/User/2',
avatarUrl:
'https://www.gravatar.com/avatar/a95e5b71488f4b9d69ce5ff58bfd28d6?s=80\u0026d=identicon',
@@ -535,6 +541,7 @@ export const participantsQueryResponse = {
mockUser1,
mockUser1,
{
+ __typename: 'UserCore',
id: 'gid://gitlab/User/2',
avatarUrl:
'https://www.gravatar.com/avatar/a95e5b71488f4b9d69ce5ff58bfd28d6?s=80\u0026d=identicon',
@@ -546,6 +553,7 @@ export const participantsQueryResponse = {
},
},
{
+ __typename: 'UserCore',
id: 'gid://gitlab/User/3',
avatarUrl: '/avatar',
name: 'John Doe',
diff --git a/spec/frontend/sidebar/participants_spec.js b/spec/frontend/sidebar/participants_spec.js
index 94cdbe7f2ef..356628849d9 100644
--- a/spec/frontend/sidebar/participants_spec.js
+++ b/spec/frontend/sidebar/participants_spec.js
@@ -1,6 +1,6 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import { nextTick } from 'vue';
import Participants from '~/sidebar/components/participants/participants.vue';
const PARTICIPANT = {
@@ -77,7 +77,7 @@ describe('Participants', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
- it('when only showing visible participants, shows an avatar only for each participant under the limit', () => {
+ it('when only showing visible participants, shows an avatar only for each participant under the limit', async () => {
const numberOfLessParticipants = 2;
wrapper = mountComponent({
loading: false,
@@ -91,12 +91,11 @@ describe('Participants', () => {
isShowingMoreParticipants: false,
});
- return Vue.nextTick().then(() => {
- expect(wrapper.findAll('.participants-author')).toHaveLength(numberOfLessParticipants);
- });
+ await nextTick();
+ expect(wrapper.findAll('.participants-author')).toHaveLength(numberOfLessParticipants);
});
- it('when only showing all participants, each has an avatar', () => {
+ it('when only showing all participants, each has an avatar', async () => {
wrapper = mountComponent({
loading: false,
participants: PARTICIPANT_LIST,
@@ -109,9 +108,8 @@ describe('Participants', () => {
isShowingMoreParticipants: true,
});
- return Vue.nextTick().then(() => {
- expect(wrapper.findAll('.participants-author')).toHaveLength(PARTICIPANT_LIST.length);
- });
+ await nextTick();
+ expect(wrapper.findAll('.participants-author')).toHaveLength(PARTICIPANT_LIST.length);
});
it('does not have more participants link when they can all be shown', () => {
@@ -126,7 +124,7 @@ describe('Participants', () => {
expect(getMoreParticipantsButton().exists()).toBe(false);
});
- it('when too many participants, has more participants link to show more', () => {
+ it('when too many participants, has more participants link to show more', async () => {
wrapper = mountComponent({
loading: false,
participants: PARTICIPANT_LIST,
@@ -139,12 +137,11 @@ describe('Participants', () => {
isShowingMoreParticipants: false,
});
- return Vue.nextTick().then(() => {
- expect(getMoreParticipantsButton().text()).toBe('+ 1 more');
- });
+ await nextTick();
+ expect(getMoreParticipantsButton().text()).toBe('+ 1 more');
});
- it('when too many participants and already showing them, has more participants link to show less', () => {
+ it('when too many participants and already showing them, has more participants link to show less', async () => {
wrapper = mountComponent({
loading: false,
participants: PARTICIPANT_LIST,
@@ -157,9 +154,8 @@ describe('Participants', () => {
isShowingMoreParticipants: true,
});
- return Vue.nextTick().then(() => {
- expect(getMoreParticipantsButton().text()).toBe('- show less');
- });
+ await nextTick();
+ expect(getMoreParticipantsButton().text()).toBe('- show less');
});
it('clicking more participants link emits event', () => {
@@ -176,7 +172,7 @@ describe('Participants', () => {
expect(wrapper.vm.isShowingMoreParticipants).toBe(true);
});
- it('clicking on participants icon emits `toggleSidebar` event', () => {
+ it('clicking on participants icon emits `toggleSidebar` event', async () => {
wrapper = mountComponent({
loading: false,
participants: PARTICIPANT_LIST,
@@ -187,11 +183,9 @@ describe('Participants', () => {
wrapper.find('.sidebar-collapsed-icon').trigger('click');
- return Vue.nextTick(() => {
- expect(spy).toHaveBeenCalledWith('toggleSidebar');
-
- spy.mockRestore();
- });
+ await nextTick();
+ expect(spy).toHaveBeenCalledWith('toggleSidebar');
+ spy.mockRestore();
});
});
diff --git a/spec/frontend/sidebar/sidebar_assignees_spec.js b/spec/frontend/sidebar/sidebar_assignees_spec.js
index dc121dcb897..5f77e21c1f8 100644
--- a/spec/frontend/sidebar/sidebar_assignees_spec.js
+++ b/spec/frontend/sidebar/sidebar_assignees_spec.js
@@ -1,6 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import axios from 'axios';
import AxiosMockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import Assigness from '~/sidebar/components/assignees/assignees.vue';
import AssigneesRealtime from '~/sidebar/components/assignees/assignees_realtime.vue';
import SidebarAssignees from '~/sidebar/components/assignees/sidebar_assignees.vue';
@@ -74,16 +75,15 @@ describe('sidebar assignees', () => {
expect(mediator.store.assignees.length).toBe(1);
});
- it('hides assignees until fetched', () => {
+ it('hides assignees until fetched', async () => {
createComponent();
expect(wrapper.find(Assigness).exists()).toBe(false);
wrapper.vm.store.isFetching.assignees = false;
- return wrapper.vm.$nextTick(() => {
- expect(wrapper.find(Assigness).exists()).toBe(true);
- });
+ await nextTick();
+ expect(wrapper.find(Assigness).exists()).toBe(true);
});
describe('when realTimeIssueSidebar is turned on', () => {
diff --git a/spec/frontend/sidebar/sidebar_move_issue_spec.js b/spec/frontend/sidebar/sidebar_move_issue_spec.js
index d9972ae75c3..7bb7b18adf8 100644
--- a/spec/frontend/sidebar/sidebar_move_issue_spec.js
+++ b/spec/frontend/sidebar/sidebar_move_issue_spec.js
@@ -1,5 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
+import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import SidebarMoveIssue from '~/sidebar/lib/sidebar_move_issue';
@@ -77,15 +78,14 @@ describe('SidebarMoveIssue', () => {
expect(test.sidebarMoveIssue.$dropdownToggle.data('deprecatedJQueryDropdown')).toBeTruthy();
});
- it('escapes html from project name', (done) => {
+ it('escapes html from project name', async () => {
test.$toggleButton.dropdown('toggle');
- setImmediate(() => {
- expect(test.$content.find('.js-move-issue-dropdown-item')[1].innerHTML.trim()).toEqual(
- '&lt;img src=x onerror=alert(document.domain)&gt; foo / bar',
- );
- done();
- });
+ await waitForPromises();
+
+ expect(test.$content.find('.js-move-issue-dropdown-item')[1].innerHTML.trim()).toEqual(
+ '&lt;img src=x onerror=alert(document.domain)&gt; foo / bar',
+ );
});
});
@@ -101,20 +101,20 @@ describe('SidebarMoveIssue', () => {
expect(test.$confirmButton.hasClass('is-loading')).toBe(true);
});
- it('should remove loading state from confirm button on failure', (done) => {
+ it('should remove loading state from confirm button on failure', async () => {
jest.spyOn(test.mediator, 'moveIssue').mockReturnValue(Promise.reject());
test.mediator.setMoveToProjectId(7);
test.sidebarMoveIssue.onConfirmClicked();
expect(test.mediator.moveIssue).toHaveBeenCalled();
+
// Wait for the move issue request to fail
- setImmediate(() => {
- expect(createFlash).toHaveBeenCalled();
- expect(test.$confirmButton.prop('disabled')).toBeFalsy();
- expect(test.$confirmButton.hasClass('is-loading')).toBe(false);
- done();
- });
+ await waitForPromises();
+
+ expect(createFlash).toHaveBeenCalled();
+ expect(test.$confirmButton.prop('disabled')).toBeFalsy();
+ expect(test.$confirmButton.hasClass('is-loading')).toBe(false);
});
it('should not move the issue with id=0', () => {
@@ -127,35 +127,33 @@ describe('SidebarMoveIssue', () => {
});
});
- it('should set moveToProjectId on dropdown item "No project" click', (done) => {
+ it('should set moveToProjectId on dropdown item "No project" click', async () => {
jest.spyOn(test.mediator, 'setMoveToProjectId').mockImplementation(() => {});
// Open the dropdown
test.$toggleButton.dropdown('toggle');
// Wait for the autocomplete request to finish
- setImmediate(() => {
- test.$content.find('.js-move-issue-dropdown-item').eq(0).trigger('click');
+ await waitForPromises();
- expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(0);
- expect(test.$confirmButton.prop('disabled')).toBeTruthy();
- done();
- });
+ test.$content.find('.js-move-issue-dropdown-item').eq(0).trigger('click');
+
+ expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(0);
+ expect(test.$confirmButton.prop('disabled')).toBeTruthy();
});
- it('should set moveToProjectId on dropdown item click', (done) => {
+ it('should set moveToProjectId on dropdown item click', async () => {
jest.spyOn(test.mediator, 'setMoveToProjectId').mockImplementation(() => {});
// Open the dropdown
test.$toggleButton.dropdown('toggle');
// Wait for the autocomplete request to finish
- setImmediate(() => {
- test.$content.find('.js-move-issue-dropdown-item').eq(1).trigger('click');
+ await waitForPromises();
- expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(20);
- expect(test.$confirmButton.attr('disabled')).toBe(undefined);
- done();
- });
+ test.$content.find('.js-move-issue-dropdown-item').eq(1).trigger('click');
+
+ expect(test.mediator.setMoveToProjectId).toHaveBeenCalledWith(20);
+ expect(test.$confirmButton.attr('disabled')).toBe(undefined);
});
});
diff --git a/spec/frontend/sidebar/todo_spec.js b/spec/frontend/sidebar/todo_spec.js
index 6829e688c65..9316268d2ad 100644
--- a/spec/frontend/sidebar/todo_spec.js
+++ b/spec/frontend/sidebar/todo_spec.js
@@ -1,6 +1,7 @@
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import SidebarTodos from '~/sidebar/components/todo_toggle/todo.vue';
const defaultProps = {
@@ -49,13 +50,12 @@ describe('SidebarTodo', () => {
);
describe('template', () => {
- it('emits `toggleTodo` event when clicked on button', () => {
+ it('emits `toggleTodo` event when clicked on button', async () => {
createComponent();
wrapper.find('button').trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted().toggleTodo).toBeTruthy();
- });
+ await nextTick();
+ expect(wrapper.emitted().toggleTodo).toBeTruthy();
});
it('renders component container element with proper data attributes', () => {
diff --git a/spec/frontend/snippets/components/edit_spec.js b/spec/frontend/snippets/components/edit_spec.js
index 80a8b8ec489..61424fa1eb2 100644
--- a/spec/frontend/snippets/components/edit_spec.js
+++ b/spec/frontend/snippets/components/edit_spec.js
@@ -1,7 +1,8 @@
import { GlLoadingIcon } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import { merge } from 'lodash';
-import { nextTick } from 'vue';
+
import VueApollo, { ApolloMutation } from 'vue-apollo';
import { useFakeDate } from 'helpers/fake_date';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -78,8 +79,7 @@ const getApiData = ({
blobActions: [],
});
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
describe('Snippet Edit app', () => {
useFakeDate();
@@ -141,7 +141,6 @@ describe('Snippet Edit app', () => {
wrapper = shallowMount(SnippetEditApp, {
apolloProvider,
- localVue,
stubs: {
ApolloMutation,
FormFooterActions,
@@ -330,6 +329,7 @@ describe('Snippet Edit app', () => {
mutateSpy.mockRejectedValue(TEST_API_ERROR);
await createComponentAndSubmit();
+ await nextTick();
});
it('should not redirect', () => {
@@ -339,7 +339,7 @@ describe('Snippet Edit app', () => {
it('should flash', () => {
// Apollo automatically wraps the resolver's error in a NetworkError
expect(createFlash).toHaveBeenCalledWith({
- message: `Can't update snippet: Network error: ${TEST_API_ERROR.message}`,
+ message: `Can't update snippet: ${TEST_API_ERROR.message}`,
});
});
@@ -349,7 +349,7 @@ describe('Snippet Edit app', () => {
// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalledWith(
'[gitlab] unexpected error while updating snippet',
- expect.objectContaining({ message: `Network error: ${TEST_API_ERROR.message}` }),
+ expect.objectContaining({ message: `${TEST_API_ERROR.message}` }),
);
});
});
diff --git a/spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js b/spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js
index 2693b26aeae..8174ba5c693 100644
--- a/spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js
+++ b/spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js
@@ -1,5 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { times } from 'lodash';
+import { nextTick } from 'vue';
import SnippetBlobActionsEdit from '~/snippets/components/snippet_blob_actions_edit.vue';
import SnippetBlobEdit from '~/snippets/components/snippet_blob_edit.vue';
import {
@@ -193,7 +194,7 @@ describe('snippets/components/snippet_blob_actions_edit', () => {
it('emits an action when content changes again', async () => {
triggerBlobUpdate(0, { content });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(getLastActions()).toEqual([testEntries.updated.diff]);
});
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/snippets/components/snippet_header_spec.js b/spec/frontend/snippets/components/snippet_header_spec.js
index daa9d6345b0..1b9d170556b 100644
--- a/spec/frontend/snippets/components/snippet_header_spec.js
+++ b/spec/frontend/snippets/components/snippet_header_spec.js
@@ -2,6 +2,7 @@ import { GlButton, GlModal, GlDropdown } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { ApolloMutation } from 'vue-apollo';
import MockAdapter from 'axios-mock-adapter';
+import { nextTick } from 'vue';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
@@ -245,7 +246,7 @@ describe('Snippet header component', () => {
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ canCreateSnippet: true });
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findButtonsAsModel()).toEqual(
expect.arrayContaining([
@@ -348,33 +349,31 @@ describe('Snippet header component', () => {
describe('in case of successful mutation, closes modal and redirects to correct listing', () => {
useMockLocationHelper();
- const createDeleteSnippet = (snippetProps = {}) => {
+ const createDeleteSnippet = async (snippetProps = {}) => {
createComponent({
snippetProps,
});
wrapper.vm.closeDeleteModal = jest.fn();
wrapper.vm.deleteSnippet();
- return wrapper.vm.$nextTick();
+ await nextTick();
};
- it('redirects to dashboard/snippets for personal snippet', () => {
- return createDeleteSnippet().then(() => {
- expect(wrapper.vm.closeDeleteModal).toHaveBeenCalled();
- expect(window.location.pathname).toBe(`${gon.relative_url_root}dashboard/snippets`);
- });
+ it('redirects to dashboard/snippets for personal snippet', async () => {
+ await createDeleteSnippet();
+ expect(wrapper.vm.closeDeleteModal).toHaveBeenCalled();
+ expect(window.location.pathname).toBe(`${gon.relative_url_root}dashboard/snippets`);
});
- it('redirects to project snippets for project snippet', () => {
+ it('redirects to project snippets for project snippet', async () => {
const fullPath = 'foo/bar';
- return createDeleteSnippet({
+ await createDeleteSnippet({
project: {
fullPath,
},
- }).then(() => {
- expect(wrapper.vm.closeDeleteModal).toHaveBeenCalled();
- expect(window.location.pathname).toBe(`${fullPath}/-/snippets`);
});
+ expect(wrapper.vm.closeDeleteModal).toHaveBeenCalled();
+ expect(window.location.pathname).toBe(`${fullPath}/-/snippets`);
});
});
});
diff --git a/spec/frontend/static_site_editor/components/edit_meta_controls_spec.js b/spec/frontend/static_site_editor/components/edit_meta_controls_spec.js
index 7a8834933e0..f6b29e98e5f 100644
--- a/spec/frontend/static_site_editor/components/edit_meta_controls_spec.js
+++ b/spec/frontend/static_site_editor/components/edit_meta_controls_spec.js
@@ -1,6 +1,7 @@
import { GlDropdown, GlDropdownItem, GlFormInput, GlFormTextarea } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import EditMetaControls from '~/static_site_editor/components/edit_meta_controls.vue';
import { mergeRequestMeta, mergeRequestTemplates } from '../mock_data';
@@ -38,11 +39,11 @@ describe('~/static_site_editor/components/edit_meta_controls.vue', () => {
const findGlFormTextAreaDescription = () => wrapper.find(GlFormTextarea);
- beforeEach(() => {
+ beforeEach(async () => {
buildWrapper();
buildMocks();
- return wrapper.vm.$nextTick();
+ await nextTick();
});
afterEach(() => {
diff --git a/spec/frontend/static_site_editor/components/edit_meta_modal_spec.js b/spec/frontend/static_site_editor/components/edit_meta_modal_spec.js
index 3a336f6a230..bf3f8b7f571 100644
--- a/spec/frontend/static_site_editor/components/edit_meta_modal_spec.js
+++ b/spec/frontend/static_site_editor/components/edit_meta_modal_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 { useLocalStorageSpy } from 'helpers/local_storage_helper';
import axios from '~/lib/utils/axios_utils';
import EditMetaControls from '~/static_site_editor/components/edit_meta_controls.vue';
@@ -50,14 +51,14 @@ describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
const findEditMetaControls = () => wrapper.find(EditMetaControls);
const findLocalStorageSync = () => wrapper.find(LocalStorageSync);
- beforeEach(() => {
+ beforeEach(async () => {
localStorage.setItem(MR_META_LOCAL_STORAGE_KEY);
buildMockAxios();
buildWrapper();
buildMockRefs();
- return wrapper.vm.$nextTick();
+ await nextTick();
});
afterEach(() => {
@@ -77,7 +78,7 @@ describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
findLocalStorageSync().vm.$emit('input', localStorageMeta);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findEditMetaControls().props()).toEqual(localStorageMeta);
});
@@ -134,13 +135,13 @@ describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
it('sets the currentTemplate on the changeTemplate event', async () => {
findEditMetaControls().vm.$emit('changeTemplate', template1);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findEditMetaControls().props().currentTemplate).toBe(template1);
findEditMetaControls().vm.$emit('changeTemplate', null);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findEditMetaControls().props().currentTemplate).toBe(null);
});
@@ -148,7 +149,7 @@ describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
it('updates the description on the changeTemplate event', async () => {
findEditMetaControls().vm.$emit('changeTemplate', template1);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findEditMetaControls().props().description).toEqual(template1.content);
});
@@ -164,7 +165,7 @@ describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
findEditMetaControls().vm.$emit('updateSettings', newMeta);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findLocalStorageSync().props('value')).toEqual(newMeta);
});
diff --git a/spec/frontend/static_site_editor/pages/home_spec.js b/spec/frontend/static_site_editor/pages/home_spec.js
index eb056469603..6571d295c36 100644
--- a/spec/frontend/static_site_editor/pages/home_spec.js
+++ b/spec/frontend/static_site_editor/pages/home_spec.js
@@ -1,4 +1,5 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import EditArea from '~/static_site_editor/components/edit_area.vue';
import EditMetaModal from '~/static_site_editor/components/edit_meta_modal.vue';
@@ -29,8 +30,6 @@ import {
imageRoot,
} from '../mock_data';
-const localVue = createLocalVue();
-
describe('static_site_editor/pages/home', () => {
let wrapper;
let store;
@@ -78,7 +77,6 @@ describe('static_site_editor/pages/home', () => {
const buildWrapper = (data = {}) => {
wrapper = shallowMount(Home, {
- localVue,
store,
mocks: {
$apollo,
@@ -182,7 +180,7 @@ describe('static_site_editor/pages/home', () => {
});
describe('when preparing submission', () => {
- it('calls the show method when the edit-area submit event is emitted', () => {
+ it('calls the show method when the edit-area submit event is emitted', async () => {
buildWrapper();
const mockInstance = { show: jest.fn() };
@@ -190,9 +188,8 @@ describe('static_site_editor/pages/home', () => {
findEditArea().vm.$emit('submit', { content });
- return wrapper.vm.$nextTick().then(() => {
- expect(mockInstance.show).toHaveBeenCalled();
- });
+ await nextTick();
+ expect(mockInstance.show).toHaveBeenCalled();
});
});
@@ -203,13 +200,13 @@ describe('static_site_editor/pages/home', () => {
.mockRejectedValueOnce(new Error(submitChangesError));
};
- beforeEach(() => {
+ beforeEach(async () => {
setupMutateMock();
buildWrapper({ content });
findEditMetaModal().vm.$emit('primary', mergeRequestMeta);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays submit changes error message', () => {
@@ -224,12 +221,11 @@ describe('static_site_editor/pages/home', () => {
expect(mutateMock).toHaveBeenCalled();
});
- it('hides submit changes error message when dismiss button is clicked', () => {
+ it('hides submit changes error message when dismiss button is clicked', async () => {
findSubmitChangesError().vm.$emit('dismiss');
- return wrapper.vm.$nextTick().then(() => {
- expect(findSubmitChangesError().exists()).toBe(false);
- });
+ await nextTick();
+ expect(findSubmitChangesError().exists()).toBe(false);
});
});
@@ -237,7 +233,7 @@ describe('static_site_editor/pages/home', () => {
const newContent = `new ${content}`;
const formattedMarkdown = `formatted ${content}`;
- beforeEach(() => {
+ beforeEach(async () => {
mutateMock.mockResolvedValueOnce(hasSubmittedChangesMutationPayload).mockResolvedValueOnce({
data: {
submitContentChanges: savedContentMeta,
@@ -252,7 +248,7 @@ describe('static_site_editor/pages/home', () => {
findEditMetaModal().vm.$emit('primary', mergeRequestMeta);
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('dispatches hasSubmittedChanges mutation', () => {
diff --git a/spec/frontend/terraform/components/states_table_actions_spec.js b/spec/frontend/terraform/components/states_table_actions_spec.js
index fbe55306f37..a6c80b95af4 100644
--- a/spec/frontend/terraform/components/states_table_actions_spec.js
+++ b/spec/frontend/terraform/components/states_table_actions_spec.js
@@ -1,5 +1,6 @@
import { GlDropdown, GlModal, GlSprintf } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -9,8 +10,7 @@ import lockStateMutation from '~/terraform/graphql/mutations/lock_state.mutation
import removeStateMutation from '~/terraform/graphql/mutations/remove_state.mutation.graphql';
import unlockStateMutation from '~/terraform/graphql/mutations/unlock_state.mutation.graphql';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
describe('StatesTableActions', () => {
let lockResponse;
@@ -58,20 +58,19 @@ describe('StatesTableActions', () => {
);
};
- const createComponent = (propsData = defaultProps) => {
+ const createComponent = async (propsData = defaultProps) => {
const apolloProvider = createMockApolloProvider();
toast = jest.fn();
wrapper = shallowMount(StateActions, {
apolloProvider,
- localVue,
propsData,
mocks: { $toast: { show: toast } },
stubs: { GlDropdown, GlModal, GlSprintf },
});
- return wrapper.vm.$nextTick();
+ await nextTick();
};
const findActionsDropdown = () => wrapper.findComponent(GlDropdown);
diff --git a/spec/frontend/terraform/components/states_table_spec.js b/spec/frontend/terraform/components/states_table_spec.js
index 100e577f514..fa9c8320b4f 100644
--- a/spec/frontend/terraform/components/states_table_spec.js
+++ b/spec/frontend/terraform/components/states_table_spec.js
@@ -1,5 +1,6 @@
-import { GlIcon, GlLoadingIcon, GlTooltip } from '@gitlab/ui';
+import { GlBadge, GlLoadingIcon, GlTooltip } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { useFakeDate } from 'helpers/fake_date';
import StatesTable from '~/terraform/components/states_table.vue';
import StateActions from '~/terraform/components/states_table_actions.vue';
@@ -106,9 +107,9 @@ describe('StatesTable', () => {
],
};
- const createComponent = (propsData = defaultProps) => {
+ const createComponent = async (propsData = defaultProps) => {
wrapper = mount(StatesTable, { propsData });
- return wrapper.vm.$nextTick();
+ await nextTick();
};
const findActions = () => wrapper.findAll(StateActions);
@@ -138,7 +139,7 @@ describe('StatesTable', () => {
const toolTip = state.find(GlTooltip);
expect(state.text()).toContain(name);
- expect(state.find(GlIcon).exists()).toBe(locked);
+ expect(state.find(GlBadge).exists()).toBe(locked);
expect(state.find(GlLoadingIcon).exists()).toBe(loading);
expect(toolTip.exists()).toBe(locked);
diff --git a/spec/frontend/terraform/components/terraform_list_spec.js b/spec/frontend/terraform/components/terraform_list_spec.js
index 8e565df81ae..c8b4cd564d9 100644
--- a/spec/frontend/terraform/components/terraform_list_spec.js
+++ b/spec/frontend/terraform/components/terraform_list_spec.js
@@ -1,5 +1,6 @@
import { GlAlert, GlBadge, GlKeysetPagination, GlLoadingIcon, GlTab } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -8,8 +9,7 @@ import StatesTable from '~/terraform/components/states_table.vue';
import TerraformList from '~/terraform/components/terraform_list.vue';
import getStatesQuery from '~/terraform/graphql/queries/get_states.query.graphql';
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
describe('TerraformList', () => {
let wrapper;
@@ -45,7 +45,6 @@ describe('TerraformList', () => {
const apolloProvider = createMockApollo([[getStatesQuery, statsQueryResponse]], mockResolvers);
wrapper = shallowMount(TerraformList, {
- localVue,
apolloProvider,
propsData,
stubs: {
@@ -100,6 +99,7 @@ describe('TerraformList', () => {
nodes: states,
count: states.length,
pageInfo: {
+ __typename: 'PageInfo',
hasNextPage: true,
hasPreviousPage: false,
startCursor: 'prev',
diff --git a/spec/frontend/toggles/index_spec.js b/spec/frontend/toggles/index_spec.js
new file mode 100644
index 00000000000..575b1b6080c
--- /dev/null
+++ b/spec/frontend/toggles/index_spec.js
@@ -0,0 +1,149 @@
+import { createWrapper } from '@vue/test-utils';
+import { GlToggle } from '@gitlab/ui';
+import { initToggle } from '~/toggles';
+
+// Selectors
+const TOGGLE_WRAPPER_CLASS = '.gl-toggle-wrapper';
+const TOGGLE_LABEL_CLASS = '.gl-toggle-label';
+const CHECKED_CLASS = '.is-checked';
+const DISABLED_CLASS = '.is-disabled';
+const LOADING_CLASS = '.toggle-loading';
+const HELP_TEXT_SELECTOR = '[data-testid="toggle-help"]';
+
+// Toggle settings
+const toggleClassName = 'js-custom-toggle-class';
+const toggleLabel = 'Toggle label';
+
+describe('toggles/index.js', () => {
+ let instance;
+ let toggleWrapper;
+
+ const createRootEl = (dataAttrs) => {
+ const dataset = {
+ label: toggleLabel,
+ ...dataAttrs,
+ };
+ const el = document.createElement('span');
+ el.classList.add(toggleClassName);
+
+ Object.entries(dataset).forEach(([key, value]) => {
+ el.dataset[key] = value;
+ });
+
+ document.body.appendChild(el);
+
+ return el;
+ };
+
+ const initToggleWithOptions = (options = {}) => {
+ const el = createRootEl(options);
+ instance = initToggle(el);
+ toggleWrapper = document.querySelector(TOGGLE_WRAPPER_CLASS);
+ };
+
+ afterEach(() => {
+ document.body.innerHTML = '';
+ instance = null;
+ toggleWrapper = null;
+ });
+
+ describe('initToggle', () => {
+ describe('default state', () => {
+ beforeEach(() => {
+ initToggleWithOptions();
+ });
+
+ it('attaches a GlToggle to the element', async () => {
+ expect(toggleWrapper).not.toBe(null);
+ expect(toggleWrapper.querySelector(TOGGLE_LABEL_CLASS).textContent).toBe(toggleLabel);
+ });
+
+ it('passes CSS classes down to GlToggle', () => {
+ expect(toggleWrapper.className).toContain(toggleClassName);
+ });
+
+ it('is not checked', () => {
+ expect(toggleWrapper.querySelector(CHECKED_CLASS)).toBe(null);
+ });
+
+ it('is enabled', () => {
+ expect(toggleWrapper.querySelector(DISABLED_CLASS)).toBe(null);
+ });
+
+ it('is not loading', () => {
+ expect(toggleWrapper.querySelector(LOADING_CLASS)).toBe(null);
+ });
+
+ it('emits "change" event when value changes', () => {
+ const wrapper = createWrapper(instance);
+ const event = 'change';
+ const listener = jest.fn();
+
+ instance.$on(event, listener);
+
+ expect(listener).toHaveBeenCalledTimes(0);
+
+ wrapper.find(GlToggle).vm.$emit(event, true);
+
+ expect(listener).toHaveBeenCalledTimes(1);
+ expect(listener).toHaveBeenLastCalledWith(true);
+
+ wrapper.find(GlToggle).vm.$emit(event, false);
+
+ expect(listener).toHaveBeenCalledTimes(2);
+ expect(listener).toHaveBeenLastCalledWith(false);
+ });
+ });
+
+ describe('with custom options', () => {
+ const name = 'toggle-name';
+ const help = 'Help text';
+ const foo = 'bar';
+
+ beforeEach(() => {
+ initToggleWithOptions({
+ name,
+ isChecked: true,
+ disabled: true,
+ isLoading: true,
+ help,
+ labelPosition: 'hidden',
+ foo,
+ });
+ toggleWrapper = document.querySelector(TOGGLE_WRAPPER_CLASS);
+ });
+
+ it('sets the custom name', () => {
+ const input = toggleWrapper.querySelector('input[type="hidden"]');
+
+ expect(input.name).toBe(name);
+ });
+
+ it('is checked', () => {
+ expect(toggleWrapper.querySelector(CHECKED_CLASS)).not.toBe(null);
+ });
+
+ it('is disabled', () => {
+ expect(toggleWrapper.querySelector(DISABLED_CLASS)).not.toBe(null);
+ });
+
+ it('is loading', () => {
+ expect(toggleWrapper.querySelector(LOADING_CLASS)).not.toBe(null);
+ });
+
+ it('sets the custom help text', () => {
+ expect(toggleWrapper.querySelector(HELP_TEXT_SELECTOR).textContent).toBe(help);
+ });
+
+ it('hides the label', () => {
+ expect(
+ toggleWrapper.querySelector(TOGGLE_LABEL_CLASS).classList.contains('gl-sr-only'),
+ ).toBe(true);
+ });
+
+ it('passes custom dataset to the wrapper', () => {
+ expect(toggleWrapper.dataset.foo).toBe('bar');
+ });
+ });
+ });
+});
diff --git a/spec/frontend/token_access/token_access_spec.js b/spec/frontend/token_access/token_access_spec.js
index c4e29a52f1c..5aaeebd5af4 100644
--- a/spec/frontend/token_access/token_access_spec.js
+++ b/spec/frontend/token_access/token_access_spec.js
@@ -1,5 +1,5 @@
import { GlToggle, GlLoadingIcon } from '@gitlab/ui';
-import { createLocalVue } from '@vue/test-utils';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -22,9 +22,8 @@ import {
const projectPath = 'root/my-repo';
const error = new Error('Error');
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
jest.mock('~/flash');
@@ -52,7 +51,6 @@ describe('TokenAccess component', () => {
const createComponent = (requestHandlers, mountFn = shallowMountExtended) => {
wrapper = mountFn(TokenAccess, {
- localVue,
provide: {
fullPath: projectPath,
},
diff --git a/spec/frontend/tooltips/components/tooltips_spec.js b/spec/frontend/tooltips/components/tooltips_spec.js
index 9b703b74a1a..eef352a72ff 100644
--- a/spec/frontend/tooltips/components/tooltips_spec.js
+++ b/spec/frontend/tooltips/components/tooltips_spec.js
@@ -1,5 +1,6 @@
import { GlTooltip } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import { useMockMutationObserver } from 'helpers/mock_dom_observer';
import Tooltips from '~/tooltips/components/tooltips.vue';
@@ -46,7 +47,7 @@ describe('tooltips/components/tooltips.vue', () => {
it('attaches tooltips to the targets specified', async () => {
wrapper.vm.addTooltips([target]);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlTooltip).props('target')).toBe(target);
});
@@ -56,7 +57,7 @@ describe('tooltips/components/tooltips.vue', () => {
wrapper.vm.addTooltips([target]);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlTooltip).exists()).toBe(false);
});
@@ -65,7 +66,7 @@ describe('tooltips/components/tooltips.vue', () => {
wrapper.vm.addTooltips([target]);
wrapper.vm.addTooltips([target]);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.findAll(GlTooltip)).toHaveLength(1);
});
@@ -73,7 +74,7 @@ describe('tooltips/components/tooltips.vue', () => {
it('sets tooltip content from title attribute', async () => {
wrapper.vm.addTooltips([target]);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlTooltip).text()).toBe(target.getAttribute('title'));
});
@@ -85,7 +86,7 @@ describe('tooltips/components/tooltips.vue', () => {
});
wrapper.vm.addTooltips([target]);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlTooltip).html()).toContain(target.getAttribute('title'));
});
@@ -94,7 +95,7 @@ describe('tooltips/components/tooltips.vue', () => {
const config = { show: true };
target = createTooltipTarget();
wrapper.vm.addTooltips([target], config);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlTooltip).props()).toMatchObject(config);
});
@@ -110,7 +111,7 @@ describe('tooltips/components/tooltips.vue', () => {
target = createTooltipTarget({ [attribute]: value });
wrapper.vm.addTooltips([target]);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlTooltip).props(prop)).toBe(value);
},
@@ -124,10 +125,10 @@ describe('tooltips/components/tooltips.vue', () => {
it('removes all tooltips when elements is nil', async () => {
wrapper.vm.addTooltips([createTooltipTarget(), createTooltipTarget()]);
- await wrapper.vm.$nextTick();
+ await nextTick();
wrapper.vm.dispose();
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(allTooltips()).toHaveLength(0);
});
@@ -136,10 +137,10 @@ describe('tooltips/components/tooltips.vue', () => {
const target = createTooltipTarget();
wrapper.vm.addTooltips([target, createTooltipTarget()]);
- await wrapper.vm.$nextTick();
+ await nextTick();
wrapper.vm.dispose(target);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(allTooltips()).toHaveLength(1);
});
@@ -154,13 +155,13 @@ describe('tooltips/components/tooltips.vue', () => {
const target = createTooltipTarget();
wrapper.vm.addTooltips([target, createTooltipTarget()]);
- await wrapper.vm.$nextTick();
+ await nextTick();
triggerMutate(document.body, {
entry: { removedNodes: [target] },
options: { childList: true },
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(allTooltips()).toHaveLength(1);
});
@@ -175,7 +176,7 @@ describe('tooltips/components/tooltips.vue', () => {
wrapper.vm.addTooltips([target]);
- await wrapper.vm.$nextTick();
+ await nextTick();
wrapper.vm.triggerEvent(target, event);
@@ -195,14 +196,14 @@ describe('tooltips/components/tooltips.vue', () => {
wrapper.vm.addTooltips([target]);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlTooltip).text()).toBe(currentTitle);
target.setAttribute('title', newTitle);
wrapper.vm.fixTitle(target);
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlTooltip).text()).toBe(newTitle);
});
@@ -225,7 +226,7 @@ describe('tooltips/components/tooltips.vue', () => {
buildWrapper();
wrapper.vm.addTooltips([createTooltipTarget()]);
- await wrapper.vm.$nextTick();
+ await nextTick();
wrapper.findComponent(GlTooltip).vm.$emit('hidden');
expect(wrapper.emitted('hidden')).toHaveLength(1);
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/user_lists/components/add_user_modal_spec.js b/spec/frontend/user_lists/components/add_user_modal_spec.js
index c9ad40ed228..cd04836a8c4 100644
--- a/spec/frontend/user_lists/components/add_user_modal_spec.js
+++ b/spec/frontend/user_lists/components/add_user_modal_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import AddUserModal from '~/user_lists/components/add_user_modal.vue';
describe('Add User Modal', () => {
@@ -30,7 +31,7 @@ describe('Add User Modal', () => {
it('should clear the input after emitting', async () => {
click('confirm-add-user-ids');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('#add-user-ids').element.value).toBe('');
});
@@ -42,7 +43,7 @@ describe('Add User Modal', () => {
it('should clear the input after cancelling', async () => {
click('cancel-add-user-ids');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('#add-user-ids').element.value).toBe('');
});
diff --git a/spec/frontend/user_lists/components/edit_user_list_spec.js b/spec/frontend/user_lists/components/edit_user_list_spec.js
index bd71a677a24..7cafe5e1f56 100644
--- a/spec/frontend/user_lists/components/edit_user_list_spec.js
+++ b/spec/frontend/user_lists/components/edit_user_list_spec.js
@@ -1,6 +1,6 @@
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
-import { createLocalVue, mount } from '@vue/test-utils';
-import Vue from 'vue';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
@@ -13,8 +13,7 @@ import { userList } from '../../feature_flags/mock_data';
jest.mock('~/api');
jest.mock('~/lib/utils/url_utility');
-const localVue = createLocalVue(Vue);
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('user_lists/components/edit_user_list', () => {
let wrapper;
@@ -30,7 +29,6 @@ describe('user_lists/components/edit_user_list', () => {
destroy();
wrapper = mount(EditUserList, {
- localVue,
store: createStore({ projectId: '1', userListIid: '2' }),
provide: {
userListsDocsPath: '/docs/user_lists',
@@ -79,11 +77,11 @@ describe('user_lists/components/edit_user_list', () => {
});
describe('update', () => {
- beforeEach(() => {
+ beforeEach(async () => {
Api.fetchFeatureFlagUserList.mockResolvedValue({ data: userList });
factory();
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('should link to the documentation', () => {
@@ -101,11 +99,11 @@ describe('user_lists/components/edit_user_list', () => {
});
describe('success', () => {
- beforeEach(() => {
+ beforeEach(async () => {
Api.updateFeatureFlagUserList.mockResolvedValue({ data: userList });
setInputValue('test');
clickSave();
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('should create a user list with the entered name', () => {
@@ -141,7 +139,7 @@ describe('user_lists/components/edit_user_list', () => {
it('should dismiss the error if dismiss is clicked', async () => {
alert.find('button').trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(alert.exists()).toBe(false);
});
diff --git a/spec/frontend/user_lists/components/new_user_list_spec.js b/spec/frontend/user_lists/components/new_user_list_spec.js
index a81e8912714..5eb44970fe4 100644
--- a/spec/frontend/user_lists/components/new_user_list_spec.js
+++ b/spec/frontend/user_lists/components/new_user_list_spec.js
@@ -1,6 +1,6 @@
import { GlAlert } from '@gitlab/ui';
-import { mount, createLocalVue } from '@vue/test-utils';
-import Vue from 'vue';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
@@ -12,8 +12,7 @@ import { userList } from '../../feature_flags/mock_data';
jest.mock('~/api');
jest.mock('~/lib/utils/url_utility');
-const localVue = createLocalVue(Vue);
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('user_lists/components/new_user_list', () => {
let wrapper;
@@ -24,7 +23,6 @@ describe('user_lists/components/new_user_list', () => {
beforeEach(() => {
wrapper = mount(NewUserList, {
- localVue,
store: createStore({ projectId: '1' }),
provide: {
featureFlagsPath: '/feature_flags',
@@ -45,11 +43,11 @@ describe('user_lists/components/new_user_list', () => {
describe('create', () => {
describe('success', () => {
- beforeEach(() => {
+ beforeEach(async () => {
Api.createFeatureFlagUserList.mockResolvedValue({ data: userList });
setInputValue('test');
click('save-user-list');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('should create a user list with the entered name', () => {
@@ -84,7 +82,7 @@ describe('user_lists/components/new_user_list', () => {
it('should dismiss the error when the dismiss button is clicked', async () => {
alert.find('button').trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(alert.exists()).toBe(false);
});
diff --git a/spec/frontend/user_lists/components/user_list_spec.js b/spec/frontend/user_lists/components/user_list_spec.js
index f016b5091d9..88dad06938b 100644
--- a/spec/frontend/user_lists/components/user_list_spec.js
+++ b/spec/frontend/user_lists/components/user_list_spec.js
@@ -1,7 +1,7 @@
import { GlAlert, GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { uniq } from 'lodash';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import Api from '~/api';
import UserList from '~/user_lists/components/user_list.vue';
@@ -57,12 +57,12 @@ describe('User List', () => {
describe('success', () => {
let userIds;
- beforeEach(() => {
+ beforeEach(async () => {
userIds = parseUserIds(userList.user_xids);
Api.fetchFeatureFlagUserList.mockResolvedValueOnce({ data: userList });
factory();
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('requests the user list on mount', () => {
@@ -101,10 +101,10 @@ describe('User List', () => {
beforeEach(async () => {
Api.updateFeatureFlagUserList.mockResolvedValue(userList);
click('add-users');
- await wrapper.vm.$nextTick();
+ await nextTick();
wrapper.find('#add-user-ids').setValue(`${stringifyUserIds(newIds)},`);
click('confirm-add-user-ids');
- await wrapper.vm.$nextTick();
+ await nextTick();
[[, { user_xids: receivedUserIds }]] = Api.updateFeatureFlagUserList.mock.calls;
parsedReceivedUserIds = parseUserIds(receivedUserIds);
});
@@ -140,7 +140,7 @@ describe('User List', () => {
beforeEach(async () => {
Api.updateFeatureFlagUserList.mockResolvedValue(userList);
click('delete-user-id');
- await wrapper.vm.$nextTick();
+ await nextTick();
[[, { user_xids: receivedUserIds }]] = Api.updateFeatureFlagUserList.mock.calls;
});
@@ -159,11 +159,11 @@ describe('User List', () => {
describe('error', () => {
const findAlert = () => wrapper.find(GlAlert);
- beforeEach(() => {
+ beforeEach(async () => {
Api.fetchFeatureFlagUserList.mockRejectedValue();
factory();
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays the alert message', () => {
@@ -175,18 +175,18 @@ describe('User List', () => {
const alert = findAlert();
alert.find('button').trigger('click');
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(alert.exists()).toBe(false);
});
});
describe('empty list', () => {
- beforeEach(() => {
+ beforeEach(async () => {
Api.fetchFeatureFlagUserList.mockResolvedValueOnce({ data: { ...userList, user_xids: '' } });
factory();
- return wrapper.vm.$nextTick();
+ await nextTick();
});
it('displays an empty state', () => {
diff --git a/spec/frontend/user_lists/components/user_lists_spec.js b/spec/frontend/user_lists/components/user_lists_spec.js
index 7a33c6faac9..10742c029c1 100644
--- a/spec/frontend/user_lists/components/user_lists_spec.js
+++ b/spec/frontend/user_lists/components/user_lists_spec.js
@@ -1,7 +1,7 @@
import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import { within } from '@testing-library/dom';
import { mount, createWrapper } from '@vue/test-utils';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
@@ -82,7 +82,7 @@ describe('~/user_lists/components/user_lists.vue', () => {
factory();
await waitForPromises();
- await Vue.nextTick();
+ await nextTick();
emptyState = wrapper.findComponent(GlEmptyState);
});
@@ -130,7 +130,7 @@ describe('~/user_lists/components/user_lists.vue', () => {
factory();
jest.spyOn(store, 'dispatch');
- await Vue.nextTick();
+ await nextTick();
table = wrapper.findComponent(UserListsTable);
});
@@ -171,7 +171,7 @@ describe('~/user_lists/components/user_lists.vue', () => {
Api.fetchFeatureFlagUserLists.mockRejectedValue();
factory();
- await Vue.nextTick();
+ await nextTick();
});
it('should render error state', () => {
diff --git a/spec/frontend/user_lists/components/user_lists_table_spec.js b/spec/frontend/user_lists/components/user_lists_table_spec.js
index 7f4d510a39c..63587703392 100644
--- a/spec/frontend/user_lists/components/user_lists_table_spec.js
+++ b/spec/frontend/user_lists/components/user_lists_table_spec.js
@@ -1,6 +1,7 @@
import { GlModal } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import * as timeago from 'timeago.js';
+import { nextTick } from 'vue';
import UserListsTable from '~/user_lists/components/user_lists_table.vue';
import { userList } from '../../feature_flags/mock_data';
@@ -56,43 +57,40 @@ describe('User Lists Table', () => {
});
describe('delete button', () => {
- it('should display the confirmation modal', () => {
+ it('should display the confirmation modal', async () => {
const modal = wrapper.find(GlModal);
wrapper.find('[data-testid="delete-user-list"]').trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(modal.text()).toContain(`Delete ${userList.name}?`);
- expect(modal.text()).toContain(`User list ${userList.name} will be removed.`);
- });
+ await nextTick();
+ expect(modal.text()).toContain(`Delete ${userList.name}?`);
+ expect(modal.text()).toContain(`User list ${userList.name} will be removed.`);
});
});
describe('confirmation modal', () => {
let modal;
- beforeEach(() => {
+ beforeEach(async () => {
modal = wrapper.find(GlModal);
wrapper.find('button').trigger('click');
- return wrapper.vm.$nextTick();
+ await nextTick();
});
- it('should emit delete with list on confirmation', () => {
+ it('should emit delete with list on confirmation', async () => {
modal.find('[data-testid="modal-confirm"]').trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('delete')).toEqual([[userLists[0]]]);
- });
+ await nextTick();
+ expect(wrapper.emitted('delete')).toEqual([[userLists[0]]]);
});
- it('should not emit delete with list when not confirmed', () => {
+ it('should not emit delete with list when not confirmed', async () => {
modal.find('button').trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('delete')).toBeUndefined();
- });
+ await nextTick();
+ expect(wrapper.emitted('delete')).toBeUndefined();
});
});
});
diff --git a/spec/frontend/vue_alerts_spec.js b/spec/frontend/vue_alerts_spec.js
index 30be606292f..1952eea4a01 100644
--- a/spec/frontend/vue_alerts_spec.js
+++ b/spec/frontend/vue_alerts_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import { nextTick } from 'vue';
import { setHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
import initVueAlerts from '~/vue_alerts';
@@ -75,10 +75,9 @@ describe('VueAlerts', () => {
});
describe('when dismissed', () => {
- beforeEach(() => {
+ beforeEach(async () => {
findAlertDismiss(findAlerts()[0]).click();
-
- return Vue.nextTick();
+ await nextTick();
});
it('hides the alert', () => {
diff --git a/spec/frontend/vue_mr_widget/components/mr_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/mr_widget_memory_usage_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js
index f55d313a719..c0a30a5093d 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_memory_usage_spec.js
@@ -1,6 +1,7 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
+import waitForPromises from 'helpers/wait_for_promises';
import MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
@@ -152,23 +153,18 @@ describe('MemoryUsage', () => {
});
describe('loadMetrics', () => {
- const returnServicePromise = () =>
- new Promise((resolve) => {
- resolve({
- data: metricsMockData,
- });
+ it('should load metrics data using MRWidgetService', async () => {
+ jest.spyOn(MRWidgetService, 'fetchMetrics').mockResolvedValue({
+ data: metricsMockData,
});
-
- it('should load metrics data using MRWidgetService', (done) => {
- jest.spyOn(MRWidgetService, 'fetchMetrics').mockReturnValue(returnServicePromise(true));
jest.spyOn(vm, 'computeGraphData').mockImplementation(() => {});
vm.loadMetrics();
- setImmediate(() => {
- expect(MRWidgetService.fetchMetrics).toHaveBeenCalledWith(url);
- expect(vm.computeGraphData).toHaveBeenCalledWith(metrics, deployment_time);
- done();
- });
+
+ await waitForPromises();
+
+ expect(MRWidgetService.fetchMetrics).toHaveBeenCalledWith(url);
+ expect(vm.computeGraphData).toHaveBeenCalledWith(metrics, deployment_time);
});
});
});
@@ -184,7 +180,7 @@ describe('MemoryUsage', () => {
vm.hasMetrics = false;
vm.loadFailed = false;
- Vue.nextTick(() => {
+ nextTick(() => {
expect(el.querySelector('.js-usage-info.usage-info-loading')).toBeDefined();
expect(el.querySelector('.js-usage-info .usage-info-load-spinner')).toBeDefined();
@@ -203,7 +199,7 @@ describe('MemoryUsage', () => {
vm.loadFailed = false;
vm.memoryMetrics = metricsMockData.metrics.memory_values[0].values;
- Vue.nextTick(() => {
+ nextTick(() => {
expect(el.querySelector('.memory-graph-container')).toBeDefined();
expect(el.querySelector('.js-usage-info').innerText).toContain(messages.hasMetrics);
done();
@@ -215,7 +211,7 @@ describe('MemoryUsage', () => {
vm.hasMetrics = false;
vm.loadFailed = true;
- Vue.nextTick(() => {
+ nextTick(() => {
expect(el.querySelector('.js-usage-info.usage-info-failed')).toBeDefined();
expect(el.querySelector('.js-usage-info').innerText).toContain(messages.loadFailed);
@@ -228,7 +224,7 @@ describe('MemoryUsage', () => {
vm.hasMetrics = false;
vm.loadFailed = false;
- Vue.nextTick(() => {
+ nextTick(() => {
expect(el.querySelector('.js-usage-info.usage-info-unavailable')).toBeDefined();
expect(el.querySelector('.js-usage-info').innerText).toContain(messages.metricsUnavailable);
diff --git a/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap b/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap
index a124008b36a..98297630792 100644
--- a/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap
+++ b/spec/frontend/vue_mr_widget/components/states/__snapshots__/mr_widget_pipeline_failed_spec.js.snap
@@ -16,7 +16,7 @@ exports[`PipelineFailed should render error message with a disabled merge button
class="bold"
>
<gl-sprintf-stub
- message="The pipeline for this merge request did not complete. Push a new commit to fix the failure, or check the %{linkStart}troubleshooting documentation%{linkEnd} to see other possible actions."
+ message="Merge blocked: pipeline must succeed. Push a commit that fixes the failure, or %{linkStart}learn about other solutions.%{linkEnd}"
/>
</span>
</div>
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/merge_failed_pipeline_confirmation_dialog_spec.js b/spec/frontend/vue_mr_widget/components/states/merge_failed_pipeline_confirmation_dialog_spec.js
new file mode 100644
index 00000000000..0e1c38437f0
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/states/merge_failed_pipeline_confirmation_dialog_spec.js
@@ -0,0 +1,78 @@
+import { shallowMount } from '@vue/test-utils';
+import MergeFailedPipelineConfirmationDialog from '~/vue_merge_request_widget/components/states/merge_failed_pipeline_confirmation_dialog.vue';
+import { trimText } from 'helpers/text_helper';
+
+describe('MergeFailedPipelineConfirmationDialog', () => {
+ let wrapper;
+
+ const GlModal = {
+ template: `
+ <div>
+ <slot></slot>
+ <slot name="modal-footer"></slot>
+ </div>
+ `,
+ methods: {
+ hide: jest.fn(),
+ },
+ };
+
+ const createComponent = () => {
+ wrapper = shallowMount(MergeFailedPipelineConfirmationDialog, {
+ propsData: {
+ visible: true,
+ },
+ stubs: {
+ GlModal,
+ },
+ attachTo: document.body,
+ });
+ };
+
+ const findModal = () => wrapper.findComponent(GlModal);
+ const findMergeBtn = () => wrapper.find('[data-testid="merge-unverified-changes"]');
+ const findCancelBtn = () => wrapper.find('[data-testid="merge-cancel-btn"]');
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should render informational text explaining why merging immediately can be dangerous', () => {
+ expect(trimText(wrapper.text())).toContain(
+ 'The latest pipeline for this merge request did not succeed. The latest changes are unverified. Are you sure you want to attempt to merge?',
+ );
+ });
+
+ it('should emit the mergeWithFailedPipeline event', () => {
+ findMergeBtn().vm.$emit('click');
+
+ expect(wrapper.emitted('mergeWithFailedPipeline')).toBeTruthy();
+ });
+
+ it('when the cancel button is clicked should emit cancel and call hide', () => {
+ jest.spyOn(findModal().vm, 'hide');
+
+ findCancelBtn().vm.$emit('click');
+
+ expect(wrapper.emitted('cancel')).toBeTruthy();
+ expect(findModal().vm.hide).toHaveBeenCalled();
+ });
+
+ it('should emit cancel when the hide event is emitted', () => {
+ findModal().vm.$emit('hide');
+
+ expect(wrapper.emitted('cancel')).toBeTruthy();
+ });
+
+ it('when modal is shown it will focus the cancel button', () => {
+ jest.spyOn(findCancelBtn().element, 'focus');
+
+ findModal().vm.$emit('shown');
+
+ expect(findCancelBtn().element.focus).toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js
index 52a56af454f..7387ed2d5e9 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_auto_merge_enabled_spec.js
@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { trimText } from 'helpers/text_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import autoMergeEnabledComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue';
import { MWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
import eventHub from '~/vue_merge_request_widget/event_hub';
@@ -185,7 +186,7 @@ describe('MRWidgetAutoMergeEnabled', () => {
describe('methods', () => {
describe('cancelAutomaticMerge', () => {
- it('should set flag and call service then tell main component to update the widget with data', (done) => {
+ it('should set flag and call service then tell main component to update the widget with data', async () => {
factory({
...defaultMrProps(),
});
@@ -201,20 +202,20 @@ describe('MRWidgetAutoMergeEnabled', () => {
);
wrapper.vm.cancelAutomaticMerge();
- setImmediate(() => {
- expect(wrapper.vm.isCancellingAutoMerge).toBeTruthy();
- if (mergeRequestWidgetGraphql) {
- expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
- } else {
- expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
- }
- done();
- });
+
+ await waitForPromises();
+
+ expect(wrapper.vm.isCancellingAutoMerge).toBeTruthy();
+ if (mergeRequestWidgetGraphql) {
+ expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
+ } else {
+ expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
+ }
});
});
describe('removeSourceBranch', () => {
- it('should set flag and call service then request main component to update the widget', (done) => {
+ it('should set flag and call service then request main component to update the widget', async () => {
factory({
...defaultMrProps(),
});
@@ -227,14 +228,14 @@ describe('MRWidgetAutoMergeEnabled', () => {
);
wrapper.vm.removeSourceBranch();
- setImmediate(() => {
- expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
- expect(wrapper.vm.service.merge).toHaveBeenCalledWith({
- sha,
- auto_merge_strategy: MWPS_MERGE_STRATEGY,
- should_remove_source_branch: true,
- });
- done();
+
+ await waitForPromises();
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
+ expect(wrapper.vm.service.merge).toHaveBeenCalledWith({
+ sha,
+ auto_merge_strategy: MWPS_MERGE_STRATEGY,
+ should_remove_source_branch: true,
});
});
});
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_merged_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js
index e0f1f091129..7d86e453bc7 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_merged_spec.js
@@ -1,6 +1,7 @@
import { getByRole } from '@testing-library/dom';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import { OPEN_REVERT_MODAL, OPEN_CHERRY_PICK_MODAL } from '~/projects/commit/constants';
import modalEventHub from '~/projects/commit/event_hub';
import mergedComponent from '~/vue_merge_request_widget/components/states/mr_widget_merged.vue';
@@ -127,7 +128,7 @@ describe('MRWidgetMerged', () => {
describe('methods', () => {
describe('removeSourceBranch', () => {
- it('should set flag and call service then request main component to update the widget', (done) => {
+ it('should set flag and call service then request main component to update the widget', async () => {
jest.spyOn(vm.service, 'removeSourceBranch').mockReturnValue(
new Promise((resolve) => {
resolve({
@@ -139,14 +140,14 @@ describe('MRWidgetMerged', () => {
);
vm.removeSourceBranch();
- setImmediate(() => {
- const args = eventHub.$emit.mock.calls[0];
-
- expect(vm.isMakingRequest).toEqual(true);
- expect(args[0]).toEqual('MRWidgetUpdateRequested');
- expect(args[1]).not.toThrow();
- done();
- });
+
+ await waitForPromises();
+
+ const args = eventHub.$emit.mock.calls[0];
+
+ expect(vm.isMakingRequest).toEqual(true);
+ expect(args[0]).toEqual('MRWidgetUpdateRequested');
+ expect(args[1]).not.toThrow();
});
});
});
@@ -200,7 +201,7 @@ describe('MRWidgetMerged', () => {
it('hides button to copy commit SHA if SHA does not exist', (done) => {
vm.mr.mergeCommitSha = null;
- Vue.nextTick(() => {
+ nextTick(() => {
expect(selectors.copyMergeShaButton).toBe(null);
expect(vm.$el.querySelector('.mr-info-list').innerText).not.toContain('with');
done();
@@ -216,7 +217,7 @@ describe('MRWidgetMerged', () => {
it('should not show source branch deleted text', (done) => {
vm.mr.sourceBranchRemoved = false;
- Vue.nextTick(() => {
+ nextTick(() => {
expect(vm.$el.innerText).not.toContain('The source branch has been deleted');
done();
});
@@ -226,7 +227,7 @@ describe('MRWidgetMerged', () => {
vm.mr.isRemovingSourceBranch = true;
vm.mr.sourceBranchRemoved = false;
- Vue.nextTick(() => {
+ nextTick(() => {
expect(vm.$el.innerText).toContain('The source branch is being deleted');
expect(vm.$el.innerText).not.toContain('The source branch has been deleted');
done();
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_merging_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_merging_spec.js
index e6b2e9fa176..e16c897a49b 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_merging_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_merging_spec.js
@@ -1,6 +1,11 @@
import { shallowMount } from '@vue/test-utils';
+import simplePoll from '~/lib/utils/simple_poll';
import MrWidgetMerging from '~/vue_merge_request_widget/components/states/mr_widget_merging.vue';
+jest.mock('~/lib/utils/simple_poll', () =>
+ jest.fn().mockImplementation(jest.requireActual('~/lib/utils/simple_poll').default),
+);
+
describe('MRWidgetMerging', () => {
let wrapper;
@@ -11,6 +16,10 @@ describe('MRWidgetMerging', () => {
mr: {
targetBranchPath: '/branch-path',
targetBranch: 'branch',
+ transitionStateMachine() {},
+ },
+ service: {
+ poll: jest.fn().mockResolvedValue(),
},
},
stubs: {
@@ -46,4 +55,20 @@ describe('MRWidgetMerging', () => {
expect(wrapper.find('a').attributes('href')).toBe('/branch-path');
});
+
+ describe('initiateMergePolling', () => {
+ it('should call simplePoll', () => {
+ wrapper.vm.initiateMergePolling();
+
+ expect(simplePoll).toHaveBeenCalledWith(expect.any(Function), { timeout: 0 });
+ });
+
+ it('should call handleMergePolling', () => {
+ jest.spyOn(wrapper.vm, 'handleMergePolling').mockImplementation(() => {});
+
+ wrapper.vm.initiateMergePolling();
+
+ expect(wrapper.vm.handleMergePolling).toHaveBeenCalled();
+ });
+ });
});
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 936d673768c..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', () => {
@@ -40,7 +41,7 @@ describe('MRWidgetMissingBranch', () => {
async ({ sourceBranchRemoved, branchName }) => {
await factory(sourceBranchRemoved, mergeRequestWidgetGraphql);
- expect(wrapper.find('[data-testid="missingBranchName"]').text()).toContain(branchName);
+ expect(wrapper.find('[data-testid="widget-content"]').text()).toContain(branchName);
},
);
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
index 2c04905d3a9..c7c0b69425d 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_nothing_to_merge_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import NothingToMerge from '~/vue_merge_request_widget/components/states/nothing_to_merge.vue';
describe('NothingToMerge', () => {
@@ -20,7 +20,7 @@ describe('NothingToMerge', () => {
it('should not show new blob link if there is no link available', () => {
vm.mr.newBlobPath = null;
- Vue.nextTick(() => {
+ nextTick(() => {
expect(vm.$el.querySelector('[data-testid="createFileButton"]')).toEqual(null);
});
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index f4ecebbb40c..78585ed75bc 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -1,12 +1,14 @@
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import { nextTick } from 'vue';
import { GlSprintf } from '@gitlab/ui';
+import waitForPromises from 'helpers/wait_for_promises';
import simplePoll from '~/lib/utils/simple_poll';
import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue';
import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue';
import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue';
import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue';
import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue';
+import MergeFailedPipelineConfirmationDialog from '~/vue_merge_request_widget/components/states/merge_failed_pipeline_confirmation_dialog.vue';
import { MWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
import eventHub from '~/vue_merge_request_widget/event_hub';
@@ -61,6 +63,11 @@ const createTestService = () => ({
});
let wrapper;
+
+const findMergeButton = () => wrapper.find('[data-testid="merge-button"]');
+const findPipelineFailedConfirmModal = () =>
+ wrapper.findComponent(MergeFailedPipelineConfirmationDialog);
+
const createComponent = (customConfig = {}, mergeRequestWidgetGraphql = false) => {
wrapper = shallowMount(ReadyToMerge, {
propsData: {
@@ -132,33 +139,13 @@ describe('ReadyToMerge', () => {
});
});
- describe('mergeButtonVariant', () => {
+ describe('Merge Button Variant', () => {
it('defaults to confirm class', () => {
createComponent({
mr: { availableAutoMergeStrategies: [] },
});
- expect(wrapper.vm.mergeButtonVariant).toEqual('confirm');
- });
-
- it('returns confirm class for success status', () => {
- createComponent({
- mr: { availableAutoMergeStrategies: [], pipeline: true },
- });
-
- expect(wrapper.vm.mergeButtonVariant).toEqual('confirm');
- });
-
- it('returns confirm class for pending status', () => {
- createComponent();
-
- expect(wrapper.vm.mergeButtonVariant).toEqual('confirm');
- });
-
- it('returns danger class for failed status', () => {
- createComponent({ mr: { hasCI: true } });
-
- expect(wrapper.vm.mergeButtonVariant).toEqual('danger');
+ expect(findMergeButton().attributes('variant')).toBe('confirm');
});
});
@@ -196,7 +183,7 @@ describe('ReadyToMerge', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ isMergingImmediately: true });
- await Vue.nextTick();
+ await nextTick();
expect(wrapper.vm.mergeButtonText).toEqual('Merge in progress');
});
@@ -266,7 +253,7 @@ describe('ReadyToMerge', () => {
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ isMakingRequest: true });
- await Vue.nextTick();
+ await nextTick();
expect(wrapper.vm.isMergeButtonDisabled).toBe(true);
});
@@ -275,110 +262,86 @@ describe('ReadyToMerge', () => {
describe('methods', () => {
describe('handleMergeButtonClick', () => {
- const returnPromise = (status) =>
- new Promise((resolve) => {
- resolve({
- data: {
- status,
- },
- });
- });
+ const response = (status) => ({
+ data: {
+ status,
+ },
+ });
- it('should handle merge when pipeline succeeds', (done) => {
+ it('should handle merge when pipeline succeeds', async () => {
createComponent();
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
jest
.spyOn(wrapper.vm.service, 'merge')
- .mockReturnValue(returnPromise('merge_when_pipeline_succeeds'));
+ .mockResolvedValue(response('merge_when_pipeline_succeeds'));
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({ removeSourceBranch: false });
wrapper.vm.handleMergeButtonClick(true);
- setImmediate(() => {
- expect(wrapper.vm.isMakingRequest).toBeTruthy();
- expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
- expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', {
- transition: 'start-auto-merge',
- });
+ await waitForPromises();
- const params = wrapper.vm.service.merge.mock.calls[0][0];
-
- expect(params).toEqual(
- expect.objectContaining({
- sha: wrapper.vm.mr.sha,
- commit_message: wrapper.vm.mr.commitMessage,
- should_remove_source_branch: false,
- auto_merge_strategy: 'merge_when_pipeline_succeeds',
- }),
- );
- done();
+ expect(wrapper.vm.isMakingRequest).toBeTruthy();
+ expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
+ expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', {
+ transition: 'start-auto-merge',
});
+
+ const params = wrapper.vm.service.merge.mock.calls[0][0];
+
+ expect(params).toEqual(
+ expect.objectContaining({
+ sha: wrapper.vm.mr.sha,
+ commit_message: wrapper.vm.mr.commitMessage,
+ should_remove_source_branch: false,
+ auto_merge_strategy: 'merge_when_pipeline_succeeds',
+ }),
+ );
});
- it('should handle merge failed', (done) => {
+ it('should handle merge failed', async () => {
createComponent();
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- jest.spyOn(wrapper.vm.service, 'merge').mockReturnValue(returnPromise('failed'));
+ jest.spyOn(wrapper.vm.service, 'merge').mockResolvedValue(response('failed'));
wrapper.vm.handleMergeButtonClick(false, true);
- setImmediate(() => {
- expect(wrapper.vm.isMakingRequest).toBeTruthy();
- expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined);
+ await waitForPromises();
- const params = wrapper.vm.service.merge.mock.calls[0][0];
+ expect(wrapper.vm.isMakingRequest).toBeTruthy();
+ expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined);
- expect(params.should_remove_source_branch).toBeTruthy();
- expect(params.auto_merge_strategy).toBeUndefined();
- done();
- });
+ const params = wrapper.vm.service.merge.mock.calls[0][0];
+
+ expect(params.should_remove_source_branch).toBeTruthy();
+ expect(params.auto_merge_strategy).toBeUndefined();
});
- it('should handle merge action accepted case', (done) => {
+ it('should handle merge action accepted case', async () => {
createComponent();
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- jest.spyOn(wrapper.vm.service, 'merge').mockReturnValue(returnPromise('success'));
- jest.spyOn(wrapper.vm, 'initiateMergePolling').mockImplementation(() => {});
+ jest.spyOn(wrapper.vm.service, 'merge').mockResolvedValue(response('success'));
+ jest.spyOn(wrapper.vm.mr, 'transitionStateMachine');
wrapper.vm.handleMergeButtonClick();
expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', {
transition: 'start-merge',
});
- setImmediate(() => {
- expect(wrapper.vm.isMakingRequest).toBeTruthy();
- expect(wrapper.vm.initiateMergePolling).toHaveBeenCalled();
-
- const params = wrapper.vm.service.merge.mock.calls[0][0];
+ await waitForPromises();
- expect(params.should_remove_source_branch).toBeTruthy();
- expect(params.auto_merge_strategy).toBeUndefined();
- done();
+ expect(wrapper.vm.isMakingRequest).toBeTruthy();
+ expect(wrapper.vm.mr.transitionStateMachine).toHaveBeenCalledWith({
+ transition: 'start-merge',
});
- });
- });
-
- describe('initiateMergePolling', () => {
- it('should call simplePoll', () => {
- createComponent();
-
- wrapper.vm.initiateMergePolling();
-
- expect(simplePoll).toHaveBeenCalledWith(expect.any(Function), { timeout: 0 });
- });
- it('should call handleMergePolling', () => {
- createComponent();
-
- jest.spyOn(wrapper.vm, 'handleMergePolling').mockImplementation(() => {});
+ const params = wrapper.vm.service.merge.mock.calls[0][0];
- wrapper.vm.initiateMergePolling();
-
- expect(wrapper.vm.handleMergePolling).toHaveBeenCalled();
+ expect(params.should_remove_source_branch).toBeTruthy();
+ expect(params.auto_merge_strategy).toBeUndefined();
});
});
@@ -396,20 +359,17 @@ describe('ReadyToMerge', () => {
});
describe('handleRemoveBranchPolling', () => {
- const returnPromise = (state) =>
- new Promise((resolve) => {
- resolve({
- data: {
- source_branch_exists: state,
- },
- });
- });
+ const response = (state) => ({
+ data: {
+ source_branch_exists: state,
+ },
+ });
- it('should call start and stop polling when MR merged', (done) => {
+ it('should call start and stop polling when MR merged', async () => {
createComponent();
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- jest.spyOn(wrapper.vm.service, 'poll').mockReturnValue(returnPromise(false));
+ jest.spyOn(wrapper.vm.service, 'poll').mockResolvedValue(response(false));
let cpc = false; // continuePollingCalled
let spc = false; // stopPollingCalled
@@ -422,28 +382,27 @@ describe('ReadyToMerge', () => {
spc = true;
},
);
- setImmediate(() => {
- expect(wrapper.vm.service.poll).toHaveBeenCalled();
- const args = eventHub.$emit.mock.calls[0];
+ await waitForPromises();
- expect(args[0]).toEqual('MRWidgetUpdateRequested');
- expect(args[1]).toBeDefined();
- args[1]();
+ expect(wrapper.vm.service.poll).toHaveBeenCalled();
- expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [false]);
+ const args = eventHub.$emit.mock.calls[0];
- expect(cpc).toBeFalsy();
- expect(spc).toBeTruthy();
+ expect(args[0]).toEqual('MRWidgetUpdateRequested');
+ expect(args[1]).toBeDefined();
+ args[1]();
- done();
- });
+ expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [false]);
+
+ expect(cpc).toBeFalsy();
+ expect(spc).toBeTruthy();
});
- it('should continue polling until MR is merged', (done) => {
+ it('should continue polling until MR is merged', async () => {
createComponent();
- jest.spyOn(wrapper.vm.service, 'poll').mockReturnValue(returnPromise(true));
+ jest.spyOn(wrapper.vm.service, 'poll').mockResolvedValue(response(true));
let cpc = false; // continuePollingCalled
let spc = false; // stopPollingCalled
@@ -456,12 +415,11 @@ describe('ReadyToMerge', () => {
spc = true;
},
);
- setImmediate(() => {
- expect(cpc).toBeTruthy();
- expect(spc).toBeFalsy();
- done();
- });
+ await waitForPromises();
+
+ expect(cpc).toBeTruthy();
+ expect(spc).toBeFalsy();
});
});
});
@@ -710,7 +668,7 @@ describe('ReadyToMerge', () => {
commitsWithoutMergeCommits: {},
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(findCommitEditElements().length).toBe(2);
});
@@ -794,4 +752,24 @@ describe('ReadyToMerge', () => {
});
});
});
+
+ describe('Merge button when pipeline has failed', () => {
+ beforeEach(() => {
+ createComponent({
+ mr: { pipeline: {}, isPipelineFailed: true, availableAutoMergeStrategies: [] },
+ });
+ });
+
+ it('should display the correct merge text', () => {
+ expect(findMergeButton().text()).toBe('Merge...');
+ });
+
+ it('should display confirmation modal when merge button is clicked', async () => {
+ expect(findPipelineFailedConfirmModal().props()).toEqual({ visible: false });
+
+ await findMergeButton().vm.$emit('click');
+
+ expect(findPipelineFailedConfirmModal().props()).toEqual({ visible: true });
+ });
+ });
});
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
index 6abdbd11f5e..6ea2e8675d3 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
@@ -1,16 +1,13 @@
import { GlFormCheckbox, GlLink } from '@gitlab/ui';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue';
import { SQUASH_BEFORE_MERGE } from '~/vue_merge_request_widget/i18n';
-const localVue = createLocalVue();
-
describe('Squash before merge component', () => {
let wrapper;
const createComponent = (props) => {
- wrapper = shallowMount(localVue.extend(SquashBeforeMerge), {
- localVue,
+ wrapper = shallowMount(SquashBeforeMerge, {
propsData: {
...props,
},
diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js
index 4070ca8d8dc..4998147c6b6 100644
--- a/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js
@@ -1,4 +1,5 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
+import waitForPromises from 'helpers/wait_for_promises';
import WorkInProgress from '~/vue_merge_request_widget/components/states/work_in_progress.vue';
import toast from '~/vue_shared/plugins/global_toast';
import eventHub from '~/vue_merge_request_widget/event_hub';
@@ -47,7 +48,7 @@ describe('Wip', () => {
};
describe('handleRemoveDraft', () => {
- it('should make a request to service and handle response', (done) => {
+ it('should make a request to service and handle response', async () => {
const vm = createComponent();
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
@@ -60,12 +61,12 @@ describe('Wip', () => {
);
vm.handleRemoveDraft();
- setImmediate(() => {
- expect(vm.isMakingRequest).toBeTruthy();
- expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
- expect(toast).toHaveBeenCalledWith('Marked as ready. Merging is now allowed.');
- done();
- });
+
+ await waitForPromises();
+
+ expect(vm.isMakingRequest).toBeTruthy();
+ expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
+ expect(toast).toHaveBeenCalledWith('Marked as ready. Merging is now allowed.');
});
});
});
@@ -91,13 +92,12 @@ describe('Wip', () => {
);
});
- it('should not show removeWIP button is user cannot update MR', (done) => {
+ it('should not show removeWIP button is user cannot update MR', async () => {
vm.mr.removeWIPPath = '';
- Vue.nextTick(() => {
- expect(el.querySelector('.js-remove-draft')).toEqual(null);
- done();
- });
+ await nextTick();
+
+ expect(el.querySelector('.js-remove-draft')).toEqual(null);
});
});
});
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_mr_widget/deployment/deployment_actions_spec.js b/spec/frontend/vue_mr_widget/deployment/deployment_actions_spec.js
index 31ade17e50a..a285d26f404 100644
--- a/spec/frontend/vue_mr_widget/deployment/deployment_actions_spec.js
+++ b/spec/frontend/vue_mr_widget/deployment/deployment_actions_spec.js
@@ -1,5 +1,7 @@
import { mount } from '@vue/test-utils';
+import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
+import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import { visitUrl } from '~/lib/utils/url_utility';
import {
CREATED,
@@ -20,6 +22,11 @@ import {
jest.mock('~/flash');
jest.mock('~/lib/utils/url_utility');
+jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal', () => {
+ return {
+ confirmAction: jest.fn(),
+ };
+});
describe('DeploymentAction component', () => {
let wrapper;
@@ -51,6 +58,7 @@ describe('DeploymentAction component', () => {
afterEach(() => {
wrapper.destroy();
+ confirmAction.mockReset();
});
describe('actions do not appear when conditions are unmet', () => {
@@ -95,16 +103,6 @@ describe('DeploymentAction component', () => {
'$configConst action',
({ configConst, computedDeploymentStatus, displayConditionChanges, finderFn, endpoint }) => {
describe(`${configConst} action`, () => {
- const confirmAction = () => {
- jest.spyOn(window, 'confirm').mockReturnValueOnce(true);
- finderFn().trigger('click');
- };
-
- const rejectAction = () => {
- jest.spyOn(window, 'confirm').mockReturnValueOnce(false);
- finderFn().trigger('click');
- };
-
beforeEach(() => {
factory({
propsData: {
@@ -125,13 +123,18 @@ describe('DeploymentAction component', () => {
describe('should show a confirm dialog but not call executeInlineAction when declined', () => {
beforeEach(() => {
executeActionSpy.mockResolvedValueOnce();
- rejectAction();
+ confirmAction.mockResolvedValueOnce(false);
+ finderFn().trigger('click');
});
it('should show the confirm dialog', () => {
- expect(window.confirm).toHaveBeenCalled();
- expect(window.confirm).toHaveBeenCalledWith(
+ expect(confirmAction).toHaveBeenCalled();
+ expect(confirmAction).toHaveBeenCalledWith(
actionButtonMocks[configConst].confirmMessage,
+ {
+ primaryBtnVariant: actionButtonMocks[configConst].buttonVariant,
+ primaryBtnText: actionButtonMocks[configConst].buttonText,
+ },
);
});
@@ -143,13 +146,18 @@ describe('DeploymentAction component', () => {
describe('should show a confirm dialog and call executeInlineAction when accepted', () => {
beforeEach(() => {
executeActionSpy.mockResolvedValueOnce();
- confirmAction();
+ confirmAction.mockResolvedValueOnce(true);
+ finderFn().trigger('click');
});
it('should show the confirm dialog', () => {
- expect(window.confirm).toHaveBeenCalled();
- expect(window.confirm).toHaveBeenCalledWith(
+ expect(confirmAction).toHaveBeenCalled();
+ expect(confirmAction).toHaveBeenCalledWith(
actionButtonMocks[configConst].confirmMessage,
+ {
+ primaryBtnVariant: actionButtonMocks[configConst].buttonVariant,
+ primaryBtnText: actionButtonMocks[configConst].buttonText,
+ },
);
});
@@ -164,11 +172,15 @@ describe('DeploymentAction component', () => {
describe('response includes redirect_url', () => {
const url = '/root/example';
- beforeEach(() => {
+ beforeEach(async () => {
executeActionSpy.mockResolvedValueOnce({
data: { redirect_url: url },
});
- confirmAction();
+
+ await waitForPromises();
+
+ confirmAction.mockResolvedValueOnce(true);
+ finderFn().trigger('click');
});
it('calls visit url with the redirect_url', () => {
@@ -178,9 +190,13 @@ describe('DeploymentAction component', () => {
});
describe('it should call the executeAction method ', () => {
- beforeEach(() => {
+ beforeEach(async () => {
jest.spyOn(wrapper.vm, 'executeAction').mockImplementation();
- confirmAction();
+
+ await waitForPromises();
+
+ confirmAction.mockResolvedValueOnce(true);
+ finderFn().trigger('click');
});
it('calls with the expected arguments', () => {
@@ -193,9 +209,13 @@ describe('DeploymentAction component', () => {
});
describe('when executeInlineAction errors', () => {
- beforeEach(() => {
+ beforeEach(async () => {
executeActionSpy.mockRejectedValueOnce();
- confirmAction();
+
+ await waitForPromises();
+
+ confirmAction.mockResolvedValueOnce(true);
+ finderFn().trigger('click');
});
it('should call createFlash with error message', () => {
diff --git a/spec/frontend/vue_mr_widget/deployment/deployment_mock_data.js b/spec/frontend/vue_mr_widget/deployment/deployment_mock_data.js
index 2083dc88681..e98b1160ae4 100644
--- a/spec/frontend/vue_mr_widget/deployment/deployment_mock_data.js
+++ b/spec/frontend/vue_mr_widget/deployment/deployment_mock_data.js
@@ -9,6 +9,7 @@ const actionButtonMocks = {
[STOPPING]: {
actionName: STOPPING,
buttonText: 'Stop environment',
+ buttonVariant: 'danger',
busyText: 'This environment is being deployed',
confirmMessage: 'Are you sure you want to stop this environment?',
errorMessage: 'Something went wrong while stopping this environment. Please try again.',
@@ -16,6 +17,7 @@ const actionButtonMocks = {
[DEPLOYING]: {
actionName: DEPLOYING,
buttonText: 'Deploy',
+ buttonVariant: 'confirm',
busyText: 'This environment is being deployed',
confirmMessage: 'Are you sure you want to deploy this environment?',
errorMessage: 'Something went wrong while deploying this environment. Please try again.',
@@ -23,6 +25,7 @@ const actionButtonMocks = {
[REDEPLOYING]: {
actionName: REDEPLOYING,
buttonText: 'Re-deploy',
+ buttonVariant: 'confirm',
busyText: 'This environment is being re-deployed',
confirmMessage: 'Are you sure you want to re-deploy this environment?',
errorMessage: 'Something went wrong while deploying this environment. Please try again.',
diff --git a/spec/frontend/vue_mr_widget/extentions/accessibility/index_spec.js b/spec/frontend/vue_mr_widget/extentions/accessibility/index_spec.js
new file mode 100644
index 00000000000..a9fe29a484a
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/extentions/accessibility/index_spec.js
@@ -0,0 +1,125 @@
+import MockAdapter from 'axios-mock-adapter';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { trimText } from 'helpers/text_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import axios from '~/lib/utils/axios_utils';
+import extensionsContainer from '~/vue_merge_request_widget/components/extensions/container';
+import { registerExtension } from '~/vue_merge_request_widget/components/extensions';
+import accessibilityExtension from '~/vue_merge_request_widget/extensions/accessibility';
+import httpStatusCodes from '~/lib/utils/http_status';
+import { accessibilityReportResponseErrors, accessibilityReportResponseSuccess } from './mock_data';
+
+describe('Accessibility extension', () => {
+ let wrapper;
+ let mock;
+
+ registerExtension(accessibilityExtension);
+
+ const endpoint = '/root/repo/-/merge_requests/4/accessibility_reports.json';
+
+ const mockApi = (statusCode, data) => {
+ mock.onGet(endpoint).reply(statusCode, data);
+ };
+
+ const findToggleCollapsedButton = () => wrapper.findByTestId('toggle-button');
+ const findAllExtensionListItems = () => wrapper.findAllByTestId('extension-list-item');
+
+ const createComponent = () => {
+ wrapper = mountExtended(extensionsContainer, {
+ propsData: {
+ mr: {
+ accessibilityReportPath: endpoint,
+ },
+ },
+ });
+ };
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ mock.restore();
+ });
+
+ describe('summary', () => {
+ it('displays loading text', () => {
+ mockApi(httpStatusCodes.OK, accessibilityReportResponseErrors);
+
+ createComponent();
+
+ expect(wrapper.text()).toBe('Accessibility scanning results are being parsed');
+ });
+
+ it('displays failed loading text', async () => {
+ mockApi(httpStatusCodes.INTERNAL_SERVER_ERROR);
+
+ createComponent();
+
+ await waitForPromises();
+
+ expect(wrapper.text()).toBe('Accessibility scanning failed loading results');
+ });
+
+ it('displays detected errors', async () => {
+ mockApi(httpStatusCodes.OK, accessibilityReportResponseErrors);
+
+ createComponent();
+
+ await waitForPromises();
+
+ expect(wrapper.text()).toBe(
+ 'Accessibility scanning detected 8 issues for the source branch only',
+ );
+ });
+
+ it('displays no detected errors', async () => {
+ mockApi(httpStatusCodes.OK, accessibilityReportResponseSuccess);
+
+ createComponent();
+
+ await waitForPromises();
+
+ expect(wrapper.text()).toBe(
+ 'Accessibility scanning detected no issues for the source branch only',
+ );
+ });
+ });
+
+ describe('expanded data', () => {
+ beforeEach(async () => {
+ mockApi(httpStatusCodes.OK, accessibilityReportResponseErrors);
+
+ createComponent();
+
+ await waitForPromises();
+
+ findToggleCollapsedButton().trigger('click');
+
+ await waitForPromises();
+ });
+
+ it('displays all report list items', async () => {
+ expect(findAllExtensionListItems()).toHaveLength(10);
+ });
+
+ it('displays report list item formatted', () => {
+ const text = {
+ newError: trimText(findAllExtensionListItems().at(0).text()),
+ resolvedError: findAllExtensionListItems().at(3).text(),
+ existingError: trimText(findAllExtensionListItems().at(8).text()),
+ };
+
+ expect(text.newError).toBe(
+ 'New The accessibility scanning found an error of the following type: WCAG2AA.Principle2.Guideline2_4.2_4_1.H64.1 Learn more Message: Iframe element requires a non-empty title attribute that identifies the frame.',
+ );
+ expect(text.resolvedError).toBe(
+ 'The accessibility scanning found an error of the following type: WCAG2AA.Principle1.Guideline1_1.1_1_1.H30.2 Learn more Message: Img element is the only content of the link, but is missing alt text. The alt text should describe the purpose of the link.',
+ );
+ expect(text.existingError).toBe(
+ 'The accessibility scanning found an error of the following type: WCAG2AA.Principle2.Guideline2_4.2_4_1.H64.1 Learn more Message: Iframe element requires a non-empty title attribute that identifies the frame.',
+ );
+ });
+ });
+});
diff --git a/spec/frontend/vue_mr_widget/extentions/accessibility/mock_data.js b/spec/frontend/vue_mr_widget/extentions/accessibility/mock_data.js
new file mode 100644
index 00000000000..06dc93d101f
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/extentions/accessibility/mock_data.js
@@ -0,0 +1,137 @@
+export const accessibilityReportResponseErrors = {
+ status: 'failed',
+ new_errors: [
+ {
+ code: 'WCAG2AA.Principle2.Guideline2_4.2_4_1.H64.1',
+ type: 'error',
+ type_code: 1,
+ message: 'Iframe element requires a non-empty title attribute that identifies the frame.',
+ context:
+ '<iframe height="0" width="0" style="display: none; visibility: hidden;" src="//10421980.fls.doubleclick.net/activityi;src=10421980;type=count0;cat=globa0;ord=6271888671448;gtm=2wg1c0;auiddc=40010797.1642181125;u1=undefined;u2=undefined;u3=undefined;u...',
+ selector: 'html > body > iframe:nth-child(42)',
+ runner: 'htmlcs',
+ runner_extras: {},
+ },
+ {
+ code: 'WCAG2AA.Principle3.Guideline3_2.3_2_2.H32.2',
+ type: 'error',
+ type_code: 1,
+ message:
+ 'This form does not contain a submit button, which creates issues for those who cannot submit the form using the keyboard. Submit buttons are INPUT elements with type attribute "submit" or "image", or BUTTON elements with type "submit" or omitted/invalid.',
+ context:
+ '<form class="challenge-form" id="challenge-form" action="/users/sign_in?__cf_chl_jschl_tk__=xoagAHj9DXTTDveypAmMkakkNQgeWc6LmZA53YyDeSg-1642181129-0-gaNycGzNB1E" method="POST" enctype="application/x-www-form-urlencoded">\n <input type="hidden" name...',
+ selector: '#challenge-form',
+ runner: 'htmlcs',
+ runner_extras: {},
+ },
+ {
+ code: 'WCAG2AA.Principle2.Guideline2_4.2_4_1.H64.1',
+ type: 'error',
+ type_code: 1,
+ message: 'Iframe element requires a non-empty title attribute that identifies the frame.',
+ context: '<iframe style="display: none;"></iframe>',
+ selector: 'html > body > iframe',
+ runner: 'htmlcs',
+ runner_extras: {},
+ },
+ ],
+ resolved_errors: [
+ {
+ code: 'WCAG2AA.Principle2.Guideline2_4.2_4_1.H64.1',
+ type: 'error',
+ type_code: 1,
+ message: 'Iframe element requires a non-empty title attribute that identifies the frame.',
+ context:
+ '<iframe height="0" width="0" style="display: none; visibility: hidden;" src="//10421980.fls.doubleclick.net/activityi;src=10421980;type=count0;cat=globa0;ord=6722452746146;gtm=2wg1a0;auiddc=716711306.1642082367;u1=undefined;u2=undefined;u3=undefined;...',
+ selector: 'html > body > iframe:nth-child(42)',
+ runner: 'htmlcs',
+ runner_extras: {},
+ },
+ {
+ code: 'WCAG2AA.Principle3.Guideline3_2.3_2_2.H32.2',
+ type: 'error',
+ type_code: 1,
+ message:
+ 'This form does not contain a submit button, which creates issues for those who cannot submit the form using the keyboard. Submit buttons are INPUT elements with type attribute "submit" or "image", or BUTTON elements with type "submit" or omitted/invalid.',
+ context:
+ '<form class="challenge-form" id="challenge-form" action="/users/sign_in?__cf_chl_jschl_tk__=vDKZT2hjxWCstlWz2wtxsLdqLF79rM4IsoxzMgY6Lfw-1642082370-0-gaNycGzNB2U" method="POST" enctype="application/x-www-form-urlencoded">\n <input type="hidden" name...',
+ selector: '#challenge-form',
+ runner: 'htmlcs',
+ runner_extras: {},
+ },
+ ],
+ existing_errors: [
+ {
+ code: 'WCAG2AA.Principle1.Guideline1_1.1_1_1.H30.2',
+ type: 'error',
+ type_code: 1,
+ message:
+ 'Img element is the only content of the link, but is missing alt text. The alt text should describe the purpose of the link.',
+ context: '<a href="/" data-nav="logo">\n<img src="/images/icons/logos/...</a>',
+ selector: '#navigation-mobile > header > a',
+ runner: 'htmlcs',
+ runner_extras: {},
+ },
+ {
+ code: 'WCAG2AA.Principle1.Guideline1_1.1_1_1.H37',
+ type: 'error',
+ type_code: 1,
+ message:
+ 'Img element missing an alt attribute. Use the alt attribute to specify a short text alternative.',
+ context: '<img src="/images/icons/slp-hamburger.svg" class="slp-inline-block slp-mr-8">',
+ selector: '#slpMobileNavActive > img',
+ runner: 'htmlcs',
+ runner_extras: {},
+ },
+ {
+ code: 'WCAG2AA.Principle1.Guideline1_1.1_1_1.H37',
+ type: 'error',
+ type_code: 1,
+ message:
+ 'Img element missing an alt attribute. Use the alt attribute to specify a short text alternative.',
+ context: '<img src="/images/icons/slp-caret-down.svg">',
+ selector: '#navigation-mobile > div:nth-child(2) > div:nth-child(2) > button > div > img',
+ runner: 'htmlcs',
+ runner_extras: {},
+ },
+ {
+ code: 'WCAG2AA.Principle1.Guideline1_1.1_1_1.H37',
+ type: 'error',
+ type_code: 1,
+ message:
+ 'Img element missing an alt attribute. Use the alt attribute to specify a short text alternative.',
+ context: '<img src="/images/icons/slp-caret-down.svg">',
+ selector: '#navigation-mobile > div:nth-child(2) > div:nth-child(3) > button > div > img',
+ runner: 'htmlcs',
+ runner_extras: {},
+ },
+ {
+ code: 'WCAG2AA.Principle1.Guideline1_1.1_1_1.H37',
+ type: 'error',
+ type_code: 1,
+ message:
+ 'Img element missing an alt attribute. Use the alt attribute to specify a short text alternative.',
+ context: '<img src="/images/icons/slp-caret-down.svg">',
+ selector: '#navigation-mobile > div:nth-child(2) > div:nth-child(4) > button > div > img',
+ runner: 'htmlcs',
+ runner_extras: {},
+ },
+ ],
+ summary: {
+ total: 8,
+ resolved: 2,
+ errored: 8,
+ },
+};
+
+export const accessibilityReportResponseSuccess = {
+ status: 'success',
+ new_errors: [],
+ resolved_errors: [],
+ existing_errors: [],
+ summary: {
+ total: 0,
+ resolved: 0,
+ errored: 0,
+ },
+};
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..ba3b0335a8e 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);
@@ -175,17 +176,18 @@ describe('AlertManagementStatus', () => {
jest.spyOn(Tracking, 'event');
});
- it('should not track alert status updates when the tracking options do not exist', () => {
+ it('should not track alert status updates when the tracking options do not exist', async () => {
mountComponent({});
Tracking.event.mockClear();
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({});
findFirstStatusOption().vm.$emit('click');
- setImmediate(() => {
- expect(Tracking.event).not.toHaveBeenCalled();
- });
+
+ await nextTick();
+
+ expect(Tracking.event).not.toHaveBeenCalled();
});
- it('should track alert status updates when the tracking options exist', () => {
+ it('should track alert status updates when the tracking options exist', async () => {
const trackAlertStatusUpdateOptions = {
category: 'Alert Management',
action: 'update_alert_status',
@@ -195,11 +197,12 @@ describe('AlertManagementStatus', () => {
Tracking.event.mockClear();
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({});
findFirstStatusOption().vm.$emit('click');
+
+ await nextTick();
+
const status = findFirstStatusOption().text();
- setImmediate(() => {
- const { category, action, label } = trackAlertStatusUpdateOptions;
- expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property: status });
- });
+ const { category, action, label } = trackAlertStatusUpdateOptions;
+ expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property: status });
});
});
});
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/__snapshots__/simple_viewer_spec.js.snap b/spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap
index 06753044e93..fbf3d17fd64 100644
--- a/spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap
+++ b/spec/frontend/vue_shared/components/blob_viewers/__snapshots__/simple_viewer_spec.js.snap
@@ -6,7 +6,7 @@ exports[`Blob Simple Viewer component rendering matches the snapshot 1`] = `
class="file-content code js-syntax-highlight"
>
<div
- class="line-numbers"
+ class="line-numbers gl-pt-0!"
>
<a
class="diff-line-num js-line-number"
@@ -56,7 +56,7 @@ exports[`Blob Simple Viewer component rendering matches the snapshot 1`] = `
class="blob-content"
>
<pre
- class="code highlight"
+ class="code highlight gl-p-0! gl-display-flex"
>
<code
data-blob-hash="foo-bar"
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..663ebd3e12f 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,16 +1,21 @@
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';
+import LineHighlighter from '~/blob/line_highlighter';
+
+jest.mock('~/blob/line_highlighter');
describe('Blob Simple Viewer component', () => {
let wrapper;
const contentMock = `<span id="LC1">First</span>\n<span id="LC2">Second</span>\n<span id="LC3">Third</span>`;
const blobHash = 'foo-bar';
- function createComponent(content = contentMock, isRawContent = false) {
+ function createComponent(content = contentMock, isRawContent = false, glFeatures = {}) {
wrapper = shallowMount(SimpleViewer, {
provide: {
blobHash,
+ glFeatures,
},
propsData: {
content,
@@ -25,6 +30,20 @@ describe('Blob Simple Viewer component', () => {
wrapper.destroy();
});
+ describe('refactorBlobViewer feature flag', () => {
+ it('loads the LineHighlighter if refactorBlobViewer is enabled', () => {
+ createComponent('', false, { refactorBlobViewer: true });
+
+ expect(LineHighlighter).toHaveBeenCalled();
+ });
+
+ it('does not load the LineHighlighter if refactorBlobViewer is disabled', () => {
+ createComponent('', false, { refactorBlobViewer: false });
+
+ expect(LineHighlighter).not.toHaveBeenCalled();
+ });
+ });
+
it('does not fail if content is empty', () => {
const spy = jest.spyOn(window.console, 'error');
createComponent('');
@@ -69,7 +88,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 +97,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/confirm_fork_modal_spec.js b/spec/frontend/vue_shared/components/confirm_fork_modal_spec.js
new file mode 100644
index 00000000000..1cde92cf522
--- /dev/null
+++ b/spec/frontend/vue_shared/components/confirm_fork_modal_spec.js
@@ -0,0 +1,80 @@
+import { GlModal } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import ConfirmForkModal, { i18n } from '~/vue_shared/components/confirm_fork_modal.vue';
+
+describe('vue_shared/components/confirm_fork_modal', () => {
+ let wrapper = null;
+
+ const forkPath = '/fake/fork/path';
+ const modalId = 'confirm-fork-modal';
+ const defaultProps = { modalId, forkPath };
+
+ const findModal = () => wrapper.findComponent(GlModal);
+ const findModalProp = (prop) => findModal().props(prop);
+ const findModalActionProps = () => findModalProp('actionPrimary');
+
+ const createComponent = (props = {}) =>
+ shallowMountExtended(ConfirmForkModal, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('visible = false', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ it('sets the visible prop to `false`', () => {
+ expect(findModalProp('visible')).toBe(false);
+ });
+
+ it('sets the modal title', () => {
+ const title = findModalProp('title');
+ expect(title).toBe(i18n.title);
+ });
+
+ it('sets the modal id', () => {
+ const fakeModalId = findModalProp('modalId');
+ expect(fakeModalId).toBe(modalId);
+ });
+
+ it('has the fork path button', () => {
+ const modalProps = findModalActionProps();
+ expect(modalProps.text).toBe(i18n.btnText);
+ expect(modalProps.attributes.variant).toBe('confirm');
+ });
+
+ it('sets the correct fork path', () => {
+ const modalProps = findModalActionProps();
+ expect(modalProps.attributes.href).toBe(forkPath);
+ });
+
+ it('has the fork message', () => {
+ expect(findModal().text()).toContain(i18n.message);
+ });
+ });
+
+ describe('visible = true', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ visible: true });
+ });
+
+ it('sets the visible prop to `true`', () => {
+ expect(findModalProp('visible')).toBe(true);
+ });
+
+ it('emits the `change` event if the modal is hidden', () => {
+ expect(wrapper.emitted('change')).toBeUndefined();
+
+ findModal().vm.$emit('change', false);
+
+ expect(wrapper.emitted('change')).toEqual([[false]]);
+ });
+ });
+});
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/design_management/design_note_pin_spec.js b/spec/frontend/vue_shared/components/design_management/design_note_pin_spec.js
index 984a28c93d6..353d493add9 100644
--- a/spec/frontend/vue_shared/components/design_management/design_note_pin_spec.js
+++ b/spec/frontend/vue_shared/components/design_management/design_note_pin_spec.js
@@ -39,4 +39,72 @@ describe('Design note pin component', () => {
createComponent({ position: null });
expect(wrapper.element).toMatchSnapshot();
});
+
+ it('applies `on-image` class when isOnImage is true', () => {
+ createComponent({ isOnImage: true });
+
+ expect(wrapper.find('.on-image').exists()).toBe(true);
+ });
+
+ it('applies `draft` class when isDraft is true', () => {
+ createComponent({ isDraft: true });
+
+ expect(wrapper.find('.draft').exists()).toBe(true);
+ });
+
+ describe('size', () => {
+ it('is `sm` it applies `small` class', () => {
+ createComponent({ size: 'sm' });
+ expect(wrapper.find('.small').exists()).toBe(true);
+ });
+
+ it('is `md` it applies no size class', () => {
+ createComponent({ size: 'md' });
+ expect(wrapper.find('.small').exists()).toBe(false);
+ expect(wrapper.find('.medium').exists()).toBe(false);
+ });
+
+ it('throws when passed any other value except `sm` or `md`', () => {
+ jest.spyOn(console, 'error').mockImplementation(() => {});
+
+ createComponent({ size: 'lg' });
+
+ // eslint-disable-next-line no-console
+ expect(console.error).toHaveBeenCalled();
+ });
+ });
+
+ describe('ariaLabel', () => {
+ describe('when value is passed', () => {
+ it('overrides default aria-label', () => {
+ const ariaLabel = 'Aria Label';
+
+ createComponent({ ariaLabel });
+
+ const button = wrapper.find('button');
+
+ expect(button.attributes('aria-label')).toBe(ariaLabel);
+ });
+ });
+
+ describe('when no value is passed', () => {
+ it('shows new note label as aria-label when label is absent', () => {
+ createComponent({ label: null });
+
+ const button = wrapper.find('button');
+
+ expect(button.attributes('aria-label')).toBe('Comment form position');
+ });
+
+ it('shows label position as aria-label when label is present', () => {
+ const label = 1;
+
+ createComponent({ label, isNewNote: false });
+
+ const button = wrapper.find('button');
+
+ expect(button.attributes('aria-label')).toBe(`Comment '${label}' position`);
+ });
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/diff_viewer/diff_viewer_spec.js b/spec/frontend/vue_shared/components/diff_viewer/diff_viewer_spec.js
index 68e3ee11a0d..69964b2687d 100644
--- a/spec/frontend/vue_shared/components/diff_viewer/diff_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/diff_viewer/diff_viewer_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants';
@@ -26,27 +26,25 @@ describe('DiffViewer', () => {
vm.$destroy();
});
- it('renders image diff', (done) => {
+ it('renders image diff', async () => {
window.gon = {
relative_url_root: '',
};
createComponent({ ...requiredProps, projectPath: '' });
- setImmediate(() => {
- expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(
- `//-/raw/DEF/${RED_BOX_IMAGE_URL}`,
- );
+ await nextTick();
- expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(
- `//-/raw/ABC/${GREEN_BOX_IMAGE_URL}`,
- );
+ expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(
+ `//-/raw/DEF/${RED_BOX_IMAGE_URL}`,
+ );
- done();
- });
+ expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(
+ `//-/raw/ABC/${GREEN_BOX_IMAGE_URL}`,
+ );
});
- it('renders fallback download diff display', (done) => {
+ it('renders fallback download diff display', async () => {
createComponent({
...requiredProps,
diffViewerMode: 'added',
@@ -54,22 +52,18 @@ describe('DiffViewer', () => {
oldPath: 'testold.abc',
});
- setImmediate(() => {
- expect(vm.$el.querySelector('.deleted .file-info').textContent.trim()).toContain(
- 'testold.abc',
- );
+ await nextTick();
- expect(vm.$el.querySelector('.deleted .btn.btn-default').textContent.trim()).toContain(
- 'Download',
- );
+ expect(vm.$el.querySelector('.deleted .file-info').textContent.trim()).toContain('testold.abc');
- expect(vm.$el.querySelector('.added .file-info').textContent.trim()).toContain('test.abc');
- expect(vm.$el.querySelector('.added .btn.btn-default').textContent.trim()).toContain(
- 'Download',
- );
+ expect(vm.$el.querySelector('.deleted .btn.btn-default').textContent.trim()).toContain(
+ 'Download',
+ );
- done();
- });
+ expect(vm.$el.querySelector('.added .file-info').textContent.trim()).toContain('test.abc');
+ expect(vm.$el.querySelector('.added .btn.btn-default').textContent.trim()).toContain(
+ 'Download',
+ );
});
describe('renamed file', () => {
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..d0fa8b8dacb 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,60 +51,53 @@ 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('.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('.deleted img').getAttribute('src')).toBe(RED_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('.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('.view-modes-menu li:nth-child(3)').textContent.trim()).toBe(
- 'Onion skin',
- );
+ expect(vm.$el.querySelector('.view-modes-menu li:nth-child(3)').textContent.trim()).toBe(
+ 'Onion skin',
+ );
- expect(metaInfoElements.length).toBe(2);
- expect(metaInfoElements[0]).toHaveText('2.00 KiB');
- expect(metaInfoElements[1]).toHaveText('1.00 KiB');
-
- 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) => {
+ it('renders image diff for new', async () => {
createComponent({ ...allProps, diffMode: 'new', oldPath: '' });
- setImmediate(() => {
- const metaInfoElement = vm.$el.querySelector('.image-info');
+ await nextTick();
- expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL);
- expect(metaInfoElement).toHaveText('1.00 KiB');
+ const metaInfoElement = vm.$el.querySelector('.image-info');
- done();
- });
+ expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL);
+ expect(metaInfoElement).toHaveText('1.00 KiB');
});
- it('renders image diff for deleted', (done) => {
+ it('renders image diff for deleted', async () => {
createComponent({ ...allProps, diffMode: 'deleted', newPath: '' });
- setImmediate(() => {
- const metaInfoElement = vm.$el.querySelector('.image-info');
+ await nextTick();
- expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(RED_BOX_IMAGE_URL);
- expect(metaInfoElement).toHaveText('2.00 KiB');
+ const metaInfoElement = vm.$el.querySelector('.image-info');
- done();
- });
+ expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(RED_BOX_IMAGE_URL);
+ expect(metaInfoElement).toHaveText('2.00 KiB');
});
- it('renders image diff for renamed', (done) => {
+ it('renders image diff for renamed', async () => {
vm = new Vue({
components: {
imageDiffViewer,
@@ -130,69 +123,56 @@ describe('ImageDiffViewer', () => {
`),
}).$mount();
- setImmediate(() => {
- const metaInfoElement = vm.$el.querySelector('.image-info');
+ await nextTick();
- expect(vm.$el.querySelector('img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL);
- expect(vm.$el.querySelector('.overlay')).not.toBe(null);
+ const metaInfoElement = vm.$el.querySelector('.image-info');
- expect(metaInfoElement).toHaveText('2.00 KiB');
+ expect(vm.$el.querySelector('img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL);
+ expect(vm.$el.querySelector('.overlay')).not.toBe(null);
- done();
- });
+ expect(metaInfoElement).toHaveText('2.00 KiB');
});
describe('swipeMode', () => {
- beforeEach((done) => {
+ beforeEach(() => {
createComponent({ ...requiredProps });
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
- 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');
});
});
describe('onionSkin', () => {
- beforeEach((done) => {
+ beforeEach(() => {
createComponent({ ...requiredProps });
- setImmediate(() => {
- done();
- });
+ return nextTick();
});
- 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/expand_button_spec.js b/spec/frontend/vue_shared/components/expand_button_spec.js
index 7874658cc0f..87d6ed6b21f 100644
--- a/spec/frontend/vue_shared/components/expand_button_spec.js
+++ b/spec/frontend/vue_shared/components/expand_button_spec.js
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
-import Vue from 'vue';
+import { nextTick } from 'vue';
import ExpandButton from '~/vue_shared/components/expand_button.vue';
const text = {
@@ -66,9 +66,9 @@ describe('Expand button', () => {
});
describe('on click', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
expanderPrependEl().trigger('click');
- Vue.nextTick(done);
+ await nextTick();
});
afterEach(() => {
@@ -85,7 +85,7 @@ describe('Expand button', () => {
});
describe('when short text is provided', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
factory({
slots: {
expanded: `<p>${text.expanded}</p>`,
@@ -94,7 +94,7 @@ describe('Expand button', () => {
});
expanderPrependEl().trigger('click');
- Vue.nextTick(done);
+ await nextTick();
});
it('only renders expanded text', () => {
@@ -110,31 +110,29 @@ describe('Expand button', () => {
});
describe('append button', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
expanderPrependEl().trigger('click');
- Vue.nextTick(done);
+ await nextTick();
});
- it('clicking hides itself and shows prepend', () => {
+ it('clicking hides itself and shows prepend', async () => {
expect(expanderAppendEl().isVisible()).toBe(true);
expanderAppendEl().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(expanderPrependEl().isVisible()).toBe(true);
- });
+ await nextTick();
+ expect(expanderPrependEl().isVisible()).toBe(true);
});
- it('clicking hides expanded text', () => {
+ it('clicking hides expanded text', async () => {
expect(wrapper.find(ExpandButton).text().trim()).toBe(text.expanded);
expanderAppendEl().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(ExpandButton).text().trim()).not.toBe(text.expanded);
- });
+ await nextTick();
+ expect(wrapper.find(ExpandButton).text().trim()).not.toBe(text.expanded);
});
describe('when short text is provided', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
factory({
slots: {
expanded: `<p>${text.expanded}</p>`,
@@ -143,16 +141,15 @@ describe('Expand button', () => {
});
expanderPrependEl().trigger('click');
- Vue.nextTick(done);
+ await nextTick();
});
- it('clicking reveals short text', () => {
+ it('clicking reveals short text', async () => {
expect(wrapper.find(ExpandButton).text().trim()).toBe(text.expanded);
expanderAppendEl().trigger('click');
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(ExpandButton).text().trim()).toBe(text.short);
- });
+ await nextTick();
+ expect(wrapper.find(ExpandButton).text().trim()).toBe(text.short);
});
});
});
diff --git a/spec/frontend/vue_shared/components/file_finder/index_spec.js b/spec/frontend/vue_shared/components/file_finder/index_spec.js
index 181fc4017a3..921091c5b84 100644
--- a/spec/frontend/vue_shared/components/file_finder/index_spec.js
+++ b/spec/frontend/vue_shared/components/file_finder/index_spec.js
@@ -1,6 +1,5 @@
import Mousetrap from 'mousetrap';
-import Vue from 'vue';
-import waitForPromises from 'helpers/wait_for_promises';
+import Vue, { nextTick } from 'vue';
import { file } from 'jest/ide/helpers';
import { UP_KEY_CODE, DOWN_KEY_CODE, ENTER_KEY_CODE, ESC_KEY_CODE } from '~/lib/utils/keycodes';
import FindFileComponent from '~/vue_shared/components/file_finder/index.vue';
@@ -31,7 +30,7 @@ describe('File finder item spec', () => {
});
describe('with entries', () => {
- beforeEach((done) => {
+ beforeEach(() => {
createComponent({
files: [
{
@@ -48,7 +47,7 @@ describe('File finder item spec', () => {
],
});
- setImmediate(done);
+ return nextTick();
});
it('renders list of blobs', () => {
@@ -57,68 +56,48 @@ describe('File finder item spec', () => {
expect(vm.$el.textContent).not.toContain('folder');
});
- it('filters entries', (done) => {
+ it('filters entries', async () => {
vm.searchText = 'index';
- setImmediate(() => {
- expect(vm.$el.textContent).toContain('index.js');
- expect(vm.$el.textContent).not.toContain('component.js');
+ await nextTick();
- done();
- });
+ expect(vm.$el.textContent).toContain('index.js');
+ expect(vm.$el.textContent).not.toContain('component.js');
});
- it('shows clear button when searchText is not empty', (done) => {
+ it('shows clear button when searchText is not empty', async () => {
vm.searchText = 'index';
- setImmediate(() => {
- expect(vm.$el.querySelector('.dropdown-input').classList).toContain('has-value');
- expect(vm.$el.querySelector('.dropdown-input-search').classList).toContain('hidden');
+ await nextTick();
- done();
- });
+ expect(vm.$el.querySelector('.dropdown-input').classList).toContain('has-value');
+ expect(vm.$el.querySelector('.dropdown-input-search').classList).toContain('hidden');
});
- it('clear button resets searchText', (done) => {
+ it('clear button resets searchText', async () => {
vm.searchText = 'index';
- waitForPromises()
- .then(() => {
- vm.clearSearchInput();
- })
- .then(waitForPromises)
- .then(() => {
- expect(vm.searchText).toBe('');
- })
- .then(done)
- .catch(done.fail);
+ vm.clearSearchInput();
+
+ expect(vm.searchText).toBe('');
});
- it('clear button focuses search input', (done) => {
+ it('clear button focuses search input', async () => {
jest.spyOn(vm.$refs.searchInput, 'focus').mockImplementation(() => {});
vm.searchText = 'index';
- waitForPromises()
- .then(() => {
- vm.clearSearchInput();
- })
- .then(waitForPromises)
- .then(() => {
- expect(vm.$refs.searchInput.focus).toHaveBeenCalled();
- })
- .then(done)
- .catch(done.fail);
+ vm.clearSearchInput();
+
+ await nextTick();
+
+ expect(vm.$refs.searchInput.focus).toHaveBeenCalled();
});
describe('listShowCount', () => {
- it('returns 1 when no filtered entries exist', (done) => {
+ it('returns 1 when no filtered entries exist', () => {
vm.searchText = 'testing 123';
- setImmediate(() => {
- expect(vm.listShowCount).toBe(1);
-
- done();
- });
+ expect(vm.listShowCount).toBe(1);
});
it('returns entries length when not filtered', () => {
@@ -131,26 +110,18 @@ describe('File finder item spec', () => {
expect(vm.listHeight).toBe(55);
});
- it('returns 33 when entries dont exist', (done) => {
+ it('returns 33 when entries dont exist', () => {
vm.searchText = 'testing 123';
- setImmediate(() => {
- expect(vm.listHeight).toBe(33);
-
- done();
- });
+ expect(vm.listHeight).toBe(33);
});
});
describe('filteredBlobsLength', () => {
- it('returns length of filtered blobs', (done) => {
+ it('returns length of filtered blobs', () => {
vm.searchText = 'index';
- setImmediate(() => {
- expect(vm.filteredBlobsLength).toBe(1);
-
- done();
- });
+ expect(vm.filteredBlobsLength).toBe(1);
});
});
@@ -158,7 +129,7 @@ describe('File finder item spec', () => {
it('renders less DOM nodes if not visible by utilizing v-if', async () => {
vm.visible = false;
- await waitForPromises();
+ await nextTick();
expect(vm.$el).toBeInstanceOf(Comment);
});
@@ -166,33 +137,24 @@ describe('File finder item spec', () => {
describe('watches', () => {
describe('searchText', () => {
- it('resets focusedIndex when updated', (done) => {
+ it('resets focusedIndex when updated', async () => {
vm.focusedIndex = 1;
vm.searchText = 'test';
- setImmediate(() => {
- expect(vm.focusedIndex).toBe(0);
+ await nextTick();
- done();
- });
+ expect(vm.focusedIndex).toBe(0);
});
});
describe('visible', () => {
- it('resets searchText when changed to false', (done) => {
+ it('resets searchText when changed to false', async () => {
vm.searchText = 'test';
- vm.visible = true;
-
- waitForPromises()
- .then(() => {
- vm.visible = false;
- })
- .then(waitForPromises)
- .then(() => {
- expect(vm.searchText).toBe('');
- })
- .then(done)
- .catch(done.fail);
+ vm.visible = false;
+
+ await nextTick();
+
+ expect(vm.searchText).toBe('');
});
});
});
@@ -216,7 +178,7 @@ describe('File finder item spec', () => {
});
describe('onKeyup', () => {
- it('opens file on enter key', (done) => {
+ it('opens file on enter key', async () => {
const event = new CustomEvent('keyup');
event.keyCode = ENTER_KEY_CODE;
@@ -224,14 +186,12 @@ describe('File finder item spec', () => {
vm.$refs.searchInput.dispatchEvent(event);
- setImmediate(() => {
- expect(vm.openFile).toHaveBeenCalledWith(vm.files[0]);
+ await nextTick();
- done();
- });
+ expect(vm.openFile).toHaveBeenCalledWith(vm.files[0]);
});
- it('closes file finder on esc key', (done) => {
+ it('closes file finder on esc key', async () => {
const event = new CustomEvent('keyup');
event.keyCode = ESC_KEY_CODE;
@@ -239,11 +199,9 @@ describe('File finder item spec', () => {
vm.$refs.searchInput.dispatchEvent(event);
- setImmediate(() => {
- expect(vm.$emit).toHaveBeenCalledWith('toggle', false);
+ await nextTick();
- done();
- });
+ expect(vm.$emit).toHaveBeenCalledWith('toggle', false);
});
});
@@ -310,34 +268,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 84f0151d9db..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 } from '@gitlab/ui';
+import { GlFilteredSearchToken, GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
import {
mockRegularLabel,
mockLabels,
@@ -61,13 +62,10 @@ const mockProps = {
getActiveTokenValue: (labels, data) => labels.find((label) => label.title === data),
};
-function createComponent({
- props = { ...mockProps },
- stubs = defaultStubs,
- slots = defaultSlots,
-} = {}) {
+function createComponent({ props = {}, stubs = defaultStubs, slots = defaultSlots } = {}) {
return mount(BaseToken, {
propsData: {
+ ...mockProps,
...props,
},
provide: {
@@ -83,15 +81,7 @@ function createComponent({
describe('BaseToken', () => {
let wrapper;
- beforeEach(() => {
- wrapper = createComponent({
- props: {
- ...mockProps,
- value: { data: `"${mockRegularLabel.title}"` },
- suggestions: mockLabels,
- },
- });
- });
+ const findGlFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken);
afterEach(() => {
wrapper.destroy();
@@ -99,21 +89,25 @@ describe('BaseToken', () => {
describe('data', () => {
it('calls `getRecentlyUsedSuggestions` to populate `recentSuggestions` when `recentSuggestionsStorageKey` is defined', () => {
+ wrapper = createComponent();
+
expect(getRecentlyUsedSuggestions).toHaveBeenCalledWith(mockStorageKey);
});
});
describe('computed', () => {
describe('activeTokenValue', () => {
- it('calls `getActiveTokenValue` when it is provided', async () => {
+ it('calls `getActiveTokenValue` when it is provided', () => {
const mockGetActiveTokenValue = jest.fn();
- wrapper.setProps({
- getActiveTokenValue: mockGetActiveTokenValue,
+ wrapper = createComponent({
+ props: {
+ value: { data: `"${mockRegularLabel.title}"` },
+ suggestions: mockLabels,
+ getActiveTokenValue: mockGetActiveTokenValue,
+ },
});
- await wrapper.vm.$nextTick();
-
expect(mockGetActiveTokenValue).toHaveBeenCalledTimes(1);
expect(mockGetActiveTokenValue).toHaveBeenCalledWith(
mockLabels,
@@ -125,33 +119,19 @@ describe('BaseToken', () => {
describe('watch', () => {
describe('active', () => {
- let wrapperWithTokenActive;
-
beforeEach(() => {
- wrapperWithTokenActive = createComponent({
+ wrapper = createComponent({
props: {
- ...mockProps,
value: { data: `"${mockRegularLabel.title}"` },
active: true,
},
});
});
- afterEach(() => {
- wrapperWithTokenActive.destroy();
- });
-
it('emits `fetch-suggestions` event on the component when value of this prop is changed to false and `suggestions` array is empty', async () => {
- wrapperWithTokenActive.setProps({
- active: false,
- });
-
- await wrapperWithTokenActive.vm.$nextTick();
+ await wrapper.setProps({ active: false });
- expect(wrapperWithTokenActive.emitted('fetch-suggestions')).toBeTruthy();
- expect(wrapperWithTokenActive.emitted('fetch-suggestions')).toEqual([
- [`"${mockRegularLabel.title}"`],
- ]);
+ expect(wrapper.emitted('fetch-suggestions')).toEqual([[`"${mockRegularLabel.title}"`]]);
});
});
});
@@ -161,17 +141,15 @@ describe('BaseToken', () => {
const mockTokenValue = mockLabels[0];
it('calls `setTokenValueToRecentlyUsed` when `recentSuggestionsStorageKey` is defined', () => {
+ wrapper = createComponent({ props: { suggestions: mockLabels } });
+
wrapper.vm.handleTokenValueSelected(mockTokenValue.title);
expect(setTokenValueToRecentlyUsed).toHaveBeenCalledWith(mockStorageKey, mockTokenValue);
});
- it('does not add token from preloadedSuggestions', async () => {
- wrapper.setProps({
- preloadedSuggestions: [mockTokenValue],
- });
-
- await wrapper.vm.$nextTick();
+ it('does not add token from preloadedSuggestions', () => {
+ wrapper = createComponent({ props: { preloadedSuggestions: [mockTokenValue] } });
wrapper.vm.handleTokenValueSelected(mockTokenValue.title);
@@ -182,58 +160,60 @@ describe('BaseToken', () => {
describe('template', () => {
it('renders gl-filtered-search-token component', () => {
- const wrapperWithNoStubs = createComponent({
- stubs: {},
- });
- const glFilteredSearchToken = wrapperWithNoStubs.find(GlFilteredSearchToken);
-
- expect(glFilteredSearchToken.exists()).toBe(true);
- expect(glFilteredSearchToken.props('config')).toEqual(mockProps.config);
+ wrapper = createComponent({ stubs: {} });
- wrapperWithNoStubs.destroy();
+ expect(findGlFilteredSearchToken().props('config')).toEqual(mockProps.config);
});
it('renders `view-token` slot when present', () => {
+ wrapper = createComponent();
+
expect(wrapper.find('.js-view-token').exists()).toBe(true);
});
it('renders `view` slot when present', () => {
+ wrapper = createComponent();
+
expect(wrapper.find('.js-view').exists()).toBe(true);
});
- describe('events', () => {
- let wrapperWithNoStubs;
-
- afterEach(() => {
- wrapperWithNoStubs.destroy();
+ it('renders loading spinner when loading', () => {
+ wrapper = createComponent({
+ props: {
+ active: true,
+ config: mockLabelToken,
+ suggestionsLoading: true,
+ },
+ stubs: { Portal: true },
});
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ });
+
+ describe('events', () => {
describe('when activeToken has been selected', () => {
beforeEach(() => {
- wrapperWithNoStubs = createComponent({
- props: {
- ...mockProps,
- getActiveTokenValue: () => ({ title: '' }),
- suggestionsLoading: true,
- },
+ wrapper = createComponent({
+ props: { getActiveTokenValue: () => ({ title: '' }) },
stubs: { Portal: true },
});
});
+
it('does not emit `fetch-suggestions` event on component after a delay when component emits `input` event', async () => {
jest.useFakeTimers();
- wrapperWithNoStubs.find(GlFilteredSearchToken).vm.$emit('input', { data: 'foo' });
- await wrapperWithNoStubs.vm.$nextTick();
+ findGlFilteredSearchToken().vm.$emit('input', { data: 'foo' });
+ await nextTick();
jest.runAllTimers();
- expect(wrapperWithNoStubs.emitted('fetch-suggestions')).toEqual([['']]);
+ expect(wrapper.emitted('fetch-suggestions')).toEqual([['']]);
});
});
describe('when activeToken has not been selected', () => {
beforeEach(() => {
- wrapperWithNoStubs = createComponent({
+ wrapper = createComponent({
stubs: { Portal: true },
});
});
@@ -241,38 +221,27 @@ describe('BaseToken', () => {
it('emits `fetch-suggestions` event on component after a delay when component emits `input` event', async () => {
jest.useFakeTimers();
- wrapperWithNoStubs.find(GlFilteredSearchToken).vm.$emit('input', { data: 'foo' });
- await wrapperWithNoStubs.vm.$nextTick();
+ findGlFilteredSearchToken().vm.$emit('input', { data: 'foo' });
+ await nextTick();
jest.runAllTimers();
- expect(wrapperWithNoStubs.emitted('fetch-suggestions')).toBeTruthy();
- expect(wrapperWithNoStubs.emitted('fetch-suggestions')[2]).toEqual(['foo']);
+ expect(wrapper.emitted('fetch-suggestions')[2]).toEqual(['foo']);
});
describe('when search is started with a quote', () => {
- it('emits `fetch-suggestions` with filtered value', async () => {
- jest.useFakeTimers();
-
- wrapperWithNoStubs.find(GlFilteredSearchToken).vm.$emit('input', { data: '"foo' });
- await wrapperWithNoStubs.vm.$nextTick();
+ it('emits `fetch-suggestions` with filtered value', () => {
+ findGlFilteredSearchToken().vm.$emit('input', { data: '"foo' });
- jest.runAllTimers();
-
- expect(wrapperWithNoStubs.emitted('fetch-suggestions')[2]).toEqual(['foo']);
+ expect(wrapper.emitted('fetch-suggestions')[2]).toEqual(['foo']);
});
});
describe('when search starts and ends with a quote', () => {
- it('emits `fetch-suggestions` with filtered value', async () => {
- jest.useFakeTimers();
-
- wrapperWithNoStubs.find(GlFilteredSearchToken).vm.$emit('input', { data: '"foo"' });
- await wrapperWithNoStubs.vm.$nextTick();
-
- jest.runAllTimers();
+ it('emits `fetch-suggestions` with filtered value', () => {
+ findGlFilteredSearchToken().vm.$emit('input', { data: '"foo"' });
- expect(wrapperWithNoStubs.emitted('fetch-suggestions')[2]).toEqual(['foo']);
+ expect(wrapper.emitted('fetch-suggestions')[2]).toEqual(['foo']);
});
});
});
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..de9ec863dd5 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';
@@ -31,7 +32,7 @@ const defaultStubs = {
function createComponent(options = {}) {
const {
- config = mockMilestoneToken,
+ config = { ...mockMilestoneToken, shouldSkipSort: true },
value = { data: '' },
active = false,
stubs = defaultStubs,
@@ -67,6 +68,27 @@ describe('MilestoneToken', () => {
describe('methods', () => {
describe('fetchMilestones', () => {
+ describe('when config.shouldSkipSort is true', () => {
+ beforeEach(() => {
+ wrapper.vm.config.shouldSkipSort = true;
+ });
+
+ afterEach(() => {
+ wrapper.vm.config.shouldSkipSort = false;
+ });
+ it('does not call sortMilestonesByDueDate', async () => {
+ jest.spyOn(wrapper.vm.config, 'fetchMilestones').mockResolvedValue({
+ data: mockMilestones,
+ });
+
+ wrapper.vm.fetchMilestones();
+
+ await waitForPromises();
+
+ expect(sortMilestonesByDueDate).toHaveBeenCalledTimes(0);
+ });
+ });
+
it('calls `config.fetchMilestones` with provided searchTerm param', () => {
jest.spyOn(wrapper.vm.config, 'fetchMilestones');
@@ -76,10 +98,11 @@ describe('MilestoneToken', () => {
});
it('sets response to `milestones` when request is successful', () => {
+ wrapper.vm.config.shouldSkipSort = false;
+
jest.spyOn(wrapper.vm.config, 'fetchMilestones').mockResolvedValue({
data: mockMilestones,
});
-
wrapper.vm.fetchMilestones();
return waitForPromises().then(() => {
@@ -127,7 +150,7 @@ describe('MilestoneToken', () => {
milestones: mockMilestones,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
});
it('renders gl-filtered-search-token component', () => {
@@ -150,7 +173,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 +192,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 +207,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/gfm_autocomplete/__snapshots__/utils_spec.js.snap b/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap
deleted file mode 100644
index 370b6eb01bc..00000000000
--- a/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap
+++ /dev/null
@@ -1,54 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`gfm_autocomplete/utils emojis config shows the emoji name and icon in the menu item 1`] = `"raised_hands <gl-emoji data-name=\\"raised_hands\\"></gl-emoji>"`;
-
-exports[`gfm_autocomplete/utils issues config shows the iid and title in the menu item within a project context 1`] = `"<small>123456</small> Project context issue title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
-
-exports[`gfm_autocomplete/utils issues config shows the reference and title in the menu item within a group context 1`] = `"<small>gitlab#987654</small> Group context issue title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
-
-exports[`gfm_autocomplete/utils labels config shows the title in the menu item 1`] = `
-"
- <span class=\\"dropdown-label-box\\" style=\\"background: #123456;\\"></span>
- bug &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"
-`;
-
-exports[`gfm_autocomplete/utils members config shows an avatar character, name, parent name, and count in the menu item for a group 1`] = `
-"
- <div class=\\"gl-display-flex gl-align-items-center\\">
- <div class=\\"gl-avatar gl-avatar-s32 gl-flex-shrink-0 gl-rounded-small
- gl-display-flex gl-align-items-center gl-justify-content-center\\" aria-hidden=\\"true\\">
- G</div>
- <div class=\\"gl-line-height-normal gl-ml-4\\">
- <div>1-1s &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt; (2)</div>
- <div class=\\"gl-text-gray-700\\">GitLab Support Team</div>
- </div>
-
- </div>
- "
-`;
-
-exports[`gfm_autocomplete/utils members config shows the avatar, name and username in the menu item for a user 1`] = `
-"
- <div class=\\"gl-display-flex gl-align-items-center\\">
- <img class=\\"gl-avatar gl-avatar-s32 gl-flex-shrink-0 gl-avatar-circle\\" src=\\"/uploads/-/system/user/avatar/123456/avatar.png\\" alt=\\"\\" />
- <div class=\\"gl-line-height-normal gl-ml-4\\">
- <div>My Name &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;</div>
- <div class=\\"gl-text-gray-700\\">@myusername</div>
- </div>
-
- </div>
- "
-`;
-
-exports[`gfm_autocomplete/utils merge requests config shows the iid and title in the menu item within a project context 1`] = `"<small>123456</small> Project context merge request title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
-
-exports[`gfm_autocomplete/utils merge requests config shows the reference and title in the menu item within a group context 1`] = `"<small>gitlab!456789</small> Group context merge request title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
-
-exports[`gfm_autocomplete/utils milestones config shows the title in the menu item 1`] = `"13.2 &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
-
-exports[`gfm_autocomplete/utils quick actions config shows the name, aliases, params and description in the menu item 1`] = `
-"<div>/unlabel <small>(or /remove_label)</small> <small>~label1 ~\\"label 2\\"</small></div>
- <div><small><em>Remove all or specific label(s)</em></small></div>"
-`;
-
-exports[`gfm_autocomplete/utils snippets config shows the id and title in the menu item 1`] = `"<small>123456</small> Snippet title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
diff --git a/spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js b/spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js
deleted file mode 100644
index b4002fdf4ec..00000000000
--- a/spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import Tribute from '@gitlab/tributejs';
-import { shallowMount } from '@vue/test-utils';
-import GfmAutocomplete from '~/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue';
-
-describe('GfmAutocomplete', () => {
- let wrapper;
-
- describe('tribute', () => {
- const mentions = '/gitlab-org/gitlab-test/-/autocomplete_sources/members?type=Issue&type_id=1';
-
- beforeEach(() => {
- wrapper = shallowMount(GfmAutocomplete, {
- propsData: {
- dataSources: {
- mentions,
- },
- },
- slots: {
- default: ['<input/>'],
- },
- });
- });
-
- it('is set to tribute instance variable', () => {
- expect(wrapper.vm.tribute instanceof Tribute).toBe(true);
- });
-
- it('contains the slot input element', () => {
- wrapper.find('input').setValue('@');
-
- expect(wrapper.vm.tribute.current.element).toBe(wrapper.find('input').element);
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/gfm_autocomplete/utils_spec.js b/spec/frontend/vue_shared/components/gfm_autocomplete/utils_spec.js
deleted file mode 100644
index 7ec3fbd4e3b..00000000000
--- a/spec/frontend/vue_shared/components/gfm_autocomplete/utils_spec.js
+++ /dev/null
@@ -1,427 +0,0 @@
-import { escape, last } from 'lodash';
-import { GfmAutocompleteType, tributeConfig } from '~/vue_shared/components/gfm_autocomplete/utils';
-
-describe('gfm_autocomplete/utils', () => {
- describe('emojis config', () => {
- const emojisConfig = tributeConfig[GfmAutocompleteType.Emojis].config;
- const emoji = 'raised_hands';
-
- it('uses : as the trigger', () => {
- expect(emojisConfig.trigger).toBe(':');
- });
-
- it('searches using the emoji name', () => {
- expect(emojisConfig.lookup(emoji)).toBe(emoji);
- });
-
- it('limits the number of rendered items to 100', () => {
- expect(emojisConfig.menuItemLimit).toBe(100);
- });
-
- it('shows the emoji name and icon in the menu item', () => {
- expect(emojisConfig.menuItemTemplate({ original: emoji })).toMatchSnapshot();
- });
-
- it('inserts the emoji name on autocomplete selection', () => {
- expect(emojisConfig.selectTemplate({ original: emoji })).toBe(`:${emoji}:`);
- });
- });
-
- describe('issues config', () => {
- const issuesConfig = tributeConfig[GfmAutocompleteType.Issues].config;
- const groupContextIssue = {
- iid: 987654,
- reference: 'gitlab#987654',
- title: "Group context issue title <script>alert('hi')</script>",
- };
- const projectContextIssue = {
- id: null,
- iid: 123456,
- time_estimate: 0,
- title: "Project context issue title <script>alert('hi')</script>",
- };
-
- it('uses # as the trigger', () => {
- expect(issuesConfig.trigger).toBe('#');
- });
-
- it('searches using both the iid and title', () => {
- expect(issuesConfig.lookup(projectContextIssue)).toBe(
- `${projectContextIssue.iid}${projectContextIssue.title}`,
- );
- });
-
- it('limits the number of rendered items to 100', () => {
- expect(issuesConfig.menuItemLimit).toBe(100);
- });
-
- it('shows the reference and title in the menu item within a group context', () => {
- expect(issuesConfig.menuItemTemplate({ original: groupContextIssue })).toMatchSnapshot();
- });
-
- it('shows the iid and title in the menu item within a project context', () => {
- expect(issuesConfig.menuItemTemplate({ original: projectContextIssue })).toMatchSnapshot();
- });
-
- it('inserts the reference on autocomplete selection within a group context', () => {
- expect(issuesConfig.selectTemplate({ original: groupContextIssue })).toBe(
- groupContextIssue.reference,
- );
- });
-
- it('inserts the iid on autocomplete selection within a project context', () => {
- expect(issuesConfig.selectTemplate({ original: projectContextIssue })).toBe(
- `#${projectContextIssue.iid}`,
- );
- });
- });
-
- describe('labels config', () => {
- const labelsConfig = tributeConfig[GfmAutocompleteType.Labels].config;
- const labelsFilter = tributeConfig[GfmAutocompleteType.Labels].filterValues;
- const label = {
- color: '#123456',
- textColor: '#FFFFFF',
- title: `bug <script>alert('hi')</script>`,
- type: 'GroupLabel',
- };
- const singleWordLabel = {
- color: '#456789',
- textColor: '#DDD',
- title: `bug`,
- type: 'GroupLabel',
- };
- const numericalLabel = {
- color: '#abcdef',
- textColor: '#AAA',
- title: 123456,
- type: 'ProjectLabel',
- };
-
- it('uses ~ as the trigger', () => {
- expect(labelsConfig.trigger).toBe('~');
- });
-
- it('searches using `title`', () => {
- expect(labelsConfig.lookup).toBe('title');
- });
-
- it('limits the number of rendered items to 100', () => {
- expect(labelsConfig.menuItemLimit).toBe(100);
- });
-
- it('shows the title in the menu item', () => {
- expect(labelsConfig.menuItemTemplate({ original: label })).toMatchSnapshot();
- });
-
- it('inserts the title on autocomplete selection', () => {
- expect(labelsConfig.selectTemplate({ original: singleWordLabel })).toBe(
- `~${escape(singleWordLabel.title)}`,
- );
- });
-
- it('inserts the title enclosed with quotes on autocomplete selection when the title is numerical', () => {
- expect(labelsConfig.selectTemplate({ original: numericalLabel })).toBe(
- `~"${escape(numericalLabel.title)}"`,
- );
- });
-
- it('inserts the title enclosed with quotes on autocomplete selection when the title contains multiple words', () => {
- expect(labelsConfig.selectTemplate({ original: label })).toBe(`~"${escape(label.title)}"`);
- });
-
- describe('filter', () => {
- const collection = [label, singleWordLabel, { ...numericalLabel, set: true }];
-
- describe('/label quick action', () => {
- describe('when the line starts with `/label`', () => {
- it('shows labels that are not currently selected', () => {
- const fullText = '/label ~';
- const selectionStart = 8;
-
- expect(labelsFilter({ collection, fullText, selectionStart })).toEqual([
- collection[0],
- collection[1],
- ]);
- });
- });
-
- describe('when the line does not start with `/label`', () => {
- it('shows all labels', () => {
- const fullText = '~';
- const selectionStart = 1;
-
- expect(labelsFilter({ collection, fullText, selectionStart })).toEqual(collection);
- });
- });
- });
-
- describe('/unlabel quick action', () => {
- describe('when the line starts with `/unlabel`', () => {
- it('shows labels that are currently selected', () => {
- const fullText = '/unlabel ~';
- const selectionStart = 10;
-
- expect(labelsFilter({ collection, fullText, selectionStart })).toEqual([collection[2]]);
- });
- });
-
- describe('when the line does not start with `/unlabel`', () => {
- it('shows all labels', () => {
- const fullText = '~';
- const selectionStart = 1;
-
- expect(labelsFilter({ collection, fullText, selectionStart })).toEqual(collection);
- });
- });
- });
- });
- });
-
- describe('members config', () => {
- const membersConfig = tributeConfig[GfmAutocompleteType.Members].config;
- const membersFilter = tributeConfig[GfmAutocompleteType.Members].filterValues;
- const userMember = {
- type: 'User',
- username: 'myusername',
- name: "My Name <script>alert('hi')</script>",
- avatar_url: '/uploads/-/system/user/avatar/123456/avatar.png',
- availability: null,
- };
- const groupMember = {
- type: 'Group',
- username: 'gitlab-com/support/1-1s',
- name: "GitLab.com / GitLab Support Team / 1-1s <script>alert('hi')</script>",
- avatar_url: null,
- count: 2,
- mentionsDisabled: null,
- };
-
- it('uses @ as the trigger', () => {
- expect(membersConfig.trigger).toBe('@');
- });
-
- it('inserts the username on autocomplete selection', () => {
- expect(membersConfig.fillAttr).toBe('username');
- });
-
- it('searches using both the name and username for a user', () => {
- expect(membersConfig.lookup(userMember)).toBe(`${userMember.name}${userMember.username}`);
- });
-
- it('searches using only its own name and not its ancestors for a group', () => {
- expect(membersConfig.lookup(groupMember)).toBe(last(groupMember.name.split(' / ')));
- });
-
- it('limits the items in the autocomplete menu to 10', () => {
- expect(membersConfig.menuItemLimit).toBe(10);
- });
-
- it('shows the avatar, name and username in the menu item for a user', () => {
- expect(membersConfig.menuItemTemplate({ original: userMember })).toMatchSnapshot();
- });
-
- it('shows an avatar character, name, parent name, and count in the menu item for a group', () => {
- expect(membersConfig.menuItemTemplate({ original: groupMember })).toMatchSnapshot();
- });
-
- describe('filter', () => {
- const assignees = [userMember.username];
- const collection = [userMember, groupMember];
-
- describe('/assign quick action', () => {
- describe('when the line starts with `/assign`', () => {
- it('shows members that are not currently selected', () => {
- const fullText = '/assign @';
- const selectionStart = 9;
-
- expect(membersFilter({ assignees, collection, fullText, selectionStart })).toEqual([
- collection[1],
- ]);
- });
- });
-
- describe('when the line does not start with `/assign`', () => {
- it('shows all labels', () => {
- const fullText = '@';
- const selectionStart = 1;
-
- expect(membersFilter({ assignees, collection, fullText, selectionStart })).toEqual(
- collection,
- );
- });
- });
- });
-
- describe('/unassign quick action', () => {
- describe('when the line starts with `/unassign`', () => {
- it('shows members that are currently selected', () => {
- const fullText = '/unassign @';
- const selectionStart = 11;
-
- expect(membersFilter({ assignees, collection, fullText, selectionStart })).toEqual([
- collection[0],
- ]);
- });
- });
-
- describe('when the line does not start with `/unassign`', () => {
- it('shows all members', () => {
- const fullText = '@';
- const selectionStart = 1;
-
- expect(membersFilter({ assignees, collection, fullText, selectionStart })).toEqual(
- collection,
- );
- });
- });
- });
- });
- });
-
- describe('merge requests config', () => {
- const mergeRequestsConfig = tributeConfig[GfmAutocompleteType.MergeRequests].config;
- const groupContextMergeRequest = {
- iid: 456789,
- reference: 'gitlab!456789',
- title: "Group context merge request title <script>alert('hi')</script>",
- };
- const projectContextMergeRequest = {
- id: null,
- iid: 123456,
- time_estimate: 0,
- title: "Project context merge request title <script>alert('hi')</script>",
- };
-
- it('uses ! as the trigger', () => {
- expect(mergeRequestsConfig.trigger).toBe('!');
- });
-
- it('searches using both the iid and title', () => {
- expect(mergeRequestsConfig.lookup(projectContextMergeRequest)).toBe(
- `${projectContextMergeRequest.iid}${projectContextMergeRequest.title}`,
- );
- });
-
- it('limits the number of rendered items to 100', () => {
- expect(mergeRequestsConfig.menuItemLimit).toBe(100);
- });
-
- it('shows the reference and title in the menu item within a group context', () => {
- expect(
- mergeRequestsConfig.menuItemTemplate({ original: groupContextMergeRequest }),
- ).toMatchSnapshot();
- });
-
- it('shows the iid and title in the menu item within a project context', () => {
- expect(
- mergeRequestsConfig.menuItemTemplate({ original: projectContextMergeRequest }),
- ).toMatchSnapshot();
- });
-
- it('inserts the reference on autocomplete selection within a group context', () => {
- expect(mergeRequestsConfig.selectTemplate({ original: groupContextMergeRequest })).toBe(
- groupContextMergeRequest.reference,
- );
- });
-
- it('inserts the iid on autocomplete selection within a project context', () => {
- expect(mergeRequestsConfig.selectTemplate({ original: projectContextMergeRequest })).toBe(
- `!${projectContextMergeRequest.iid}`,
- );
- });
- });
-
- describe('milestones config', () => {
- const milestonesConfig = tributeConfig[GfmAutocompleteType.Milestones].config;
- const milestone = {
- id: null,
- iid: 49,
- title: "13.2 <script>alert('hi')</script>",
- };
-
- it('uses % as the trigger', () => {
- expect(milestonesConfig.trigger).toBe('%');
- });
-
- it('searches using the title', () => {
- expect(milestonesConfig.lookup).toBe('title');
- });
-
- it('limits the number of rendered items to 100', () => {
- expect(milestonesConfig.menuItemLimit).toBe(100);
- });
-
- it('shows the title in the menu item', () => {
- expect(milestonesConfig.menuItemTemplate({ original: milestone })).toMatchSnapshot();
- });
-
- it('inserts the title on autocomplete selection', () => {
- expect(milestonesConfig.selectTemplate({ original: milestone })).toBe(
- `%"${escape(milestone.title)}"`,
- );
- });
- });
-
- describe('quick actions config', () => {
- const quickActionsConfig = tributeConfig[GfmAutocompleteType.QuickActions].config;
- const quickAction = {
- name: 'unlabel',
- aliases: ['remove_label'],
- description: 'Remove all or specific label(s)',
- warning: '',
- icon: '',
- params: ['~label1 ~"label 2"'],
- };
-
- it('uses / as the trigger', () => {
- expect(quickActionsConfig.trigger).toBe('/');
- });
-
- it('inserts the name on autocomplete selection', () => {
- expect(quickActionsConfig.fillAttr).toBe('name');
- });
-
- it('searches using both the name and aliases', () => {
- expect(quickActionsConfig.lookup(quickAction)).toBe(
- `${quickAction.name}${quickAction.aliases.join(', /')}`,
- );
- });
-
- it('limits the number of rendered items to 100', () => {
- expect(quickActionsConfig.menuItemLimit).toBe(100);
- });
-
- it('shows the name, aliases, params and description in the menu item', () => {
- expect(quickActionsConfig.menuItemTemplate({ original: quickAction })).toMatchSnapshot();
- });
- });
-
- describe('snippets config', () => {
- const snippetsConfig = tributeConfig[GfmAutocompleteType.Snippets].config;
- const snippet = {
- id: 123456,
- title: "Snippet title <script>alert('hi')</script>",
- };
-
- it('uses $ as the trigger', () => {
- expect(snippetsConfig.trigger).toBe('$');
- });
-
- it('inserts the id on autocomplete selection', () => {
- expect(snippetsConfig.fillAttr).toBe('id');
- });
-
- it('searches using both the id and title', () => {
- expect(snippetsConfig.lookup(snippet)).toBe(`${snippet.id}${snippet.title}`);
- });
-
- it('limits the number of rendered items to 100', () => {
- expect(snippetsConfig.menuItemLimit).toBe(100);
- });
-
- it('shows the id and title in the menu item', () => {
- expect(snippetsConfig.menuItemTemplate({ original: snippet })).toMatchSnapshot();
- });
- });
-});
diff --git a/spec/frontend/vue_shared/components/gl_countdown_spec.js b/spec/frontend/vue_shared/components/gl_countdown_spec.js
index 82d18c7fd3f..0d1d42082ab 100644
--- a/spec/frontend/vue_shared/components/gl_countdown_spec.js
+++ b/spec/frontend/vue_shared/components/gl_countdown_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
@@ -17,38 +17,34 @@ describe('GlCountdown', () => {
});
describe('when there is time remaining', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
vm = mountComponent(Component, {
endDateString: '2000-01-01T01:02:03Z',
});
- Vue.nextTick().then(done).catch(done.fail);
+ await nextTick();
});
it('displays remaining time', () => {
expect(vm.$el.textContent).toContain('01:02:03');
});
- it('updates remaining time', (done) => {
+ it('updates remaining time', async () => {
now = '2000-01-01T00:00:01Z';
jest.advanceTimersByTime(1000);
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.textContent).toContain('01:02:02');
- done();
- })
- .catch(done.fail);
+ await nextTick();
+ expect(vm.$el.textContent).toContain('01:02:02');
});
});
describe('when there is no time remaining', () => {
- beforeEach((done) => {
+ beforeEach(async () => {
vm = mountComponent(Component, {
endDateString: '1900-01-01T00:00:00Z',
});
- Vue.nextTick().then(done).catch(done.fail);
+ await nextTick();
});
it('displays 00:00:00', () => {
diff --git a/spec/frontend/vue_shared/components/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/help_popover_spec.js b/spec/frontend/vue_shared/components/help_popover_spec.js
index 30c6fa04032..597fb63d95c 100644
--- a/spec/frontend/vue_shared/components/help_popover_spec.js
+++ b/spec/frontend/vue_shared/components/help_popover_spec.js
@@ -9,59 +9,117 @@ describe('HelpPopover', () => {
const findQuestionButton = () => wrapper.find(GlButton);
const findPopover = () => wrapper.find(GlPopover);
- const buildWrapper = (options = {}) => {
+
+ const createComponent = ({ props, ...opts } = {}) => {
wrapper = mount(HelpPopover, {
propsData: {
options: {
title,
content,
- ...options,
},
+ ...props,
},
+ ...opts,
});
};
- beforeEach(() => {
- buildWrapper();
- });
-
afterEach(() => {
wrapper.destroy();
});
- it('renders a link button with an icon question', () => {
- expect(findQuestionButton().props()).toMatchObject({
- icon: 'question',
- variant: 'link',
+ describe('with title and content', () => {
+ beforeEach(() => {
+ createComponent();
});
- });
- it('renders popover that uses the question button as target', () => {
- expect(findPopover().props().target()).toBe(findQuestionButton().vm.$el);
- });
+ it('renders a link button with an icon question', () => {
+ expect(findQuestionButton().props()).toMatchObject({
+ icon: 'question',
+ variant: 'link',
+ });
+ });
- it('allows rendering title with HTML tags', () => {
- expect(findPopover().find('strong').exists()).toBe(true);
- });
+ it('renders popover that uses the question button as target', () => {
+ expect(findPopover().props().target()).toBe(findQuestionButton().vm.$el);
+ });
- it('allows rendering content with HTML tags', () => {
- expect(findPopover().find('b').exists()).toBe(true);
+ it('shows title and content', () => {
+ expect(findPopover().html()).toContain(title);
+ expect(findPopover().html()).toContain(content);
+ });
+
+ it('allows rendering title with HTML tags', () => {
+ expect(findPopover().find('strong').exists()).toBe(true);
+ });
+
+ it('allows rendering content with HTML tags', () => {
+ expect(findPopover().find('b').exists()).toBe(true);
+ });
});
describe('without title', () => {
- it('does not render title', () => {
- buildWrapper({ title: null });
+ beforeEach(() => {
+ createComponent({
+ props: {
+ options: {
+ title: null,
+ content,
+ },
+ },
+ });
+ });
+
+ it('does not show title', () => {
+ expect(findPopover().html()).not.toContain(title);
+ });
- expect(findPopover().find('span').exists()).toBe(false);
+ it('shows content', () => {
+ expect(findPopover().html()).toContain(content);
});
});
- it('binds other popover options to the popover instance', () => {
+ describe('with other options', () => {
const placement = 'bottom';
- wrapper.destroy();
- buildWrapper({ placement });
+ beforeEach(() => {
+ createComponent({
+ props: {
+ options: {
+ placement,
+ },
+ },
+ });
+ });
+
+ it('options bind to the popover', () => {
+ expect(findPopover().props().placement).toBe(placement);
+ });
+ });
+
+ describe('with custom slots', () => {
+ const titleSlot = '<h1>title</h1>';
+ const defaultSlot = '<strong>content</strong>';
- expect(findPopover().props().placement).toBe(placement);
+ beforeEach(() => {
+ createComponent({
+ slots: {
+ title: titleSlot,
+ default: defaultSlot,
+ },
+ });
+ });
+
+ it('shows title slot', () => {
+ expect(findPopover().html()).toContain(titleSlot);
+ });
+
+ it('shows default content slot', () => {
+ expect(findPopover().html()).toContain(defaultSlot);
+ });
+
+ it('overrides title and content from options', () => {
+ expect(findPopover().html()).not.toContain(title);
+ expect(findPopover().html()).toContain(content);
+ });
});
});
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..c7ad47b6ef7 100644
--- a/spec/frontend/vue_shared/components/markdown/field_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/field_spec.js
@@ -1,10 +1,10 @@
-import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import AxiosMockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import { TEST_HOST, FIXTURES_PATH } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
const markdownPreviewPath = `${TEST_HOST}/preview`;
const markdownDocsPath = `${TEST_HOST}/docs`;
@@ -12,8 +12,8 @@ const textareaValue = 'testing\n123';
const uploadsPath = 'test/uploads';
function assertMarkdownTabs(isWrite, writeLink, previewLink, wrapper) {
- expect(writeLink.element.parentNode.classList.contains('active')).toBe(isWrite);
- expect(previewLink.element.parentNode.classList.contains('active')).toBe(!isWrite);
+ expect(writeLink.element.children[0].classList.contains('active')).toBe(isWrite);
+ expect(previewLink.element.children[0].classList.contains('active')).toBe(!isWrite);
expect(wrapper.find('.md-preview-holder').element.style.display).toBe(isWrite ? 'none' : '');
}
@@ -29,14 +29,13 @@ describe('Markdown field component', () => {
afterEach(() => {
subject.destroy();
- subject = null;
axiosMock.restore();
});
function createSubject(lines = []) {
// We actually mount a wrapper component so that we can force Vue to rerender classes in order to test a regression
// caused by mixing Vanilla JS and Vue.
- subject = mount(
+ subject = mountExtended(
{
components: {
MarkdownField,
@@ -63,12 +62,17 @@ describe('Markdown field component', () => {
textareaValue,
lines,
},
+ provide: {
+ glFeatures: {
+ contactsAutocomplete: true,
+ },
+ },
},
);
}
- const getPreviewLink = () => subject.find('.nav-links .js-preview-link');
- const getWriteLink = () => subject.find('.nav-links .js-write-link');
+ const getPreviewLink = () => subject.findByTestId('preview-tab');
+ const getWriteLink = () => subject.findByTestId('write-tab');
const getMarkdownButton = () => subject.find('.js-md');
const getAllMarkdownButtons = () => subject.findAll('.js-md');
const getVideo = () => subject.find('video');
@@ -100,115 +104,100 @@ 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');
+ previewLink.vm.$emit('click', { target: {} });
- return subject.vm.$nextTick().then(() => {
- expect(previewLink.element.parentNode.classList.contains('active')).toBeTruthy();
- });
+ await nextTick();
+ expect(previewLink.element.children[0].classList.contains('active')).toBe(true);
});
- it('shows preview loading text', () => {
+ it('shows preview loading text', async () => {
previewLink = getPreviewLink();
- previewLink.trigger('click');
+ previewLink.vm.$emit('click', { target: {} });
- 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');
+ previewLink.vm.$emit('click', { target: {} });
- 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');
+ previewLink.vm.$emit('click', { target: {} });
- let callPause;
+ await axios.waitFor(markdownPreviewPath);
+ const video = getVideo();
+ const callPause = jest.spyOn(video.element, 'pause').mockImplementation(() => true);
- return axios
- .waitFor(markdownPreviewPath)
- .then(() => {
- const video = getVideo();
- 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 () => {
writeLink = getWriteLink();
previewLink = getPreviewLink();
- writeLink.trigger('click');
- await subject.vm.$nextTick();
+ writeLink.vm.$emit('click', { target: {} });
+ await nextTick();
assertMarkdownTabs(true, writeLink, previewLink, subject);
- writeLink.trigger('click');
- await subject.vm.$nextTick();
+ writeLink.vm.$emit('click', { target: {} });
+ await nextTick();
assertMarkdownTabs(true, writeLink, previewLink, subject);
- previewLink.trigger('click');
- await subject.vm.$nextTick();
+ previewLink.vm.$emit('click', { target: {} });
+ await nextTick();
assertMarkdownTabs(false, writeLink, previewLink, subject);
- previewLink.trigger('click');
- await subject.vm.$nextTick();
+ previewLink.vm.$emit('click', { target: {} });
+ 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 +218,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..93ce3935fab 100644
--- a/spec/frontend/vue_shared/components/markdown/header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/header_spec.js
@@ -1,20 +1,25 @@
-import { shallowMount } from '@vue/test-utils';
import $ from 'jquery';
+import { nextTick } from 'vue';
+import { GlTabs } from '@gitlab/ui';
import HeaderComponent from '~/vue_shared/components/markdown/header.vue';
import ToolbarButton from '~/vue_shared/components/markdown/toolbar_button.vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
describe('Markdown field header component', () => {
let wrapper;
const createWrapper = (props) => {
- wrapper = shallowMount(HeaderComponent, {
+ wrapper = shallowMountExtended(HeaderComponent, {
propsData: {
previewMarkdown: false,
...props,
},
+ stubs: { GlTabs },
});
};
+ const findWriteTab = () => wrapper.findByTestId('write-tab');
+ const findPreviewTab = () => wrapper.findByTestId('preview-tab');
const findToolbarButtons = () => wrapper.findAll(ToolbarButton);
const findToolbarButtonByProp = (prop, value) =>
findToolbarButtons()
@@ -33,7 +38,6 @@ describe('Markdown field header component', () => {
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
describe('markdown header buttons', () => {
@@ -74,30 +78,29 @@ describe('Markdown field header component', () => {
});
});
- it('renders `write` link as active when previewMarkdown is false', () => {
- expect(wrapper.find('li:nth-child(1)').classes()).toContain('active');
+ it('activates `write` tab when previewMarkdown is false', () => {
+ expect(findWriteTab().attributes('active')).toBe('true');
+ expect(findPreviewTab().attributes('active')).toBeUndefined();
});
- it('renders `preview` link as active when previewMarkdown is true', () => {
+ it('activates `preview` tab when previewMarkdown is true', () => {
createWrapper({ previewMarkdown: true });
- expect(wrapper.find('li:nth-child(2)').classes()).toContain('active');
+ expect(findWriteTab().attributes('active')).toBeUndefined();
+ expect(findPreviewTab().attributes('active')).toBe('true');
});
- it('emits toggle markdown event when clicking preview', () => {
- wrapper.find('.js-preview-link').trigger('click');
+ it('emits toggle markdown event when clicking preview tab', async () => {
+ const eventData = { target: {} };
+ findPreviewTab().vm.$emit('click', eventData);
- 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);
- });
+ findWriteTab().vm.$emit('click', eventData);
+
+ await nextTick();
+ expect(wrapper.emitted('write-markdown').length).toEqual(1);
});
it('does not emit toggle markdown event when triggered from another form', () => {
@@ -112,12 +115,10 @@ describe('Markdown field header component', () => {
});
it('blurs preview link after click', () => {
- const link = wrapper.find('li:nth-child(2) button');
- jest.spyOn(HTMLElement.prototype, 'blur').mockImplementation();
-
- link.trigger('click');
+ const target = { blur: jest.fn() };
+ findPreviewTab().vm.$emit('click', { target });
- expect(link.element.blur).toHaveBeenCalled();
+ expect(target.blur).toHaveBeenCalled();
});
it('renders markdown table template', () => {
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/markdown/suggestions_spec.js b/spec/frontend/vue_shared/components/markdown/suggestions_spec.js
index 6fcac2df0b6..8f4235cfe41 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestions_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestions_spec.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
import SuggestionsComponent from '~/vue_shared/components/markdown/suggestions.vue';
const MOCK_DATA = {
@@ -51,7 +51,7 @@ describe('Suggestion component', () => {
let vm;
let diffTable;
- beforeEach((done) => {
+ beforeEach(async () => {
const Component = Vue.extend(SuggestionsComponent);
vm = new Component({
@@ -62,7 +62,7 @@ describe('Suggestion component', () => {
jest.spyOn(vm, 'renderSuggestions').mockImplementation(() => {});
vm.renderSuggestions();
- Vue.nextTick(done);
+ await nextTick();
});
describe('mounted', () => {
diff --git a/spec/frontend/vue_shared/components/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/multiselect_dropdown_spec.js b/spec/frontend/vue_shared/components/multiselect_dropdown_spec.js
deleted file mode 100644
index 566ca1817f2..00000000000
--- a/spec/frontend/vue_shared/components/multiselect_dropdown_spec.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { GlDropdown } from '@gitlab/ui';
-import { getByText } from '@testing-library/dom';
-import { shallowMount } from '@vue/test-utils';
-import MultiSelectDropdown from '~/vue_shared/components/sidebar/multiselect_dropdown.vue';
-
-describe('MultiSelectDropdown Component', () => {
- it('renders items slot', () => {
- const wrapper = shallowMount(MultiSelectDropdown, {
- propsData: {
- text: '',
- headerText: '',
- },
- slots: {
- items: '<p>Test</p>',
- },
- });
- expect(getByText(wrapper.element, 'Test')).toBeDefined();
- });
-
- it('renders search slot', () => {
- const wrapper = shallowMount(MultiSelectDropdown, {
- propsData: {
- text: '',
- headerText: '',
- },
- slots: {
- search: '<p>Search</p>',
- },
- stubs: {
- GlDropdown,
- },
- });
- expect(getByText(wrapper.element, 'Search')).toBeDefined();
- });
-});
diff --git a/spec/frontend/vue_shared/components/namespace_select/mock_data.js b/spec/frontend/vue_shared/components/namespace_select/mock_data.js
index c9d96672e85..cfd521c67cb 100644
--- a/spec/frontend/vue_shared/components/namespace_select/mock_data.js
+++ b/spec/frontend/vue_shared/components/namespace_select/mock_data.js
@@ -1,11 +1,6 @@
-export const group = [
+export const groupNamespaces = [
{ id: 1, name: 'Group 1', humanName: 'Group 1' },
{ id: 2, name: 'Subgroup 1', humanName: 'Group 1 / Subgroup 1' },
];
-export const user = [{ id: 3, name: 'User namespace 1', humanName: 'User namespace 1' }];
-
-export const namespaces = {
- group,
- user,
-};
+export const userNamespaces = [{ id: 3, name: 'User namespace 1', humanName: 'User namespace 1' }];
diff --git a/spec/frontend/vue_shared/components/namespace_select/namespace_select_spec.js b/spec/frontend/vue_shared/components/namespace_select/namespace_select_spec.js
index 8f07f63993d..c11b20a692e 100644
--- a/spec/frontend/vue_shared/components/namespace_select/namespace_select_spec.js
+++ b/spec/frontend/vue_shared/components/namespace_select/namespace_select_spec.js
@@ -1,9 +1,15 @@
-import { GlDropdown, GlDropdownItem, GlDropdownSectionHeader } from '@gitlab/ui';
+import { nextTick } from 'vue';
+import { GlDropdown, GlDropdownItem, GlDropdownSectionHeader, GlSearchBoxByType } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import NamespaceSelect, {
i18n,
+ EMPTY_NAMESPACE_ID,
} from '~/vue_shared/components/namespace_select/namespace_select.vue';
-import { user, group, namespaces } from './mock_data';
+import { userNamespaces, groupNamespaces } from './mock_data';
+
+const FLAT_NAMESPACES = [...groupNamespaces, ...userNamespaces];
+const EMPTY_NAMESPACE_TITLE = 'Empty namespace TEST';
+const EMPTY_NAMESPACE_ITEM = { id: EMPTY_NAMESPACE_ID, humanName: EMPTY_NAMESPACE_TITLE };
describe('Namespace Select', () => {
let wrapper;
@@ -11,71 +17,115 @@ describe('Namespace Select', () => {
const createComponent = (props = {}) =>
shallowMountExtended(NamespaceSelect, {
propsData: {
- data: namespaces,
+ userNamespaces,
+ groupNamespaces,
...props,
},
+ stubs: {
+ // We have to "full" mount GlDropdown so that slot children will render
+ GlDropdown,
+ },
});
const wrappersText = (arr) => arr.wrappers.map((w) => w.text());
- const flatNamespaces = () => [...group, ...user];
const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findDropdownAttributes = (attr) => findDropdown().attributes(attr);
- const selectedDropdownItemText = () => findDropdownAttributes('text');
+ const findDropdownText = () => findDropdown().props('text');
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findDropdownItemsTexts = () => findDropdownItems().wrappers.map((x) => x.text());
const findSectionHeaders = () => wrapper.findAllComponents(GlDropdownSectionHeader);
-
- beforeEach(() => {
- wrapper = createComponent();
- });
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+ const search = (term) => findSearchBox().vm.$emit('input', term);
afterEach(() => {
wrapper.destroy();
});
- it('renders the dropdown', () => {
- expect(findDropdown().exists()).toBe(true);
- });
+ describe('default', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
- it('renders each dropdown item', () => {
- const items = findDropdownItems().wrappers;
- expect(items).toHaveLength(flatNamespaces().length);
- });
+ it('renders the dropdown', () => {
+ expect(findDropdown().exists()).toBe(true);
+ });
- it('renders the human name for each item', () => {
- const dropdownItems = wrappersText(findDropdownItems());
- const flatNames = flatNamespaces().map(({ humanName }) => humanName);
- expect(dropdownItems).toEqual(flatNames);
- });
+ it('renders each dropdown item', () => {
+ expect(findDropdownItemsTexts()).toEqual(FLAT_NAMESPACES.map((x) => x.humanName));
+ });
+
+ it('renders default dropdown text', () => {
+ expect(findDropdownText()).toBe(i18n.DEFAULT_TEXT);
+ });
+
+ it('splits group and user namespaces', () => {
+ const headers = findSectionHeaders();
+ expect(wrappersText(headers)).toEqual([i18n.GROUPS, i18n.USERS]);
+ });
- it('sets the initial dropdown text', () => {
- expect(selectedDropdownItemText()).toBe(i18n.DEFAULT_TEXT);
+ it('does not render wrapper as full width', () => {
+ expect(findDropdown().attributes('block')).toBeUndefined();
+ });
});
- it('splits group and user namespaces', () => {
- const headers = findSectionHeaders();
- expect(headers).toHaveLength(2);
- expect(wrappersText(headers)).toEqual([i18n.GROUPS, i18n.USERS]);
+ it('with defaultText, it overrides dropdown text', () => {
+ const textOverride = 'Select an option';
+
+ wrapper = createComponent({ defaultText: textOverride });
+
+ expect(findDropdownText()).toBe(textOverride);
});
- it('sets the dropdown to full width', () => {
- expect(findDropdownAttributes('block')).toBeUndefined();
+ it('with includeHeaders=false, hides group/user headers', () => {
+ wrapper = createComponent({ includeHeaders: false });
+
+ expect(findSectionHeaders()).toHaveLength(0);
+ });
+ it('with fullWidth=true, sets the dropdown to full width', () => {
wrapper = createComponent({ fullWidth: true });
- expect(findDropdownAttributes('block')).not.toBeUndefined();
- expect(findDropdownAttributes('block')).toBe('true');
+ expect(findDropdown().attributes('block')).toBe('true');
+ });
+
+ describe('with search', () => {
+ it.each`
+ term | includeEmptyNamespace | expectedItems
+ ${''} | ${false} | ${[...groupNamespaces, ...userNamespaces]}
+ ${'sub'} | ${false} | ${[groupNamespaces[1]]}
+ ${'User'} | ${false} | ${[...userNamespaces]}
+ ${'User'} | ${true} | ${[...userNamespaces]}
+ ${'namespace'} | ${true} | ${[EMPTY_NAMESPACE_ITEM, ...userNamespaces]}
+ `(
+ 'with term=$term and includeEmptyNamespace=$includeEmptyNamespace, should show $expectedItems.length',
+ async ({ term, includeEmptyNamespace, expectedItems }) => {
+ wrapper = createComponent({
+ includeEmptyNamespace,
+ emptyNamespaceTitle: EMPTY_NAMESPACE_TITLE,
+ });
+
+ search(term);
+
+ await nextTick();
+
+ const expected = expectedItems.map((x) => x.humanName);
+
+ expect(findDropdownItemsTexts()).toEqual(expected);
+ },
+ );
});
describe('with a selected namespace', () => {
const selectedGroupIndex = 1;
- const selectedItem = group[selectedGroupIndex];
+ const selectedItem = groupNamespaces[selectedGroupIndex];
beforeEach(() => {
+ wrapper = createComponent();
+
findDropdownItems().at(selectedGroupIndex).vm.$emit('click');
});
it('sets the dropdown text', () => {
- expect(selectedDropdownItemText()).toBe(selectedItem.humanName);
+ expect(findDropdownText()).toBe(selectedItem.humanName);
});
it('emits the `select` event when a namespace is selected', () => {
@@ -83,4 +133,37 @@ describe('Namespace Select', () => {
expect(wrapper.emitted('select')).toEqual([args]);
});
});
+
+ describe('with an empty namespace option', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ includeEmptyNamespace: true,
+ emptyNamespaceTitle: EMPTY_NAMESPACE_TITLE,
+ });
+ });
+
+ it('includes the empty namespace', () => {
+ const first = findDropdownItems().at(0);
+
+ expect(first.text()).toBe(EMPTY_NAMESPACE_TITLE);
+ });
+
+ it('emits the `select` event when a namespace is selected', () => {
+ findDropdownItems().at(0).vm.$emit('click');
+
+ expect(wrapper.emitted('select')).toEqual([[EMPTY_NAMESPACE_ITEM]]);
+ });
+
+ it.each`
+ desc | term | shouldShow
+ ${'should hide empty option'} | ${'group'} | ${false}
+ ${'should show empty option'} | ${'Empty'} | ${true}
+ `('when search for $term, $desc', async ({ term, shouldShow }) => {
+ search(term);
+
+ await nextTick();
+
+ expect(findDropdownItemsTexts().includes(EMPTY_NAMESPACE_TITLE)).toBe(shouldShow);
+ });
+ });
});
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/pikaday_spec.js b/spec/frontend/vue_shared/components/pikaday_spec.js
deleted file mode 100644
index fed4ce5e696..00000000000
--- a/spec/frontend/vue_shared/components/pikaday_spec.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import { GlDatepicker } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import datePicker from '~/vue_shared/components/pikaday.vue';
-
-describe('datePicker', () => {
- let wrapper;
-
- const buildWrapper = (propsData = {}) => {
- wrapper = shallowMount(datePicker, {
- propsData,
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
- it('should emit newDateSelected when GlDatePicker emits the input event', () => {
- const minDate = new Date();
- const maxDate = new Date();
- const selectedDate = new Date();
- const theDate = selectedDate.toISOString().slice(0, 10);
-
- buildWrapper({ minDate, maxDate, selectedDate });
-
- expect(wrapper.find(GlDatepicker).props()).toMatchObject({
- minDate,
- maxDate,
- value: selectedDate,
- });
- wrapper.find(GlDatepicker).vm.$emit('input', selectedDate);
- expect(wrapper.emitted('newDateSelected')[0][0]).toBe(theDate);
- });
- it('should emit the hidePicker event when GlDatePicker emits the close event', () => {
- buildWrapper();
-
- wrapper.find(GlDatepicker).vm.$emit('close');
-
- expect(wrapper.emitted('hidePicker')).toHaveLength(1);
- });
-});
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/resizable_chart/resizable_chart_container_spec.js b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js
index 40f0c0f29f2..7536df24ac6 100644
--- a/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js
+++ b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
import $ from 'jquery';
-import Vue from 'vue';
+import { nextTick } from 'vue';
import ResizableChartContainer from '~/vue_shared/components/resizable_chart/resizable_chart_container.vue';
jest.mock('~/lib/utils/common_utils', () => ({
@@ -35,7 +35,7 @@ describe('Resizable Chart Container', () => {
expect(wrapper.element).toMatchSnapshot();
});
- it('updates the slot width and height props', () => {
+ it('updates the slot width and height props', async () => {
const width = 1920;
const height = 1080;
@@ -44,13 +44,12 @@ describe('Resizable Chart Container', () => {
$(document).trigger('content.resize');
- return Vue.nextTick().then(() => {
- const widthNode = wrapper.find('.slot > .width');
- const heightNode = wrapper.find('.slot > .height');
+ await nextTick();
+ const widthNode = wrapper.find('.slot > .width');
+ const heightNode = wrapper.find('.slot > .height');
- expect(parseInt(widthNode.text(), 10)).toEqual(width);
- expect(parseInt(heightNode.text(), 10)).toEqual(height);
- });
+ expect(parseInt(widthNode.text(), 10)).toEqual(width);
+ expect(parseInt(heightNode.text(), 10)).toEqual(height);
});
it('calls onResize on manual resize', () => {
diff --git a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js
index e74a867ec97..0da9939e97f 100644
--- a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js
+++ b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js
@@ -77,8 +77,7 @@ describe('RunnerInstructionsModal component', () => {
runnerSetupInstructionsHandler = jest.fn().mockResolvedValue(mockGraphqlInstructions);
createComponent();
-
- await nextTick();
+ await waitForPromises();
});
afterEach(() => {
@@ -113,13 +112,15 @@ describe('RunnerInstructionsModal component', () => {
});
});
- it('binary instructions are shown', () => {
+ it('binary instructions are shown', async () => {
+ await waitForPromises();
const instructions = findBinaryInstructions().text();
expect(instructions).toBe(installInstructions);
});
- it('register command is shown with a replaced token', () => {
+ it('register command is shown with a replaced token', async () => {
+ await waitForPromises();
const instructions = findRegisterCommand().text();
expect(instructions).toBe(
@@ -130,7 +131,7 @@ describe('RunnerInstructionsModal component', () => {
describe('when a register token is not shown', () => {
beforeEach(async () => {
createComponent({ props: { registrationToken: undefined } });
- await nextTick();
+ await waitForPromises();
});
it('register command is shown without a defined registration token', () => {
@@ -198,16 +199,17 @@ describe('RunnerInstructionsModal component', () => {
expect(findSkeletonLoader().exists()).toBe(true);
expect(findGlLoadingIcon().exists()).toBe(false);
- await nextTick(); // wait for platforms
+ await nextTick();
+ jest.runOnlyPendingTimers();
+ await nextTick();
+ await nextTick();
expect(findGlLoadingIcon().exists()).toBe(true);
});
it('once loaded, should not show a loading state', async () => {
createComponent();
-
- await nextTick(); // wait for platforms
- await nextTick(); // wait for architectures
+ await waitForPromises();
expect(findSkeletonLoader().exists()).toBe(false);
expect(findGlLoadingIcon().exists()).toBe(false);
diff --git a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js
index 23f8d6afcb5..9a95a838291 100644
--- a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js
+++ b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js
@@ -22,9 +22,9 @@ describe('RunnerInstructions component', () => {
wrapper.destroy();
});
- it('should show the "Show Runner installation instructions" button', () => {
+ it('should show the "Show runner installation instructions" button', () => {
expect(findModalButton().exists()).toBe(true);
- expect(findModalButton().text()).toBe('Show Runner installation instructions');
+ expect(findModalButton().text()).toBe('Show runner installation instructions');
});
it('should not render the modal once mounted', () => {
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
deleted file mode 100644
index 79e41ed0c9e..00000000000
--- a/spec/frontend/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlIcon } from '@gitlab/ui';
-import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-
-import CollapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
-
-describe('CollapsedCalendarIcon', () => {
- let wrapper;
-
- const defaultProps = {
- containerClass: 'test-class',
- text: 'text',
- tooltipText: 'tooltip text',
- showIcon: false,
- };
-
- const createComponent = ({ props = {} } = {}) => {
- wrapper = shallowMount(CollapsedCalendarIcon, {
- propsData: { ...defaultProps, ...props },
- directives: {
- GlTooltip: createMockDirective(),
- },
- });
- };
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- const findGlIcon = () => wrapper.findComponent(GlIcon);
- const getTooltip = () => getBinding(wrapper.element, 'gl-tooltip');
-
- it('adds class to container', () => {
- expect(wrapper.classes()).toContain(defaultProps.containerClass);
- });
-
- it('does not render calendar icon when showIcon is false', () => {
- expect(findGlIcon().exists()).toBe(false);
- });
-
- it('renders calendar icon when showIcon is true', () => {
- createComponent({
- props: { showIcon: true },
- });
-
- expect(findGlIcon().exists()).toBe(true);
- });
-
- it('renders text', () => {
- expect(wrapper.text()).toBe(defaultProps.text);
- });
-
- it('renders tooltipText as tooltip', () => {
- expect(getTooltip().value).toBe(defaultProps.tooltipText);
- });
-
- it('emits click event when container is clicked', async () => {
- wrapper.trigger('click');
-
- await wrapper.vm.$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
deleted file mode 100644
index 263d1e9d947..00000000000
--- a/spec/frontend/vue_shared/components/sidebar/date_picker_spec.js
+++ /dev/null
@@ -1,125 +0,0 @@
-import { GlLoadingIcon } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
-import DatePicker from '~/vue_shared/components/pikaday.vue';
-import SidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue';
-
-describe('SidebarDatePicker', () => {
- let wrapper;
-
- const createComponent = (propsData = {}, data = {}) => {
- wrapper = mount(SidebarDatePicker, {
- propsData,
- data: () => data,
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- const findDatePicker = () => wrapper.findComponent(DatePicker);
- const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
- const findEditButton = () => wrapper.find('.title .btn-blank');
- const findRemoveButton = () => wrapper.find('.value-content .btn-blank');
- const findSidebarToggle = () => wrapper.find('.title .gutter-toggle');
- const findValueContent = () => wrapper.find('.value-content');
-
- it('should emit toggleCollapse when collapsed toggle sidebar is clicked', () => {
- createComponent();
-
- wrapper.find('.issuable-sidebar-header .gutter-toggle').trigger('click');
-
- expect(wrapper.emitted('toggleCollapse')).toEqual([[]]);
- });
-
- it('should render collapsed-calendar-icon', () => {
- createComponent();
-
- expect(wrapper.find('.sidebar-collapsed-icon').exists()).toBe(true);
- });
-
- it('should render value when not editing', () => {
- createComponent();
-
- expect(findValueContent().exists()).toBe(true);
- });
-
- it('should render None if there is no selectedDate', () => {
- createComponent();
-
- expect(findValueContent().text()).toBe('None');
- });
-
- it('should render date-picker when editing', () => {
- createComponent({}, { editing: true });
-
- expect(findDatePicker().exists()).toBe(true);
- });
-
- it('should render label', () => {
- const label = 'label';
- createComponent({ label });
- expect(wrapper.find('.title').text()).toBe(label);
- });
-
- it('should render loading-icon when isLoading', () => {
- createComponent({ isLoading: true });
- expect(findLoadingIcon().exists()).toBe(true);
- });
-
- describe('editable', () => {
- beforeEach(() => {
- createComponent({ editable: true });
- });
-
- it('should render edit button', () => {
- expect(findEditButton().text()).toBe('Edit');
- });
-
- it('should enable editing when edit button is clicked', async () => {
- findEditButton().trigger('click');
-
- await wrapper.vm.$nextTick();
-
- expect(wrapper.vm.editing).toBe(true);
- });
- });
-
- it('should render date if selectedDate', () => {
- createComponent({ selectedDate: new Date('07/07/2017') });
-
- expect(wrapper.find('.value-content strong').text()).toBe('Jul 7, 2017');
- });
-
- describe('selectedDate and editable', () => {
- beforeEach(() => {
- createComponent({ selectedDate: new Date('07/07/2017'), editable: true });
- });
-
- it('should render remove button if selectedDate and editable', () => {
- expect(findRemoveButton().text()).toBe('remove');
- });
-
- it('should emit saveDate with null when remove button is clicked', () => {
- findRemoveButton().trigger('click');
-
- expect(wrapper.emitted('saveDate')).toEqual([[null]]);
- });
- });
-
- describe('showToggleSidebar', () => {
- beforeEach(() => {
- createComponent({ showToggleSidebar: true });
- });
-
- it('should render toggle-sidebar when showToggleSidebar', () => {
- expect(findSidebarToggle().exists()).toBe(true);
- });
-
- it('should emit toggleCollapse when toggle sidebar is clicked', () => {
- findSidebarToggle().trigger('click');
-
- expect(wrapper.emitted('toggleCollapse')).toEqual([[]]);
- });
- });
-});
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/labels_select_widget/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js
index a4199bb3e27..67e1a3ce932 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js
@@ -117,9 +117,15 @@ describe('LabelsSelectRoot', () => {
it('renders dropdown value component when query labels is resolved', () => {
expect(findDropdownValue().exists()).toBe(true);
- expect(findDropdownValue().props('selectedLabels')).toEqual(
- issuableLabelsQueryResponse.data.workspace.issuable.labels.nodes,
- );
+ expect(findDropdownValue().props('selectedLabels')).toEqual([
+ {
+ color: '#330066',
+ description: null,
+ id: 'gid://gitlab/ProjectLabel/1',
+ title: 'Label1',
+ textColor: '#000000',
+ },
+ ]);
});
it('emits `onLabelRemove` event on dropdown value label remove event', () => {
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js
index 6ef54ce37ce..49224fb915c 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/mock_data.js
@@ -96,6 +96,7 @@ export const workspaceLabelsQueryResponse = {
labels: {
nodes: [
{
+ __typename: 'Label',
color: '#330066',
description: null,
id: 'gid://gitlab/ProjectLabel/1',
@@ -103,6 +104,7 @@ export const workspaceLabelsQueryResponse = {
textColor: '#000000',
},
{
+ __typename: 'Label',
color: '#2f7b2e',
description: null,
id: 'gid://gitlab/ProjectLabel/2',
@@ -125,6 +127,7 @@ export const issuableLabelsQueryResponse = {
labels: {
nodes: [
{
+ __typename: 'Label',
color: '#330066',
description: null,
id: 'gid://gitlab/ProjectLabel/1',
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/source_viewer_spec.js b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
index 094d8d42a47..2010bac7060 100644
--- a/spec/frontend/vue_shared/components/source_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
@@ -1,8 +1,10 @@
import hljs from 'highlight.js/lib/core';
+import { GlLoadingIcon } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueRouter from 'vue-router';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import SourceViewer from '~/vue_shared/components/source_viewer.vue';
+import SourceViewer from '~/vue_shared/components/source_viewer/source_viewer.vue';
+import { ROUGE_TO_HLJS_LANGUAGE_MAP } from '~/vue_shared/components/source_viewer/constants';
import LineNumbers from '~/vue_shared/components/line_numbers.vue';
import waitForPromises from 'helpers/wait_for_promises';
@@ -12,42 +14,50 @@ const router = new VueRouter();
describe('Source Viewer component', () => {
let wrapper;
+ const language = 'docker';
+ const mappedLanguage = ROUGE_TO_HLJS_LANGUAGE_MAP[language];
const content = `// Some source code`;
+ const DEFAULT_BLOB_DATA = { language, rawTextBlob: content };
const highlightedContent = `<span data-testid='test-highlighted' id='LC1'>${content}</span><span id='LC2'></span>`;
- const language = 'javascript';
- hljs.highlight.mockImplementation(() => ({ value: highlightedContent }));
- hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
-
- const createComponent = async (props = {}) => {
+ const createComponent = async (blob = {}) => {
wrapper = shallowMountExtended(SourceViewer, {
router,
- propsData: { content, language, ...props },
+ propsData: { blob: { ...DEFAULT_BLOB_DATA, ...blob } },
});
await waitForPromises();
};
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findLineNumbers = () => wrapper.findComponent(LineNumbers);
const findHighlightedContent = () => wrapper.findByTestId('test-highlighted');
const findFirstLine = () => wrapper.find('#LC1');
- beforeEach(() => createComponent());
+ beforeEach(() => {
+ hljs.highlight.mockImplementation(() => ({ value: highlightedContent }));
+ hljs.highlightAuto.mockImplementation(() => ({ value: highlightedContent }));
+
+ return createComponent();
+ });
afterEach(() => wrapper.destroy());
describe('highlight.js', () => {
it('registers the language definition', async () => {
- const languageDefinition = await import(`highlight.js/lib/languages/${language}`);
+ const languageDefinition = await import(`highlight.js/lib/languages/${mappedLanguage}`);
- expect(hljs.registerLanguage).toHaveBeenCalledWith(language, languageDefinition.default);
+ expect(hljs.registerLanguage).toHaveBeenCalledWith(
+ mappedLanguage,
+ languageDefinition.default,
+ );
});
it('highlights the content', () => {
- expect(hljs.highlight).toHaveBeenCalledWith(content, { language });
+ expect(hljs.highlight).toHaveBeenCalledWith(content, { language: mappedLanguage });
});
- describe('auto-detect enabled', () => {
- beforeEach(() => createComponent({ autoDetect: true }));
+ describe('auto-detects if a language cannot be loaded', () => {
+ beforeEach(() => createComponent({ language: 'some_unknown_language' }));
it('highlights the content with auto-detection', () => {
expect(hljs.highlightAuto).toHaveBeenCalledWith(content);
@@ -56,6 +66,13 @@ describe('Source Viewer component', () => {
});
describe('rendering', () => {
+ it('renders a loading icon if no highlighted content is available yet', async () => {
+ hljs.highlight.mockImplementation(() => ({ value: null }));
+ await createComponent();
+
+ expect(findLoadingIcon().exists()).toBe(true);
+ });
+
it('renders Line Numbers', () => {
expect(findLineNumbers().props('lines')).toBe(1);
});
diff --git a/spec/frontend/vue_shared/components/source_viewer/utils_spec.js b/spec/frontend/vue_shared/components/source_viewer/utils_spec.js
new file mode 100644
index 00000000000..937c3b26c67
--- /dev/null
+++ b/spec/frontend/vue_shared/components/source_viewer/utils_spec.js
@@ -0,0 +1,13 @@
+import { wrapLines } from '~/vue_shared/components/source_viewer/utils';
+
+describe('Wrap lines', () => {
+ it.each`
+ input | output
+ ${'line 1'} | ${'<span id="LC1" class="line">line 1</span>'}
+ ${'line 1\nline 2'} | ${`<span id="LC1" class="line">line 1</span>\n<span id="LC2" class="line">line 2</span>`}
+ ${'<span class="hljs-code">line 1\nline 2</span>'} | ${`<span id="LC1" class="hljs-code">line 1\n<span id="LC2" class="line">line 2</span></span>`}
+ ${'<span class="hljs-code">```bash'} | ${'<span id="LC1" class="hljs-code">```bash'}
+ `('returns lines wrapped in spans containing line numbers', ({ input, output }) => {
+ expect(wrapLines(input)).toBe(output);
+ });
+});
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/__snapshots__/upload_dropzone_spec.js.snap b/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap
index 0f1e118d44c..a613b325462 100644
--- a/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap
+++ b/spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap
@@ -6,6 +6,7 @@ exports[`Upload dropzone component correctly overrides description and drop mess
>
<button
class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3"
+ type="button"
>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
@@ -86,6 +87,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
>
<button
class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3"
+ type="button"
>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
@@ -170,6 +172,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
>
<button
class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3"
+ type="button"
>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
@@ -254,6 +257,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
>
<button
class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3"
+ type="button"
>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
@@ -339,6 +343,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
>
<button
class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3"
+ type="button"
>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
@@ -424,6 +429,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
>
<button
class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3"
+ type="button"
>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
@@ -509,6 +515,7 @@ exports[`Upload dropzone component when no slot provided renders default dropzon
>
<button
class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-p-3"
+ type="button"
>
<div
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-text-center gl-flex-direction-column"
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..21e9b401215 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');
@@ -15,6 +16,7 @@ describe('Upload dropzone component', () => {
const findDropzoneArea = () => wrapper.find('[data-testid="dropzone-area"]');
const findIcon = () => wrapper.find(GlIcon);
const findUploadText = () => wrapper.find('[data-testid="upload-text"]').text();
+ const findFileInput = () => wrapper.find('input[type="file"]');
function createComponent({ slots = {}, data = {}, props = {} } = {}) {
wrapper = shallowMount(UploadDropzone, {
@@ -84,47 +86,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]]);
});
});
@@ -203,4 +198,60 @@ describe('Upload dropzone component', () => {
expect(wrapper.element).toMatchSnapshot();
});
+
+ describe('file input form name', () => {
+ it('applies inputFieldName as file input name', () => {
+ createComponent({ props: { inputFieldName: 'test_field_name' } });
+ expect(findFileInput().attributes('name')).toBe('test_field_name');
+ });
+
+ it('uses default file input name if no inputFieldName provided', () => {
+ createComponent();
+ expect(findFileInput().attributes('name')).toBe('upload_file');
+ });
+ });
+
+ describe('updates file input files value', () => {
+ // NOTE: the component assigns dropped files from the drop event to the
+ // input.files property. There's a restriction that nothing but a FileList
+ // can be assigned to this property. While FileList can't be created
+ // manually: it has no constructor. And currently there's no good workaround
+ // for jsdom. So we have to stub the file input in vm.$refs to ensure that
+ // the files property is updated. This enforces following tests to know a
+ // bit too much about the SUT internals See this thread for more details on
+ // FileList in jsdom: https://github.com/jsdom/jsdom/issues/1272
+ function stubFileInputOnWrapper() {
+ const fakeFileInput = { files: [] };
+ wrapper.vm.$refs.fileUpload = fakeFileInput;
+ }
+
+ it('assigns dragged files to the input files property', async () => {
+ const mockFile = { name: 'test', type: 'image/jpg' };
+ const mockEvent = mockDragEvent({ files: [mockFile] });
+ createComponent({ props: { shouldUpdateInputOnFileDrop: true } });
+ stubFileInputOnWrapper();
+
+ wrapper.trigger('dragenter', mockEvent);
+ await nextTick();
+ wrapper.trigger('drop', mockEvent);
+ await nextTick();
+
+ expect(wrapper.vm.$refs.fileUpload.files).toEqual([mockFile]);
+ });
+
+ it('throws an error when multiple files are dropped on a single file input dropzone', async () => {
+ const mockFile = { name: 'test', type: 'image/jpg' };
+ const mockEvent = mockDragEvent({ files: [mockFile, mockFile] });
+ createComponent({ props: { shouldUpdateInputOnFileDrop: true, singleFileSelection: true } });
+ stubFileInputOnWrapper();
+
+ wrapper.trigger('dragenter', mockEvent);
+ await nextTick();
+ wrapper.trigger('drop', mockEvent);
+ await nextTick();
+
+ expect(wrapper.vm.$refs.fileUpload.files).toEqual([]);
+ expect(wrapper.emitted('error')).toHaveLength(1);
+ });
+ });
});
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/user_select_spec.js b/spec/frontend/vue_shared/components/user_select_spec.js
index 8994e16e517..411a15e1c74 100644
--- a/spec/frontend/vue_shared/components/user_select_spec.js
+++ b/spec/frontend/vue_shared/components/user_select_spec.js
@@ -104,14 +104,14 @@ describe('User select dropdown', () => {
createComponent({ participantsQueryHandler: mockError });
await waitForPromises();
- expect(wrapper.emitted('error')).toEqual([[], []]);
+ expect(wrapper.emitted('error')).toEqual([[]]);
});
it('emits an `error` event if search query was rejected', async () => {
createComponent({ searchQueryHandler: mockError });
await waitForSearch();
- expect(wrapper.emitted('error')).toEqual([[], []]);
+ expect(wrapper.emitted('error')).toEqual([[]]);
});
it('renders current user if they are not in participants or assignees', async () => {
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..5589cbfd08f 100644
--- a/spec/frontend/vue_shared/components/web_ide_link_spec.js
+++ b/spec/frontend/vue_shared/components/web_ide_link_spec.js
@@ -4,6 +4,7 @@ import { nextTick } from 'vue';
import ActionsButton from '~/vue_shared/components/actions_button.vue';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
+import ConfirmForkModal from '~/vue_shared/components/confirm_fork_modal.vue';
import { stubComponent } from 'helpers/stub_component';
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
@@ -13,6 +14,7 @@ const TEST_WEB_IDE_URL = '/-/ide/project/gitlab-test/test/edit/main/-/';
const TEST_GITPOD_URL = 'https://gitpod.test/';
const TEST_USER_PREFERENCES_GITPOD_PATH = '/-/profile/preferences#user_gitpod_enabled';
const TEST_USER_PROFILE_ENABLE_GITPOD_PATH = '/-/profile?user%5Bgitpod_enabled%5D=true';
+const forkPath = '/some/fork/path';
const ACTION_EDIT = {
href: TEST_EDIT_URL,
@@ -74,6 +76,7 @@ describe('Web IDE link component', () => {
editUrl: TEST_EDIT_URL,
webIdeUrl: TEST_WEB_IDE_URL,
gitpodUrl: TEST_GITPOD_URL,
+ forkPath,
...props,
},
stubs: {
@@ -96,6 +99,7 @@ describe('Web IDE link component', () => {
const findActionsButton = () => wrapper.find(ActionsButton);
const findLocalStorageSync = () => wrapper.find(LocalStorageSync);
const findModal = () => wrapper.findComponent(GlModal);
+ const findForkConfirmModal = () => wrapper.findComponent(ConfirmForkModal);
it.each([
{
@@ -213,7 +217,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 +227,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);
@@ -231,16 +235,28 @@ describe('Web IDE link component', () => {
});
describe('edit actions', () => {
- it.each([
+ const testActions = [
{
- props: { showWebIdeButton: true, showEditButton: false },
+ props: {
+ showWebIdeButton: true,
+ showEditButton: false,
+ forkPath,
+ forkModalId: 'edit-modal',
+ },
expectedEventPayload: 'ide',
},
{
- props: { showWebIdeButton: false, showEditButton: true },
+ props: {
+ showWebIdeButton: false,
+ showEditButton: true,
+ forkPath,
+ forkModalId: 'webide-modal',
+ },
expectedEventPayload: 'simple',
},
- ])(
+ ];
+
+ it.each(testActions)(
'emits the correct event when an action handler is called',
async ({ props, expectedEventPayload }) => {
createComponent({ ...props, needsToFork: true, disableForkModal: true });
@@ -250,6 +266,29 @@ describe('Web IDE link component', () => {
expect(wrapper.emitted('edit')).toEqual([[expectedEventPayload]]);
},
);
+
+ it.each(testActions)('renders the fork confirmation modal', async ({ props }) => {
+ createComponent({ ...props, needsToFork: true });
+
+ expect(findForkConfirmModal().exists()).toBe(true);
+ expect(findForkConfirmModal().props()).toEqual({
+ visible: false,
+ forkPath,
+ modalId: props.forkModalId,
+ });
+ });
+
+ it.each(testActions)('opens the modal when the button is clicked', async ({ props }) => {
+ createComponent({ ...props, needsToFork: true }, mountExtended);
+
+ await findActionsButton().trigger('click');
+
+ expect(findForkConfirmModal().props()).toEqual({
+ visible: true,
+ forkPath,
+ modalId: props.forkModalId,
+ });
+ });
});
describe('when Gitpod is not enabled', () => {
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..65eb42ef053 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';
@@ -9,7 +10,6 @@ import { mockIssuable, mockRegularLabel, mockScopedLabel } from '../mock_data';
const createComponent = ({
issuableSymbol = '#',
issuable = mockIssuable,
- enableLabelPermalinks = true,
showCheckbox = true,
slots = {},
} = {}) =>
@@ -17,7 +17,6 @@ const createComponent = ({
propsData: {
issuableSymbol,
issuable,
- enableLabelPermalinks,
showDiscussions: true,
showCheckbox,
},
@@ -76,7 +75,7 @@ describe('IssuableItem', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.authorId).toBe(returnValue);
},
@@ -100,7 +99,7 @@ describe('IssuableItem', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.isIssuableUrlExternal).toBe(returnValue);
},
@@ -122,7 +121,7 @@ describe('IssuableItem', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.labels).toEqual(mockLabels);
});
@@ -135,7 +134,7 @@ describe('IssuableItem', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.labels).toEqual([]);
});
@@ -211,23 +210,13 @@ describe('IssuableItem', () => {
});
describe('labelTarget', () => {
- it('returns target string for a provided label param when `enableLabelPermalinks` is true', () => {
+ it('returns target string for a provided label param', () => {
wrapper = createComponent();
expect(wrapper.vm.labelTarget(mockRegularLabel)).toBe(
'?label_name[]=Documentation%20Update',
);
});
-
- it('returns string "#" for a provided label param when `enableLabelPermalinks` is false', async () => {
- wrapper = createComponent({
- enableLabelPermalinks: false,
- });
-
- await wrapper.vm.$nextTick();
-
- expect(wrapper.vm.labelTarget(mockRegularLabel)).toBe('#');
- });
});
});
@@ -248,7 +237,7 @@ describe('IssuableItem', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
const titleEl = wrapper.find('[data-testid="issuable-title"]');
@@ -264,7 +253,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 +262,7 @@ describe('IssuableItem', () => {
checked: true,
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find(GlFormCheckbox).attributes('checked')).toBe('true');
});
@@ -286,7 +275,7 @@ describe('IssuableItem', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.find('[data-testid="issuable-title"]').find(GlLink).attributes('target')).toBe(
'_blank',
@@ -301,7 +290,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/list/components/issuable_tabs_spec.js b/spec/frontend/vue_shared/issuable/list/components/issuable_tabs_spec.js
index 5723e2da586..27985895c62 100644
--- a/spec/frontend/vue_shared/issuable/list/components/issuable_tabs_spec.js
+++ b/spec/frontend/vue_shared/issuable/list/components/issuable_tabs_spec.js
@@ -1,5 +1,6 @@
import { GlTab, GlBadge } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import { mount, shallowMount } from '@vue/test-utils';
import { setLanguage } from 'helpers/locale_helper';
import IssuableTabs from '~/vue_shared/issuable/list/components/issuable_tabs.vue';
@@ -10,17 +11,18 @@ const createComponent = ({
tabs = mockIssuableListProps.tabs,
tabCounts = mockIssuableListProps.tabCounts,
currentTab = mockIssuableListProps.currentTab,
+ truncateCounts = false,
+ mountFn = shallowMount,
} = {}) =>
- mount(IssuableTabs, {
+ mountFn(IssuableTabs, {
propsData: {
tabs,
tabCounts,
currentTab,
+ truncateCounts,
},
slots: {
- 'nav-actions': `
- <button class="js-new-issuable">New issuable</button>
- `,
+ 'nav-actions': `<button class="js-new-issuable">New issuable</button>`,
},
});
@@ -29,7 +31,6 @@ describe('IssuableTabs', () => {
beforeEach(() => {
setLanguage('en');
- wrapper = createComponent();
});
afterEach(() => {
@@ -40,60 +41,71 @@ describe('IssuableTabs', () => {
const findAllGlBadges = () => wrapper.findAllComponents(GlBadge);
const findAllGlTabs = () => wrapper.findAllComponents(GlTab);
- describe('methods', () => {
- describe('isTabActive', () => {
- it.each`
- tabName | currentTab | returnValue
- ${'opened'} | ${'opened'} | ${true}
- ${'opened'} | ${'closed'} | ${false}
- `(
- 'returns $returnValue when tab name is "$tabName" is current tab is "$currentTab"',
- async ({ tabName, currentTab, returnValue }) => {
- wrapper.setProps({
- currentTab,
- });
-
- await wrapper.vm.$nextTick();
-
- expect(wrapper.vm.isTabActive(tabName)).toBe(returnValue);
- },
- );
- });
+ describe('tabs', () => {
+ it.each`
+ currentTab | returnValue
+ ${'opened'} | ${'true'}
+ ${'closed'} | ${undefined}
+ `(
+ 'when "$currentTab" is the selected tab, the Open tab is active=$returnValue',
+ ({ currentTab, returnValue }) => {
+ wrapper = createComponent({ currentTab });
+
+ const openTab = findAllGlTabs().at(0);
+
+ expect(openTab.attributes('active')).toBe(returnValue);
+ },
+ );
});
describe('template', () => {
it('renders gl-tab for each tab within `tabs` array', () => {
- const tabsEl = findAllGlTabs();
+ wrapper = createComponent();
+
+ const tabs = findAllGlTabs();
- expect(tabsEl.exists()).toBe(true);
- expect(tabsEl).toHaveLength(mockIssuableListProps.tabs.length);
+ expect(tabs).toHaveLength(mockIssuableListProps.tabs.length);
});
- it('renders gl-badge component within a tab', () => {
+ it('renders gl-badge component within a tab', async () => {
+ wrapper = createComponent({ mountFn: mount });
+ await nextTick();
+
const badges = findAllGlBadges();
// Does not render `All` badge since it has an undefined count
expect(badges).toHaveLength(2);
- expect(badges.at(0).text()).toBe('5,000');
+ expect(badges.at(0).text()).toBe('5,678');
expect(badges.at(1).text()).toBe(`${mockIssuableListProps.tabCounts.closed}`);
});
it('renders contents for slot "nav-actions"', () => {
- const buttonEl = wrapper.find('button.js-new-issuable');
+ wrapper = createComponent();
- expect(buttonEl.exists()).toBe(true);
- expect(buttonEl.text()).toBe('New issuable');
+ const button = wrapper.find('button.js-new-issuable');
+
+ expect(button.text()).toBe('New issuable');
+ });
+ });
+
+ describe('counts', () => {
+ it('can display as truncated', async () => {
+ wrapper = createComponent({ truncateCounts: true, mountFn: mount });
+ await nextTick();
+
+ expect(findAllGlBadges().at(0).text()).toBe('5.7k');
});
});
describe('events', () => {
it('gl-tab component emits `click` event on `click` event', () => {
- const tabEl = findAllGlTabs().at(0);
+ wrapper = createComponent();
+
+ const openTab = findAllGlTabs().at(0);
- tabEl.vm.$emit('click', 'opened');
+ openTab.vm.$emit('click', 'opened');
- expect(wrapper.emitted('click')).toBeTruthy();
- expect(wrapper.emitted('click')[0]).toEqual(['opened']);
+ expect(wrapper.emitted('click')).toEqual([['opened']]);
});
});
});
diff --git a/spec/frontend/vue_shared/issuable/list/mock_data.js b/spec/frontend/vue_shared/issuable/list/mock_data.js
index cfc7937b412..8640f4a2cd5 100644
--- a/spec/frontend/vue_shared/issuable/list/mock_data.js
+++ b/spec/frontend/vue_shared/issuable/list/mock_data.js
@@ -133,7 +133,7 @@ export const mockTabs = [
];
export const mockTabCounts = {
- opened: 5000,
+ opened: 5678,
closed: 0,
all: undefined,
};
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..b79dc0bf976 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';
@@ -35,6 +36,7 @@ describe('IssuableEditForm', () => {
beforeEach(() => {
wrapper = createComponent();
+ gon.features = { markdownContinueLists: true };
});
afterEach(() => {
@@ -52,7 +54,7 @@ describe('IssuableEditForm', () => {
},
});
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(wrapper.vm.title).toBe('Foo');
expect(wrapper.vm.description).toBe('Foobar');
@@ -67,7 +69,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..47bf3c8ed83 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';
@@ -69,7 +70,10 @@ describe('IssuableSidebarRoot', () => {
it('updates "collapsed_gutter" cookie value and layout classes', async () => {
await findToggleSidebarButton().trigger('click');
- expect(Cookies.set).toHaveBeenCalledWith(USER_COLLAPSED_GUTTER_COOKIE, true);
+ expect(Cookies.set).toHaveBeenCalledWith(USER_COLLAPSED_GUTTER_COOKIE, true, {
+ expires: 365,
+ secure: false,
+ });
assertPageLayoutClasses({ isExpanded: false });
});
});
@@ -88,7 +92,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/vue_shared/security_reports/components/manage_via_mr_spec.js b/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js
index facbd51168c..39909e26ef0 100644
--- a/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js
+++ b/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js
@@ -16,7 +16,7 @@ jest.mock('~/lib/utils/url_utility');
Vue.use(VueApollo);
-const projectPath = 'namespace/project';
+const projectFullPath = 'namespace/project';
describe('ManageViaMr component', () => {
let wrapper;
@@ -40,7 +40,7 @@ describe('ManageViaMr component', () => {
wrapper = extendedWrapper(
mount(ManageViaMr, {
provide: {
- projectPath,
+ projectFullPath,
},
propsData: {
feature: {
@@ -65,7 +65,7 @@ describe('ManageViaMr component', () => {
// the ones available in the current test context.
const supportedReportTypes = Object.entries(featureToMutationMap).map(
([featureType, { getMutationPayload, mutationId }]) => {
- const { mutation, variables: mutationVariables } = getMutationPayload(projectPath);
+ const { mutation, variables: mutationVariables } = getMutationPayload(projectFullPath);
return [humanize(featureType), featureType, mutation, mutationId, mutationVariables];
},
);
diff --git a/spec/frontend/vue_shared/security_reports/mock_data.js b/spec/frontend/vue_shared/security_reports/mock_data.js
index 2b1513bb0f8..dac9accbbf5 100644
--- a/spec/frontend/vue_shared/security_reports/mock_data.js
+++ b/spec/frontend/vue_shared/security_reports/mock_data.js
@@ -324,7 +324,9 @@ export const secretDetectionDiffSuccessMock = {
export const securityReportMergeRequestDownloadPathsQueryNoArtifactsResponse = {
project: {
+ id: 'project-1',
mergeRequest: {
+ id: 'mr-1',
headPipeline: {
id: 'gid://gitlab/Ci::Pipeline/176',
jobs: {
diff --git a/spec/frontend/whats_new/components/app_spec.js b/spec/frontend/whats_new/components/app_spec.js
index 12034346aba..945727cd664 100644
--- a/spec/frontend/whats_new/components/app_spec.js
+++ b/spec/frontend/whats_new/components/app_spec.js
@@ -1,5 +1,6 @@
import { GlDrawer, GlInfiniteScroll } from '@gitlab/ui';
-import { createLocalVue, mount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
@@ -12,8 +13,7 @@ jest.mock('~/whats_new/utils/get_drawer_body_height', () => ({
getDrawerBodyHeight: jest.fn().mockImplementation(() => MOCK_DRAWER_BODY_HEIGHT),
}));
-const localVue = createLocalVue();
-localVue.use(Vuex);
+Vue.use(Vuex);
describe('App', () => {
let wrapper;
@@ -46,7 +46,6 @@ describe('App', () => {
});
wrapper = mount(App, {
- localVue,
store,
propsData: buildProps(),
directives: {
@@ -68,7 +67,7 @@ describe('App', () => {
{ title: 'Whats New Drawer', url: 'www.url.com', release: 3.11 },
];
wrapper.vm.$store.state.drawerBodyHeight = MOCK_DRAWER_BODY_HEIGHT;
- await wrapper.vm.$nextTick();
+ await nextTick();
};
afterEach(() => {
@@ -109,7 +108,7 @@ describe('App', () => {
it.each([true, false])('passes open property', async (openState) => {
wrapper.vm.$store.state.open = openState;
- await wrapper.vm.$nextTick();
+ await nextTick();
expect(getDrawer().props('open')).toBe(openState);
});
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index 9741a193258..a98722bc465 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -34,3 +34,17 @@ export const updateWorkItemMutationResponse = {
},
},
};
+
+export const projectWorkItemTypesQueryResponse = {
+ data: {
+ workspace: {
+ id: '1',
+ workItemTypes: {
+ nodes: [
+ { id: 'work-item-1', name: 'Issue' },
+ { id: 'work-item-2', name: 'Incident' },
+ ],
+ },
+ },
+ },
+};
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..b9fef0eaa6a 100644
--- a/spec/frontend/work_items/pages/create_work_item_spec.js
+++ b/spec/frontend/work_items/pages/create_work_item_spec.js
@@ -1,12 +1,14 @@
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-import { GlAlert } from '@gitlab/ui';
+import { GlAlert, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import CreateWorkItem from '~/work_items/pages/create_work_item.vue';
import ItemTitle from '~/work_items/components/item_title.vue';
import { resolvers } from '~/work_items/graphql/resolvers';
+import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
+import { projectWorkItemTypesQueryResponse } from '../mock_data';
Vue.use(VueApollo);
@@ -14,13 +16,20 @@ describe('Create work item component', () => {
let wrapper;
let fakeApollo;
+ const querySuccessHandler = jest.fn().mockResolvedValue(projectWorkItemTypesQueryResponse);
+
const findAlert = () => wrapper.findComponent(GlAlert);
const findTitleInput = () => wrapper.findComponent(ItemTitle);
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+
const findCreateButton = () => wrapper.find('[data-testid="create-button"]');
const findCancelButton = () => wrapper.find('[data-testid="cancel-button"]');
+ const findContent = () => wrapper.find('[data-testid="content"]');
+ const findLoadingTypesIcon = () => wrapper.find('[data-testid="loading-types"]');
- const createComponent = ({ data = {} } = {}) => {
- fakeApollo = createMockApollo([], resolvers);
+ const createComponent = ({ data = {}, props = {}, queryHandler = querySuccessHandler } = {}) => {
+ fakeApollo = createMockApollo([[projectWorkItemTypesQuery, queryHandler]], resolvers);
wrapper = shallowMount(CreateWorkItem, {
apolloProvider: fakeApollo,
data() {
@@ -28,12 +37,18 @@ describe('Create work item component', () => {
...data,
};
},
+ propsData: {
+ ...props,
+ },
mocks: {
$router: {
go: jest.fn(),
push: jest.fn(),
},
},
+ provide: {
+ fullPath: 'full-path',
+ },
});
};
@@ -54,40 +69,141 @@ describe('Create work item component', () => {
expect(findCreateButton().props('disabled')).toBe(true);
});
- it('redirects to the previous page on Cancel button 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');
+ });
+
+ it('does not add padding for content', () => {
+ expect(findContent().classes('gl-px-5')).toBe(false);
+ });
+ });
+
+ 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.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('adds padding for content', () => {
+ expect(findContent().classes('gl-px-5')).toBe(true);
+ });
+ });
+
+ it('displays a loading icon inside dropdown when work items query is loading', () => {
createComponent();
- findCancelButton().vm.$emit('click');
- expect(wrapper.vm.$router.go).toHaveBeenCalledWith(-1);
+ expect(findLoadingTypesIcon().exists()).toBe(true);
+ });
+
+ it('displays an alert when work items query is rejected', async () => {
+ createComponent({ queryHandler: jest.fn().mockRejectedValue('Houston, we have a problem') });
+ await waitForPromises();
+
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toContain('fetching work item types');
+ });
+
+ describe('when work item types are fetched', () => {
+ beforeEach(() => {
+ createComponent();
+ return waitForPromises();
+ });
+
+ it('displays a list of work item types', () => {
+ expect(findDropdownItems()).toHaveLength(2);
+ expect(findDropdownItems().at(0).text()).toContain('Issue');
+ });
+
+ it('selects a work item type on click', async () => {
+ expect(findDropdown().props('text')).toBe('Type');
+ findDropdownItems().at(0).vm.$emit('click');
+ await nextTick();
+
+ expect(findDropdown().props('text')).toBe('Issue');
+ });
});
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/work_items/pages/work_item_root_spec.js b/spec/frontend/work_items/pages/work_item_root_spec.js
index ea26b2b4fb3..d0e40680b55 100644
--- a/spec/frontend/work_items/pages/work_item_root_spec.js
+++ b/spec/frontend/work_items/pages/work_item_root_spec.js
@@ -23,7 +23,11 @@ describe('Work items root component', () => {
const findTitle = () => wrapper.findComponent(ItemTitle);
const createComponent = ({ queryResponse = workItemQueryResponse } = {}) => {
- fakeApollo = createMockApollo([], resolvers);
+ fakeApollo = createMockApollo([], resolvers, {
+ possibleTypes: {
+ LocalWorkItemWidget: ['LocalTitleWidget'],
+ },
+ });
fakeApollo.clients.defaultClient.cache.writeQuery({
query: workItemQuery,
variables: {
diff --git a/spec/frontend/work_items/router_spec.js b/spec/frontend/work_items/router_spec.js
index 6017c9d9dbb..c583b5a5d4f 100644
--- a/spec/frontend/work_items/router_spec.js
+++ b/spec/frontend/work_items/router_spec.js
@@ -15,6 +15,16 @@ describe('Work items router', () => {
wrapper = mount(App, {
router,
+ provide: {
+ fullPath: 'full-path',
+ },
+ mocks: {
+ $apollo: {
+ queries: {
+ workItemTypes: {},
+ },
+ },
+ },
});
};
diff --git a/spec/frontend/work_items_hierarchy/components/app_spec.js b/spec/frontend/work_items_hierarchy/components/app_spec.js
new file mode 100644
index 00000000000..092e9c90553
--- /dev/null
+++ b/spec/frontend/work_items_hierarchy/components/app_spec.js
@@ -0,0 +1,63 @@
+import { nextTick } from 'vue';
+import { createLocalVue, mount } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import { GlBanner } from '@gitlab/ui';
+import App from '~/work_items_hierarchy/components/app.vue';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+
+const localVue = createLocalVue();
+localVue.use(VueApollo);
+
+describe('WorkItemsHierarchy App', () => {
+ let wrapper;
+ const createComponent = (props = {}, data = {}) => {
+ wrapper = extendedWrapper(
+ mount(App, {
+ localVue,
+ provide: {
+ illustrationPath: '/foo.svg',
+ licensePlan: 'free',
+ ...props,
+ },
+ data() {
+ return data;
+ },
+ }),
+ );
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('survey banner', () => {
+ it('shows when the banner is visible', () => {
+ createComponent({}, { bannerVisible: true });
+
+ expect(wrapper.find(GlBanner).exists()).toBe(true);
+ });
+
+ it('hide when close is called', async () => {
+ createComponent({}, { bannerVisible: true });
+
+ wrapper.findByTestId('close-icon').trigger('click');
+
+ await nextTick();
+
+ expect(wrapper.find(GlBanner).exists()).toBe(false);
+ });
+ });
+
+ describe('Unavailable structure', () => {
+ it.each`
+ licensePlan | visible
+ ${'free'} | ${true}
+ ${'premium'} | ${true}
+ ${'ultimate'} | ${false}
+ `('visibility is $visible when plan is $licensePlan', ({ licensePlan, visible }) => {
+ createComponent({ licensePlan });
+
+ expect(wrapper.findByTestId('unavailable-structure').exists()).toBe(visible);
+ });
+ });
+});
diff --git a/spec/frontend/work_items_hierarchy/components/hierarchy_spec.js b/spec/frontend/work_items_hierarchy/components/hierarchy_spec.js
new file mode 100644
index 00000000000..74774e38d6b
--- /dev/null
+++ b/spec/frontend/work_items_hierarchy/components/hierarchy_spec.js
@@ -0,0 +1,118 @@
+import { createLocalVue, mount } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import { GlBadge } from '@gitlab/ui';
+import Hierarchy from '~/work_items_hierarchy/components/hierarchy.vue';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import RESPONSE from '~/work_items_hierarchy/static_response';
+import { workItemTypes } from '~/work_items_hierarchy/constants';
+
+const localVue = createLocalVue();
+localVue.use(VueApollo);
+
+describe('WorkItemsHierarchy Hierarchy', () => {
+ let wrapper;
+
+ const workItemsFromResponse = (response) => {
+ return response.reduce(
+ (itemTypes, item) => {
+ const key = item.available ? 'available' : 'unavailable';
+ itemTypes[key].push({
+ ...item,
+ ...workItemTypes[item.type],
+ nestedTypes: item.nestedTypes
+ ? item.nestedTypes.map((type) => workItemTypes[type])
+ : null,
+ });
+ return itemTypes;
+ },
+ { available: [], unavailable: [] },
+ );
+ };
+
+ const createComponent = (props = {}) => {
+ wrapper = extendedWrapper(
+ mount(Hierarchy, {
+ localVue,
+ propsData: {
+ workItemTypes: props.workItemTypes,
+ ...props,
+ },
+ }),
+ );
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('available structure', () => {
+ let items = [];
+
+ beforeEach(() => {
+ items = workItemsFromResponse(RESPONSE.ultimate).available;
+ createComponent({ workItemTypes: items });
+ });
+
+ it('renders all work items', () => {
+ expect(wrapper.findAllByTestId('work-item-wrapper')).toHaveLength(items.length);
+ });
+
+ it('does not render badges', () => {
+ expect(wrapper.find(GlBadge).exists()).toBe(false);
+ });
+ });
+
+ describe('unavailable structure', () => {
+ let items = [];
+
+ beforeEach(() => {
+ items = workItemsFromResponse(RESPONSE.premium).unavailable;
+ createComponent({ workItemTypes: items });
+ });
+
+ it('renders all work items', () => {
+ expect(wrapper.findAllByTestId('work-item-wrapper')).toHaveLength(items.length);
+ });
+
+ it('renders license badges for all work items', () => {
+ expect(wrapper.findAll(GlBadge)).toHaveLength(items.length);
+ });
+
+ it('does not render svg icon for linking', () => {
+ expect(wrapper.findByTestId('hierarchy-rounded-arrow-tail').exists()).toBe(false);
+ expect(wrapper.findByTestId('level-up-icon').exists()).toBe(false);
+ });
+ });
+
+ describe('nested work items', () => {
+ describe.each`
+ licensePlan | arrowTailVisible | levelUpIconVisible | arrowDownIconVisible
+ ${'ultimate'} | ${true} | ${true} | ${true}
+ ${'premium'} | ${false} | ${false} | ${true}
+ ${'free'} | ${false} | ${false} | ${false}
+ `(
+ 'when $licensePlan license',
+ ({ licensePlan, arrowTailVisible, levelUpIconVisible, arrowDownIconVisible }) => {
+ let items = [];
+ beforeEach(() => {
+ items = workItemsFromResponse(RESPONSE[licensePlan]).available;
+ createComponent({ workItemTypes: items });
+ });
+
+ it(`${arrowTailVisible ? 'render' : 'does not render'} arrow tail svg`, () => {
+ expect(wrapper.findByTestId('hierarchy-rounded-arrow-tail').exists()).toBe(
+ arrowTailVisible,
+ );
+ });
+
+ it(`${levelUpIconVisible ? 'render' : 'does not render'} arrow tail svg`, () => {
+ expect(wrapper.findByTestId('level-up-icon').exists()).toBe(levelUpIconVisible);
+ });
+
+ it(`${arrowDownIconVisible ? 'render' : 'does not render'} arrow tail svg`, () => {
+ expect(wrapper.findByTestId('arrow-down-icon').exists()).toBe(arrowDownIconVisible);
+ });
+ },
+ );
+ });
+});
diff --git a/spec/frontend/work_items_hierarchy/hierarchy_util_spec.js b/spec/frontend/work_items_hierarchy/hierarchy_util_spec.js
new file mode 100644
index 00000000000..9042fa27d16
--- /dev/null
+++ b/spec/frontend/work_items_hierarchy/hierarchy_util_spec.js
@@ -0,0 +1,16 @@
+import { inferLicensePlan } from '~/work_items_hierarchy/hierarchy_util';
+import { LICENSE_PLAN } from '~/work_items_hierarchy/constants';
+
+describe('inferLicensePlan', () => {
+ it.each`
+ epics | subEpics | licensePlan
+ ${true} | ${true} | ${LICENSE_PLAN.ULTIMATE}
+ ${true} | ${false} | ${LICENSE_PLAN.PREMIUM}
+ ${false} | ${false} | ${LICENSE_PLAN.FREE}
+ `(
+ 'returns $licensePlan when epic is $epics and sub-epic is $subEpics',
+ ({ epics, subEpics, licensePlan }) => {
+ expect(inferLicensePlan({ hasEpics: epics, hasSubEpics: subEpics })).toBe(licensePlan);
+ },
+ );
+});
diff --git a/spec/frontend/zen_mode_spec.js b/spec/frontend/zen_mode_spec.js
index 13f221fd9d9..44684619fae 100644
--- a/spec/frontend/zen_mode_spec.js
+++ b/spec/frontend/zen_mode_spec.js
@@ -45,6 +45,8 @@ describe('ZenMode', () => {
// Set this manually because we can't actually scroll the window
zen.scroll_position = 456;
+
+ gon.features = { markdownContinueLists: true };
});
describe('enabling dropzone', () => {