summaryrefslogtreecommitdiff
path: root/spec/frontend
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-03-20 15:19:03 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-03-20 15:19:03 +0000
commit14bd84b61276ef29b97d23642d698de769bacfd2 (patch)
treef9eba90140c1bd874211dea17750a0d422c04080 /spec/frontend
parent891c388697b2db0d8ee0c8358a9bdbf6dc56d581 (diff)
downloadgitlab-ce-14bd84b61276ef29b97d23642d698de769bacfd2.tar.gz
Add latest changes from gitlab-org/gitlab@15-10-stable-eev15.10.0-rc42
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/.eslintrc.yml1
-rw-r--r--spec/frontend/__helpers__/create_mock_source_editor_extension.js12
-rw-r--r--spec/frontend/__helpers__/experimentation_helper.js7
-rw-r--r--spec/frontend/__helpers__/gon_helper.js5
-rw-r--r--spec/frontend/__helpers__/keep_alive_component_helper_spec.js4
-rw-r--r--spec/frontend/__helpers__/shared_test_setup.js16
-rw-r--r--spec/frontend/__helpers__/vue_mock_directive.js32
-rw-r--r--spec/frontend/__helpers__/vuex_action_helper.js2
-rw-r--r--spec/frontend/__helpers__/vuex_action_helper_spec.js14
-rw-r--r--spec/frontend/__mocks__/@gitlab/ui.js18
-rw-r--r--spec/frontend/__mocks__/lodash/debounce.js19
-rw-r--r--spec/frontend/__mocks__/lodash/throttle.js2
-rw-r--r--spec/frontend/abuse_reports/components/abuse_category_selector_spec.js4
-rw-r--r--spec/frontend/access_tokens/components/expires_at_field_spec.js4
-rw-r--r--spec/frontend/access_tokens/components/new_access_token_app_spec.js5
-rw-r--r--spec/frontend/access_tokens/components/token_spec.js4
-rw-r--r--spec/frontend/access_tokens/components/tokens_app_spec.js4
-rw-r--r--spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js4
-rw-r--r--spec/frontend/add_context_commits_modal/components/review_tab_container_spec.js4
-rw-r--r--spec/frontend/add_context_commits_modal/store/actions_spec.js2
-rw-r--r--spec/frontend/admin/abuse_reports/components/abuse_report_row_spec.js43
-rw-r--r--spec/frontend/admin/abuse_reports/components/abuse_reports_filtered_search_bar_spec.js214
-rw-r--r--spec/frontend/admin/abuse_reports/components/app_spec.js104
-rw-r--r--spec/frontend/admin/abuse_reports/mock_data.js14
-rw-r--r--spec/frontend/admin/abuse_reports/utils_spec.js13
-rw-r--r--spec/frontend/admin/analytics/devops_score/components/devops_score_callout_spec.js4
-rw-r--r--spec/frontend/admin/application_settings/inactive_project_deletion/components/form_spec.js4
-rw-r--r--spec/frontend/admin/application_settings/network_outbound_spec.js70
-rw-r--r--spec/frontend/admin/applications/components/delete_application_spec.js1
-rw-r--r--spec/frontend/admin/background_migrations/components/database_listbox_spec.js4
-rw-r--r--spec/frontend/admin/broadcast_messages/components/base_spec.js5
-rw-r--r--spec/frontend/admin/broadcast_messages/components/message_form_spec.js4
-rw-r--r--spec/frontend/admin/broadcast_messages/components/messages_table_spec.js4
-rw-r--r--spec/frontend/admin/deploy_keys/components/table_spec.js10
-rw-r--r--spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js4
-rw-r--r--spec/frontend/admin/signup_restrictions/components/signup_form_spec.js2
-rw-r--r--spec/frontend/admin/statistics_panel/components/app_spec.js4
-rw-r--r--spec/frontend/admin/topics/components/remove_avatar_spec.js6
-rw-r--r--spec/frontend/admin/topics/components/topic_select_spec.js1
-rw-r--r--spec/frontend/admin/users/components/actions/actions_spec.js5
-rw-r--r--spec/frontend/admin/users/components/app_spec.js5
-rw-r--r--spec/frontend/admin/users/components/modals/delete_user_modal_spec.js5
-rw-r--r--spec/frontend/admin/users/components/user_actions_spec.js7
-rw-r--r--spec/frontend/admin/users/components/user_avatar_spec.js7
-rw-r--r--spec/frontend/admin/users/components/user_date_spec.js5
-rw-r--r--spec/frontend/admin/users/components/users_table_spec.js11
-rw-r--r--spec/frontend/admin/users/index_spec.js4
-rw-r--r--spec/frontend/airflow/dags/components/dags_spec.js115
-rw-r--r--spec/frontend/airflow/dags/components/mock_data.js67
-rw-r--r--spec/frontend/alert_management/components/alert_management_table_spec.js2
-rw-r--r--spec/frontend/alert_spec.js (renamed from spec/frontend/flash_spec.js)2
-rw-r--r--spec/frontend/alerts_settings/components/__snapshots__/alerts_form_spec.js.snap33
-rw-r--r--spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js12
-rw-r--r--spec/frontend/analytics/components/activity_chart_spec.js5
-rw-r--r--spec/frontend/analytics/cycle_analytics/base_spec.js33
-rw-r--r--spec/frontend/analytics/cycle_analytics/filter_bar_spec.js1
-rw-r--r--spec/frontend/analytics/cycle_analytics/formatted_stage_count_spec.js4
-rw-r--r--spec/frontend/analytics/cycle_analytics/mock_data.js2
-rw-r--r--spec/frontend/analytics/cycle_analytics/path_navigation_spec.js2
-rw-r--r--spec/frontend/analytics/cycle_analytics/stage_table_spec.js6
-rw-r--r--spec/frontend/analytics/cycle_analytics/store/actions_spec.js31
-rw-r--r--spec/frontend/analytics/cycle_analytics/store/mutations_spec.js13
-rw-r--r--spec/frontend/analytics/cycle_analytics/total_time_spec.js4
-rw-r--r--spec/frontend/analytics/cycle_analytics/utils_spec.js23
-rw-r--r--spec/frontend/analytics/cycle_analytics/value_stream_filters_spec.js5
-rw-r--r--spec/frontend/analytics/cycle_analytics/value_stream_metrics_spec.js29
-rw-r--r--spec/frontend/analytics/devops_reports/components/service_ping_disabled_spec.js4
-rw-r--r--spec/frontend/analytics/shared/components/daterange_spec.js15
-rw-r--r--spec/frontend/analytics/shared/components/metric_popover_spec.js4
-rw-r--r--spec/frontend/analytics/shared/components/metric_tile_spec.js4
-rw-r--r--spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js4
-rw-r--r--spec/frontend/analytics/shared/utils_spec.js28
-rw-r--r--spec/frontend/analytics/usage_trends/components/app_spec.js5
-rw-r--r--spec/frontend/analytics/usage_trends/components/usage_counts_spec.js4
-rw-r--r--spec/frontend/analytics/usage_trends/components/usage_trends_count_chart_spec.js5
-rw-r--r--spec/frontend/analytics/usage_trends/components/users_chart_spec.js5
-rw-r--r--spec/frontend/api/alert_management_alerts_api_spec.js3
-rw-r--r--spec/frontend/api/groups_api_spec.js13
-rw-r--r--spec/frontend/api/packages_api_spec.js12
-rw-r--r--spec/frontend/api/projects_api_spec.js3
-rw-r--r--spec/frontend/api/tags_api_spec.js3
-rw-r--r--spec/frontend/api/user_api_spec.js3
-rw-r--r--spec/frontend/api_spec.js25
-rw-r--r--spec/frontend/approvals/mock_data.js10
-rw-r--r--spec/frontend/artifacts/components/artifact_row_spec.js39
-rw-r--r--spec/frontend/artifacts/components/artifacts_bulk_delete_spec.js96
-rw-r--r--spec/frontend/artifacts/components/artifacts_table_row_details_spec.js32
-rw-r--r--spec/frontend/artifacts/components/feedback_banner_spec.js4
-rw-r--r--spec/frontend/artifacts/components/job_artifacts_table_spec.js206
-rw-r--r--spec/frontend/artifacts/components/job_checkbox_spec.js71
-rw-r--r--spec/frontend/artifacts_settings/components/keep_latest_artifact_checkbox_spec.js2
-rw-r--r--spec/frontend/authentication/u2f/authenticate_spec.js104
-rw-r--r--spec/frontend/authentication/u2f/mock_u2f_device.js23
-rw-r--r--spec/frontend/authentication/u2f/register_spec.js84
-rw-r--r--spec/frontend/authentication/u2f/util_spec.js61
-rw-r--r--spec/frontend/authentication/webauthn/components/registration_spec.js255
-rw-r--r--spec/frontend/authentication/webauthn/error_spec.js13
-rw-r--r--spec/frontend/authentication/webauthn/util_spec.js31
-rw-r--r--spec/frontend/awards_handler_spec.js5
-rw-r--r--spec/frontend/badges/components/badge_form_spec.js1
-rw-r--r--spec/frontend/badges/components/badge_list_row_spec.js1
-rw-r--r--spec/frontend/badges/components/badge_list_spec.js4
-rw-r--r--spec/frontend/badges/components/badge_settings_spec.js4
-rw-r--r--spec/frontend/badges/components/badge_spec.js4
-rw-r--r--spec/frontend/batch_comments/components/diff_file_drafts_spec.js4
-rw-r--r--spec/frontend/batch_comments/components/draft_note_spec.js4
-rw-r--r--spec/frontend/batch_comments/components/drafts_count_spec.js4
-rw-r--r--spec/frontend/batch_comments/components/preview_dropdown_spec.js19
-rw-r--r--spec/frontend/batch_comments/components/preview_item_spec.js4
-rw-r--r--spec/frontend/batch_comments/components/review_bar_spec.js4
-rw-r--r--spec/frontend/batch_comments/components/submit_dropdown_spec.js1
-rw-r--r--spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js6
-rw-r--r--spec/frontend/batch_comments/stores/modules/batch_comments/mutations_spec.js10
-rw-r--r--spec/frontend/behaviors/components/diagram_performance_warning_spec.js4
-rw-r--r--spec/frontend/behaviors/components/json_table_spec.js4
-rw-r--r--spec/frontend/behaviors/copy_to_clipboard_spec.js2
-rw-r--r--spec/frontend/behaviors/markdown/highlight_current_user_spec.js10
-rw-r--r--spec/frontend/behaviors/markdown/render_observability_spec.js55
-rw-r--r--spec/frontend/blame/blame_redirect_spec.js4
-rw-r--r--spec/frontend/blame/streaming/index_spec.js110
-rw-r--r--spec/frontend/blob/components/blob_content_error_spec.js4
-rw-r--r--spec/frontend/blob/components/blob_content_spec.js4
-rw-r--r--spec/frontend/blob/components/blob_edit_header_spec.js4
-rw-r--r--spec/frontend/blob/components/blob_header_default_actions_spec.js4
-rw-r--r--spec/frontend/blob/components/blob_header_filepath_spec.js4
-rw-r--r--spec/frontend/blob/components/blob_header_spec.js170
-rw-r--r--spec/frontend/blob/components/blob_header_viewer_switcher_spec.js53
-rw-r--r--spec/frontend/blob/components/table_contents_spec.js1
-rw-r--r--spec/frontend/blob/csv/csv_viewer_spec.js4
-rw-r--r--spec/frontend/blob/notebook/notebook_viever_spec.js2
-rw-r--r--spec/frontend/blob/pdf/pdf_viewer_spec.js5
-rw-r--r--spec/frontend/blob/pipeline_tour_success_modal_spec.js1
-rw-r--r--spec/frontend/blob/suggest_gitlab_ci_yml/components/popover_spec.js5
-rw-r--r--spec/frontend/blob_edit/blob_bundle_spec.js4
-rw-r--r--spec/frontend/blob_edit/edit_blob_spec.js2
-rw-r--r--spec/frontend/boards/board_card_inner_spec.js8
-rw-r--r--spec/frontend/boards/board_list_helper.js1
-rw-r--r--spec/frontend/boards/board_list_spec.js4
-rw-r--r--spec/frontend/boards/components/board_add_new_column_form_spec.js124
-rw-r--r--spec/frontend/boards/components/board_add_new_column_spec.js61
-rw-r--r--spec/frontend/boards/components/board_add_new_column_trigger_spec.js6
-rw-r--r--spec/frontend/boards/components/board_app_spec.js3
-rw-r--r--spec/frontend/boards/components/board_card_spec.js2
-rw-r--r--spec/frontend/boards/components/board_column_spec.js6
-rw-r--r--spec/frontend/boards/components/board_configuration_options_spec.js4
-rw-r--r--spec/frontend/boards/components/board_content_sidebar_spec.js4
-rw-r--r--spec/frontend/boards/components/board_content_spec.js15
-rw-r--r--spec/frontend/boards/components/board_filtered_search_spec.js28
-rw-r--r--spec/frontend/boards/components/board_form_spec.js77
-rw-r--r--spec/frontend/boards/components/board_list_header_spec.js108
-rw-r--r--spec/frontend/boards/components/board_new_issue_spec.js4
-rw-r--r--spec/frontend/boards/components/board_new_item_spec.js4
-rw-r--r--spec/frontend/boards/components/board_settings_sidebar_spec.js4
-rw-r--r--spec/frontend/boards/components/board_top_bar_spec.js19
-rw-r--r--spec/frontend/boards/components/boards_selector_spec.js11
-rw-r--r--spec/frontend/boards/components/config_toggle_spec.js4
-rw-r--r--spec/frontend/boards/components/issue_board_filtered_search_spec.js16
-rw-r--r--spec/frontend/boards/components/issue_due_date_spec.js4
-rw-r--r--spec/frontend/boards/components/issue_time_estimate_spec.js4
-rw-r--r--spec/frontend/boards/components/item_count_spec.js8
-rw-r--r--spec/frontend/boards/components/sidebar/board_editable_item_spec.js5
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js5
-rw-r--r--spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js2
-rw-r--r--spec/frontend/boards/components/toggle_focus_spec.js6
-rw-r--r--spec/frontend/boards/mock_data.js4
-rw-r--r--spec/frontend/boards/project_select_spec.js5
-rw-r--r--spec/frontend/boards/stores/actions_spec.js20
-rw-r--r--spec/frontend/boards/stores/getters_spec.js8
-rw-r--r--spec/frontend/branches/components/delete_branch_button_spec.js4
-rw-r--r--spec/frontend/branches/components/delete_branch_modal_spec.js4
-rw-r--r--spec/frontend/branches/components/delete_merged_branches_spec.js6
-rw-r--r--spec/frontend/branches/components/divergence_graph_spec.js4
-rw-r--r--spec/frontend/branches/components/graph_bar_spec.js4
-rw-r--r--spec/frontend/captcha/captcha_modal_spec.js68
-rw-r--r--spec/frontend/ci/ci_lint/components/ci_lint_spec.js1
-rw-r--r--spec/frontend/ci/ci_variable_list/components/ci_admin_variables_spec.js4
-rw-r--r--spec/frontend/ci/ci_variable_list/components/ci_environments_dropdown_spec.js4
-rw-r--r--spec/frontend/ci/ci_variable_list/components/ci_group_variables_spec.js4
-rw-r--r--spec/frontend/ci/ci_variable_list/components/ci_project_variables_spec.js4
-rw-r--r--spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js4
-rw-r--r--spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js24
-rw-r--r--spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js588
-rw-r--r--spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js189
-rw-r--r--spec/frontend/ci/pipeline_editor/components/code_snippet_alert/code_snippet_alert_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/commit/commit_form_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/commit/commit_section_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/drawer/cards/first_pipeline_card_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/drawer/cards/getting_started_card_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/drawer/cards/pipeline_config_reference_card_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/drawer/cards/visualize_and_lint_card_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/drawer/pipeline_editor_drawer_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/drawer/ui/demo_job_pill_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/editor/ci_config_merged_preview_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/editor/ci_editor_header_spec.js1
-rw-r--r--spec/frontend/ci/pipeline_editor/components/editor/text_editor_spec.js76
-rw-r--r--spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/file-tree/container_spec.js3
-rw-r--r--spec/frontend/ci/pipeline_editor/components/file-tree/file_item_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/header/pipeline_editor_header_spec.js5
-rw-r--r--spec/frontend/ci/pipeline_editor/components/header/pipeline_status_spec.js1
-rw-r--r--spec/frontend/ci/pipeline_editor/components/header/validation_segment_spec.js102
-rw-r--r--spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/image_item_spec.js39
-rw-r--r--spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/job_setup_item_spec.js61
-rw-r--r--spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer_spec.js112
-rw-r--r--spec/frontend/ci/pipeline_editor/components/lint/ci_lint_results_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/lint/ci_lint_warnings_spec.js5
-rw-r--r--spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js1
-rw-r--r--spec/frontend/ci/pipeline_editor/components/popovers/file_tree_popover_spec.js1
-rw-r--r--spec/frontend/ci/pipeline_editor/components/popovers/validate_pipeline_popover_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/popovers/walkthrough_popover_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/ui/confirm_unsaved_changes_dialog_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/ui/pipeline_editor_empty_state_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/components/validate/ci_validate_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_editor/mock_data.js87
-rw-r--r--spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js17
-rw-r--r--spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js1
-rw-r--r--spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js17
-rw-r--r--spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js6
-rw-r--r--spec/frontend/ci/pipeline_new/mock_data.js2
-rw-r--r--spec/frontend/ci/pipeline_schedules/components/delete_pipeline_schedule_modal_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_schedules/components/pipeline_schedules_spec.js6
-rw-r--r--spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_actions_spec.js20
-rw-r--r--spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_next_run_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_owner_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_target_spec.js4
-rw-r--r--spec/frontend/ci/pipeline_schedules/components/table/pipeline_schedules_table_spec.js7
-rw-r--r--spec/frontend/ci/pipeline_schedules/components/take_ownership_modal_legacy_spec.js14
-rw-r--r--spec/frontend/ci/pipeline_schedules/mock_data.js2
-rw-r--r--spec/frontend/ci/reports/codequality_report/components/codequality_issue_body_spec.js5
-rw-r--r--spec/frontend/ci/reports/components/grouped_issues_list_spec.js4
-rw-r--r--spec/frontend/ci/reports/components/issue_status_icon_spec.js5
-rw-r--r--spec/frontend/ci/reports/components/report_link_spec.js4
-rw-r--r--spec/frontend/ci/reports/components/report_section_spec.js4
-rw-r--r--spec/frontend/ci/reports/components/summary_row_spec.js5
-rw-r--r--spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js93
-rw-r--r--spec/frontend/ci/runner/admin_register_runner/admin_register_runner_app_spec.js122
-rw-r--r--spec/frontend/ci/runner/admin_runner_show/admin_runner_show_app_spec.js5
-rw-r--r--spec/frontend/ci/runner/admin_runners/admin_runners_app_spec.js5
-rw-r--r--spec/frontend/ci/runner/components/cells/runner_actions_cell_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/cells/runner_owner_cell_spec.js6
-rw-r--r--spec/frontend/ci/runner/components/cells/runner_status_cell_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/cells/runner_summary_cell_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/cells/runner_summary_field_spec.js6
-rw-r--r--spec/frontend/ci/runner/components/registration/__snapshots__/utils_spec.js.snap204
-rw-r--r--spec/frontend/ci/runner/components/registration/cli_command_spec.js39
-rw-r--r--spec/frontend/ci/runner/components/registration/platforms_drawer_spec.js108
-rw-r--r--spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/registration/registration_instructions_spec.js293
-rw-r--r--spec/frontend/ci/runner/components/registration/registration_token_reset_dropdown_item_spec.js10
-rw-r--r--spec/frontend/ci/runner/components/registration/registration_token_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/registration/utils_spec.js118
-rw-r--r--spec/frontend/ci/runner/components/runner_assigned_item_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_bulk_delete_spec.js6
-rw-r--r--spec/frontend/ci/runner/components/runner_create_form_spec.js170
-rw-r--r--spec/frontend/ci/runner/components/runner_delete_button_spec.js12
-rw-r--r--spec/frontend/ci/runner/components/runner_details_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_edit_button_spec.js6
-rw-r--r--spec/frontend/ci/runner/components/runner_filtered_search_bar_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_groups_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_header_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_jobs_spec.js5
-rw-r--r--spec/frontend/ci/runner/components/runner_jobs_table_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_list_empty_state_spec.js14
-rw-r--r--spec/frontend/ci/runner/components/runner_list_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_membership_toggle_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_pagination_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_pause_button_spec.js10
-rw-r--r--spec/frontend/ci/runner/components/runner_paused_badge_spec.js6
-rw-r--r--spec/frontend/ci/runner/components/runner_projects_spec.js5
-rw-r--r--spec/frontend/ci/runner/components/runner_status_badge_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_tag_spec.js8
-rw-r--r--spec/frontend/ci/runner/components/runner_tags_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_type_badge_spec.js6
-rw-r--r--spec/frontend/ci/runner/components/runner_type_tabs_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/runner_update_form_spec.js8
-rw-r--r--spec/frontend/ci/runner/components/search_tokens/tag_token_spec.js5
-rw-r--r--spec/frontend/ci/runner/components/stat/runner_single_stat_spec.js4
-rw-r--r--spec/frontend/ci/runner/components/stat/runner_stats_spec.js4
-rw-r--r--spec/frontend/ci/runner/group_runner_show/group_runner_show_app_spec.js5
-rw-r--r--spec/frontend/ci/runner/group_runners/group_runners_app_spec.js5
-rw-r--r--spec/frontend/ci/runner/local_storage_alert/show_alert_from_local_storage_spec.js4
-rw-r--r--spec/frontend/ci/runner/mock_data.js8
-rw-r--r--spec/frontend/ci/runner/runner_edit/runner_edit_app_spec.js5
-rw-r--r--spec/frontend/ci_secure_files/components/metadata/__snapshots__/modal_spec.js.snap4
-rw-r--r--spec/frontend/ci_secure_files/components/metadata/button_spec.js4
-rw-r--r--spec/frontend/ci_secure_files/components/metadata/modal_spec.js1
-rw-r--r--spec/frontend/ci_secure_files/components/secure_files_list_spec.js13
-rw-r--r--spec/frontend/clusters/agents/components/activity_events_list_spec.js4
-rw-r--r--spec/frontend/clusters/agents/components/activity_history_item_spec.js4
-rw-r--r--spec/frontend/clusters/agents/components/agent_integration_status_row_spec.js4
-rw-r--r--spec/frontend/clusters/agents/components/create_token_button_spec.js6
-rw-r--r--spec/frontend/clusters/agents/components/create_token_modal_spec.js1
-rw-r--r--spec/frontend/clusters/agents/components/integration_status_spec.js4
-rw-r--r--spec/frontend/clusters/agents/components/revoke_token_button_spec.js3
-rw-r--r--spec/frontend/clusters/agents/components/show_spec.js4
-rw-r--r--spec/frontend/clusters/agents/components/token_table_spec.js4
-rw-r--r--spec/frontend/clusters/components/__snapshots__/new_cluster_spec.js.snap2
-rw-r--r--spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap209
-rw-r--r--spec/frontend/clusters/components/new_cluster_spec.js4
-rw-r--r--spec/frontend/clusters/components/remove_cluster_confirmation_spec.js22
-rw-r--r--spec/frontend/clusters/forms/components/integration_form_spec.js31
-rw-r--r--spec/frontend/clusters_list/components/agent_token_spec.js4
-rw-r--r--spec/frontend/clusters_list/components/agents_spec.js2
-rw-r--r--spec/frontend/clusters_list/components/ancestor_notice_spec.js4
-rw-r--r--spec/frontend/clusters_list/components/clusters_actions_spec.js5
-rw-r--r--spec/frontend/clusters_list/components/clusters_empty_state_spec.js4
-rw-r--r--spec/frontend/clusters_list/components/clusters_main_view_spec.js4
-rw-r--r--spec/frontend/clusters_list/components/clusters_spec.js5
-rw-r--r--spec/frontend/clusters_list/components/clusters_view_all_spec.js4
-rw-r--r--spec/frontend/clusters_list/components/delete_agent_button_spec.js5
-rw-r--r--spec/frontend/clusters_list/components/install_agent_modal_spec.js1
-rw-r--r--spec/frontend/clusters_list/components/node_error_help_text_spec.js4
-rw-r--r--spec/frontend/clusters_list/store/actions_spec.js6
-rw-r--r--spec/frontend/code_navigation/components/app_spec.js4
-rw-r--r--spec/frontend/code_navigation/components/popover_spec.js4
-rw-r--r--spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js8
-rw-r--r--spec/frontend/commit/commit_pipeline_status_component_spec.js11
-rw-r--r--spec/frontend/commit/components/commit_box_pipeline_status_spec.js8
-rw-r--r--spec/frontend/commit/components/signature_badge_spec.js134
-rw-r--r--spec/frontend/commit/components/x509_certificate_details_spec.js36
-rw-r--r--spec/frontend/commit/mock_data.js31
-rw-r--r--spec/frontend/commit/pipelines/pipelines_table_spec.js118
-rw-r--r--spec/frontend/confidential_merge_request/components/project_form_group_spec.js1
-rw-r--r--spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap2
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/bubble_menu_spec.js4
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/code_block_bubble_menu_spec.js4
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/formatting_bubble_menu_spec.js4
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js4
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/media_bubble_menu_spec.js97
-rw-r--r--spec/frontend/content_editor/components/content_editor_alert_spec.js4
-rw-r--r--spec/frontend/content_editor/components/content_editor_spec.js68
-rw-r--r--spec/frontend/content_editor/components/editor_state_observer_spec.js4
-rw-r--r--spec/frontend/content_editor/components/formatting_toolbar_spec.js14
-rw-r--r--spec/frontend/content_editor/components/loading_indicator_spec.js4
-rw-r--r--spec/frontend/content_editor/components/toolbar_button_spec.js4
-rw-r--r--spec/frontend/content_editor/components/toolbar_image_button_spec.js1
-rw-r--r--spec/frontend/content_editor/components/toolbar_link_button_spec.js1
-rw-r--r--spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js40
-rw-r--r--spec/frontend/content_editor/components/toolbar_table_button_spec.js1
-rw-r--r--spec/frontend/content_editor/components/toolbar_text_style_dropdown_spec.js4
-rw-r--r--spec/frontend/content_editor/components/wrappers/code_block_spec.js4
-rw-r--r--spec/frontend/content_editor/components/wrappers/details_spec.js4
-rw-r--r--spec/frontend/content_editor/components/wrappers/footnote_definition_spec.js4
-rw-r--r--spec/frontend/content_editor/components/wrappers/label_spec.js4
-rw-r--r--spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js8
-rw-r--r--spec/frontend/content_editor/components/wrappers/table_cell_body_spec.js4
-rw-r--r--spec/frontend/content_editor/components/wrappers/table_cell_header_spec.js4
-rw-r--r--spec/frontend/content_editor/components/wrappers/table_of_contents_spec.js4
-rw-r--r--spec/frontend/content_editor/extensions/attachment_spec.js21
-rw-r--r--spec/frontend/content_editor/extensions/drawio_diagram_spec.js103
-rw-r--r--spec/frontend/content_editor/extensions/paste_markdown_spec.js2
-rw-r--r--spec/frontend/content_editor/render_html_and_json_for_all_examples.js2
-rw-r--r--spec/frontend/content_editor/services/create_content_editor_spec.js16
-rw-r--r--spec/frontend/content_editor/services/gl_api_markdown_deserializer_spec.js22
-rw-r--r--spec/frontend/content_editor/services/markdown_serializer_spec.js9
-rw-r--r--spec/frontend/content_editor/test_constants.js6
-rw-r--r--spec/frontend/content_editor/test_utils.js2
-rw-r--r--spec/frontend/contributors/component/__snapshots__/contributors_spec.js.snap64
-rw-r--r--spec/frontend/contributors/component/contributors_spec.js6
-rw-r--r--spec/frontend/contributors/store/actions_spec.js6
-rw-r--r--spec/frontend/crm/contact_form_wrapper_spec.js1
-rw-r--r--spec/frontend/crm/contacts_root_spec.js1
-rw-r--r--spec/frontend/crm/crm_form_spec.js4
-rw-r--r--spec/frontend/crm/organization_form_wrapper_spec.js4
-rw-r--r--spec/frontend/crm/organizations_root_spec.js1
-rw-r--r--spec/frontend/custom_metrics/components/custom_metrics_form_fields_spec.js1
-rw-r--r--spec/frontend/custom_metrics/components/custom_metrics_form_spec.js4
-rw-r--r--spec/frontend/deploy_freeze/components/deploy_freeze_modal_spec.js5
-rw-r--r--spec/frontend/deploy_freeze/components/deploy_freeze_settings_spec.js5
-rw-r--r--spec/frontend/deploy_freeze/components/deploy_freeze_table_spec.js5
-rw-r--r--spec/frontend/deploy_freeze/store/actions_spec.js4
-rw-r--r--spec/frontend/deploy_keys/components/app_spec.js1
-rw-r--r--spec/frontend/deploy_keys/components/key_spec.js5
-rw-r--r--spec/frontend/deploy_keys/components/keys_panel_spec.js5
-rw-r--r--spec/frontend/deploy_tokens/components/new_deploy_token_spec.js51
-rw-r--r--spec/frontend/deploy_tokens/components/revoke_button_spec.js4
-rw-r--r--spec/frontend/design_management/components/delete_button_spec.js4
-rw-r--r--spec/frontend/design_management/components/design_notes/__snapshots__/design_note_spec.js.snap2
-rw-r--r--spec/frontend/design_management/components/design_notes/design_discussion_spec.js170
-rw-r--r--spec/frontend/design_management/components/design_notes/design_note_signed_out_spec.js4
-rw-r--r--spec/frontend/design_management/components/design_notes/design_note_spec.js56
-rw-r--r--spec/frontend/design_management/components/design_notes/design_reply_form_spec.js255
-rw-r--r--spec/frontend/design_management/components/design_notes/toggle_replies_widget_spec.js4
-rw-r--r--spec/frontend/design_management/components/design_presentation_spec.js6
-rw-r--r--spec/frontend/design_management/components/design_scaler_spec.js5
-rw-r--r--spec/frontend/design_management/components/design_sidebar_spec.js10
-rw-r--r--spec/frontend/design_management/components/design_todo_button_spec.js2
-rw-r--r--spec/frontend/design_management/components/image_spec.js4
-rw-r--r--spec/frontend/design_management/components/list/item_spec.js4
-rw-r--r--spec/frontend/design_management/components/toolbar/design_navigation_spec.js4
-rw-r--r--spec/frontend/design_management/components/toolbar/index_spec.js46
-rw-r--r--spec/frontend/design_management/components/upload/button_spec.js4
-rw-r--r--spec/frontend/design_management/components/upload/design_version_dropdown_spec.js4
-rw-r--r--spec/frontend/design_management/mock_data/apollo_mock.js112
-rw-r--r--spec/frontend/design_management/mock_data/project.js17
-rw-r--r--spec/frontend/design_management/pages/design/index_spec.js81
-rw-r--r--spec/frontend/design_management/pages/index_spec.js15
-rw-r--r--spec/frontend/design_management/router_spec.js6
-rw-r--r--spec/frontend/design_management/utils/cache_update_spec.js4
-rw-r--r--spec/frontend/diffs/components/app_spec.js79
-rw-r--r--spec/frontend/diffs/components/collapsed_files_warning_spec.js4
-rw-r--r--spec/frontend/diffs/components/commit_item_spec.js5
-rw-r--r--spec/frontend/diffs/components/compare_dropdown_layout_spec.js5
-rw-r--r--spec/frontend/diffs/components/compare_versions_spec.js5
-rw-r--r--spec/frontend/diffs/components/diff_code_quality_spec.js4
-rw-r--r--spec/frontend/diffs/components/diff_content_spec.js5
-rw-r--r--spec/frontend/diffs/components/diff_discussion_reply_spec.js4
-rw-r--r--spec/frontend/diffs/components/diff_discussions_spec.js4
-rw-r--r--spec/frontend/diffs/components/diff_file_header_spec.js2
-rw-r--r--spec/frontend/diffs/components/diff_file_row_spec.js4
-rw-r--r--spec/frontend/diffs/components/diff_file_spec.js13
-rw-r--r--spec/frontend/diffs/components/diff_gutter_avatars_spec.js4
-rw-r--r--spec/frontend/diffs/components/diff_row_spec.js4
-rw-r--r--spec/frontend/diffs/components/hidden_files_warning_spec.js4
-rw-r--r--spec/frontend/diffs/components/image_diff_overlay_spec.js4
-rw-r--r--spec/frontend/diffs/components/merge_conflict_warning_spec.js4
-rw-r--r--spec/frontend/diffs/components/no_changes_spec.js5
-rw-r--r--spec/frontend/diffs/components/settings_dropdown_spec.js1
-rw-r--r--spec/frontend/diffs/components/tree_list_spec.js83
-rw-r--r--spec/frontend/diffs/store/actions_spec.js49
-rw-r--r--spec/frontend/diffs/store/getters_spec.js25
-rw-r--r--spec/frontend/diffs/store/mutations_spec.js9
-rw-r--r--spec/frontend/diffs/store/utils_spec.js57
-rw-r--r--spec/frontend/diffs/utils/tree_worker_utils_spec.js30
-rw-r--r--spec/frontend/drawio/content_editor_facade_spec.js138
-rw-r--r--spec/frontend/drawio/drawio_editor_spec.js479
-rw-r--r--spec/frontend/drawio/markdown_field_editor_facade_spec.js148
-rw-r--r--spec/frontend/editor/components/helpers.js3
-rw-r--r--spec/frontend/editor/components/source_editor_toolbar_button_spec.js5
-rw-r--r--spec/frontend/editor/components/source_editor_toolbar_spec.js32
-rw-r--r--spec/frontend/editor/schema/ci/ci_schema_spec.js8
-rw-r--r--spec/frontend/editor/schema/ci/yaml_tests/negative_tests/services.yml38
-rw-r--r--spec/frontend/editor/schema/ci/yaml_tests/positive_tests/services.yml31
-rw-r--r--spec/frontend/editor/source_editor_ci_schema_ext_spec.js11
-rw-r--r--spec/frontend/editor/source_editor_extension_base_spec.js112
-rw-r--r--spec/frontend/editor/source_editor_markdown_ext_spec.js25
-rw-r--r--spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js4
-rw-r--r--spec/frontend/editor/source_editor_webide_ext_spec.js1
-rw-r--r--spec/frontend/editor/utils_spec.js22
-rw-r--r--spec/frontend/emoji/components/category_spec.js21
-rw-r--r--spec/frontend/emoji/components/emoji_group_spec.js4
-rw-r--r--spec/frontend/emoji/components/emoji_list_spec.js33
-rw-r--r--spec/frontend/environment.js8
-rw-r--r--spec/frontend/environments/canary_ingress_spec.js4
-rw-r--r--spec/frontend/environments/canary_update_modal_spec.js4
-rw-r--r--spec/frontend/environments/delete_environment_modal_spec.js6
-rw-r--r--spec/frontend/environments/edit_environment_spec.js5
-rw-r--r--spec/frontend/environments/empty_state_spec.js4
-rw-r--r--spec/frontend/environments/enable_review_app_modal_spec.js4
-rw-r--r--spec/frontend/environments/environment_actions_spec.js3
-rw-r--r--spec/frontend/environments/environment_folder_spec.js2
-rw-r--r--spec/frontend/environments/environment_form_spec.js20
-rw-r--r--spec/frontend/environments/environment_item_spec.js8
-rw-r--r--spec/frontend/environments/environment_pin_spec.js8
-rw-r--r--spec/frontend/environments/environment_table_spec.js4
-rw-r--r--spec/frontend/environments/environments_app_spec.js4
-rw-r--r--spec/frontend/environments/environments_detail_header_spec.js6
-rw-r--r--spec/frontend/environments/environments_folder_view_spec.js1
-rw-r--r--spec/frontend/environments/graphql/mock_data.js6
-rw-r--r--spec/frontend/environments/kubernetes_agent_info_spec.js126
-rw-r--r--spec/frontend/environments/kubernetes_overview_spec.js84
-rw-r--r--spec/frontend/environments/new_environment_item_spec.js83
-rw-r--r--spec/frontend/environments/new_environment_spec.js5
-rw-r--r--spec/frontend/environments/stop_stale_environments_modal_spec.js6
-rw-r--r--spec/frontend/error_tracking/components/error_details_spec.js6
-rw-r--r--spec/frontend/error_tracking/store/actions_spec.js4
-rw-r--r--spec/frontend/error_tracking/store/details/actions_spec.js6
-rw-r--r--spec/frontend/error_tracking/store/list/actions_spec.js6
-rw-r--r--spec/frontend/experimentation/components/gitlab_experiment_spec.js7
-rw-r--r--spec/frontend/experimentation/utils_spec.js3
-rw-r--r--spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js9
-rw-r--r--spec/frontend/feature_flags/components/edit_feature_flag_spec.js1
-rw-r--r--spec/frontend/feature_flags/components/empty_state_spec.js2
-rw-r--r--spec/frontend/feature_flags/components/environments_dropdown_spec.js1
-rw-r--r--spec/frontend/feature_flags/components/feature_flags_spec.js2
-rw-r--r--spec/frontend/feature_flags/components/form_spec.js4
-rw-r--r--spec/frontend/feature_flags/components/new_feature_flag_spec.js4
-rw-r--r--spec/frontend/feature_flags/components/strategies/flexible_rollout_spec.js2
-rw-r--r--spec/frontend/feature_flags/components/strategies/parameter_form_group_spec.js2
-rw-r--r--spec/frontend/feature_flags/components/strategies/percent_rollout_spec.js2
-rw-r--r--spec/frontend/feature_flags/components/strategies/users_with_id_spec.js2
-rw-r--r--spec/frontend/feature_flags/components/strategy_parameters_spec.js2
-rw-r--r--spec/frontend/feature_highlight/feature_highlight_helper_spec.js6
-rw-r--r--spec/frontend/feature_highlight/feature_highlight_popover_spec.js5
-rw-r--r--spec/frontend/filtered_search/components/recent_searches_dropdown_content_spec.js5
-rw-r--r--spec/frontend/filtered_search/dropdown_user_spec.js4
-rw-r--r--spec/frontend/filtered_search/filtered_search_manager_spec.js4
-rw-r--r--spec/frontend/filtered_search/visual_token_value_spec.js4
-rw-r--r--spec/frontend/fixtures/abuse_reports.rb2
-rw-r--r--spec/frontend/fixtures/api_deploy_keys.rb5
-rw-r--r--spec/frontend/fixtures/jobs.rb22
-rw-r--r--spec/frontend/fixtures/merge_requests.rb6
-rw-r--r--spec/frontend/fixtures/runner.rb34
-rw-r--r--spec/frontend/fixtures/saved_replies.rb28
-rw-r--r--spec/frontend/fixtures/startup_css.rb20
-rw-r--r--spec/frontend/fixtures/u2f.rb48
-rw-r--r--spec/frontend/fixtures/users.rb65
-rw-r--r--spec/frontend/fixtures/webauthn.rb1
-rw-r--r--spec/frontend/frequent_items/components/app_spec.js1
-rw-r--r--spec/frontend/frequent_items/components/frequent_items_list_item_spec.js2
-rw-r--r--spec/frontend/frequent_items/components/frequent_items_list_spec.js4
-rw-r--r--spec/frontend/gitlab_pages/new/pages/pages_pipeline_wizard_spec.js4
-rw-r--r--spec/frontend/gitlab_version_check/components/gitlab_version_check_badge_spec.js1
-rw-r--r--spec/frontend/google_cloud/components/google_cloud_menu_spec.js4
-rw-r--r--spec/frontend/google_cloud/components/incubation_banner_spec.js4
-rw-r--r--spec/frontend/google_cloud/components/revoke_oauth_spec.js4
-rw-r--r--spec/frontend/google_cloud/configuration/panel_spec.js4
-rw-r--r--spec/frontend/google_cloud/databases/cloudsql/create_instance_form_spec.js4
-rw-r--r--spec/frontend/google_cloud/databases/cloudsql/instance_table_spec.js4
-rw-r--r--spec/frontend/google_cloud/databases/panel_spec.js4
-rw-r--r--spec/frontend/google_cloud/databases/service_table_spec.js4
-rw-r--r--spec/frontend/google_cloud/deployments/panel_spec.js4
-rw-r--r--spec/frontend/google_cloud/deployments/service_table_spec.js4
-rw-r--r--spec/frontend/google_cloud/gcp_regions/form_spec.js4
-rw-r--r--spec/frontend/google_cloud/gcp_regions/list_spec.js4
-rw-r--r--spec/frontend/google_cloud/service_accounts/form_spec.js4
-rw-r--r--spec/frontend/google_cloud/service_accounts/list_spec.js4
-rw-r--r--spec/frontend/grafana_integration/components/grafana_integration_spec.js6
-rw-r--r--spec/frontend/group_settings/components/shared_runners_form_spec.js3
-rw-r--r--spec/frontend/groups/components/app_spec.js15
-rw-r--r--spec/frontend/groups/components/empty_states/subgroups_and_projects_empty_state_spec.js4
-rw-r--r--spec/frontend/groups/components/group_folder_spec.js4
-rw-r--r--spec/frontend/groups/components/group_item_spec.js4
-rw-r--r--spec/frontend/groups/components/group_name_and_path_spec.js4
-rw-r--r--spec/frontend/groups/components/groups_spec.js4
-rw-r--r--spec/frontend/groups/components/invite_members_banner_spec.js9
-rw-r--r--spec/frontend/groups/components/item_actions_spec.js5
-rw-r--r--spec/frontend/groups/components/new_top_level_group_alert_spec.js4
-rw-r--r--spec/frontend/groups/components/overview_tabs_spec.js1
-rw-r--r--spec/frontend/groups/components/transfer_group_form_spec.js4
-rw-r--r--spec/frontend/groups_projects/components/transfer_locations_spec.js4
-rw-r--r--spec/frontend/header_search/components/app_spec.js20
-rw-r--r--spec/frontend/header_search/components/header_search_autocomplete_items_spec.js11
-rw-r--r--spec/frontend/header_search/components/header_search_default_items_spec.js4
-rw-r--r--spec/frontend/header_search/components/header_search_scoped_items_spec.js7
-rw-r--r--spec/frontend/header_search/mock_data.js10
-rw-r--r--spec/frontend/header_search/store/actions_spec.js2
-rw-r--r--spec/frontend/header_search/store/getters_spec.js7
-rw-r--r--spec/frontend/helpers/startup_css_helper_spec.js7
-rw-r--r--spec/frontend/ide/components/activity_bar_spec.js4
-rw-r--r--spec/frontend/ide/components/branches/item_spec.js4
-rw-r--r--spec/frontend/ide/components/branches/search_list_spec.js5
-rw-r--r--spec/frontend/ide/components/cannot_push_code_alert_spec.js4
-rw-r--r--spec/frontend/ide/components/commit_sidebar/actions_spec.js4
-rw-r--r--spec/frontend/ide/components/commit_sidebar/editor_header_spec.js39
-rw-r--r--spec/frontend/ide/components/commit_sidebar/empty_state_spec.js4
-rw-r--r--spec/frontend/ide/components/commit_sidebar/form_spec.js6
-rw-r--r--spec/frontend/ide/components/commit_sidebar/list_item_spec.js4
-rw-r--r--spec/frontend/ide/components/commit_sidebar/list_spec.js4
-rw-r--r--spec/frontend/ide/components/commit_sidebar/message_field_spec.js4
-rw-r--r--spec/frontend/ide/components/commit_sidebar/new_merge_request_option_spec.js6
-rw-r--r--spec/frontend/ide/components/commit_sidebar/radio_group_spec.js6
-rw-r--r--spec/frontend/ide/components/commit_sidebar/success_message_spec.js4
-rw-r--r--spec/frontend/ide/components/error_message_spec.js5
-rw-r--r--spec/frontend/ide/components/file_row_extra_spec.js2
-rw-r--r--spec/frontend/ide/components/file_templates/bar_spec.js4
-rw-r--r--spec/frontend/ide/components/file_templates/dropdown_spec.js5
-rw-r--r--spec/frontend/ide/components/ide_file_row_spec.js5
-rw-r--r--spec/frontend/ide/components/ide_project_header_spec.js4
-rw-r--r--spec/frontend/ide/components/ide_review_spec.js4
-rw-r--r--spec/frontend/ide/components/ide_side_bar_spec.js5
-rw-r--r--spec/frontend/ide/components/ide_sidebar_nav_spec.js11
-rw-r--r--spec/frontend/ide/components/ide_spec.js2
-rw-r--r--spec/frontend/ide/components/ide_status_bar_spec.js4
-rw-r--r--spec/frontend/ide/components/ide_status_list_spec.js3
-rw-r--r--spec/frontend/ide/components/ide_status_mr_spec.js4
-rw-r--r--spec/frontend/ide/components/ide_tree_list_spec.js4
-rw-r--r--spec/frontend/ide/components/ide_tree_spec.js4
-rw-r--r--spec/frontend/ide/components/jobs/detail/description_spec.js4
-rw-r--r--spec/frontend/ide/components/jobs/detail/scroll_button_spec.js4
-rw-r--r--spec/frontend/ide/components/jobs/detail_spec.js4
-rw-r--r--spec/frontend/ide/components/jobs/item_spec.js4
-rw-r--r--spec/frontend/ide/components/jobs/stage_spec.js5
-rw-r--r--spec/frontend/ide/components/merge_requests/item_spec.js5
-rw-r--r--spec/frontend/ide/components/merge_requests/list_spec.js5
-rw-r--r--spec/frontend/ide/components/nav_dropdown_button_spec.js4
-rw-r--r--spec/frontend/ide/components/nav_dropdown_spec.js4
-rw-r--r--spec/frontend/ide/components/new_dropdown/button_spec.js4
-rw-r--r--spec/frontend/ide/components/new_dropdown/index_spec.js4
-rw-r--r--spec/frontend/ide/components/new_dropdown/modal_spec.js21
-rw-r--r--spec/frontend/ide/components/new_dropdown/upload_spec.js4
-rw-r--r--spec/frontend/ide/components/panes/collapsible_sidebar_spec.js5
-rw-r--r--spec/frontend/ide/components/panes/right_spec.js5
-rw-r--r--spec/frontend/ide/components/pipelines/empty_state_spec.js4
-rw-r--r--spec/frontend/ide/components/pipelines/list_spec.js5
-rw-r--r--spec/frontend/ide/components/repo_commit_section_spec.js5
-rw-r--r--spec/frontend/ide/components/repo_editor_spec.js2
-rw-r--r--spec/frontend/ide/components/repo_tab_spec.js5
-rw-r--r--spec/frontend/ide/components/repo_tabs_spec.js4
-rw-r--r--spec/frontend/ide/components/resizable_panel_spec.js5
-rw-r--r--spec/frontend/ide/components/shared/commit_message_field_spec.js4
-rw-r--r--spec/frontend/ide/components/shared/tokened_input_spec.js4
-rw-r--r--spec/frontend/ide/components/terminal/empty_state_spec.js4
-rw-r--r--spec/frontend/ide/components/terminal/terminal_spec.js4
-rw-r--r--spec/frontend/ide/components/terminal/view_spec.js4
-rw-r--r--spec/frontend/ide/components/terminal_sync/terminal_sync_status_safe_spec.js4
-rw-r--r--spec/frontend/ide/components/terminal_sync/terminal_sync_status_spec.js4
-rw-r--r--spec/frontend/ide/services/index_spec.js3
-rw-r--r--spec/frontend/ide/services/terminals_spec.js2
-rw-r--r--spec/frontend/ide/stores/actions/file_spec.js4
-rw-r--r--spec/frontend/ide/stores/actions/merge_request_spec.js8
-rw-r--r--spec/frontend/ide/stores/actions/project_spec.js6
-rw-r--r--spec/frontend/ide/stores/actions_spec.js8
-rw-r--r--spec/frontend/ide/stores/extend_spec.js5
-rw-r--r--spec/frontend/ide/stores/getters_spec.js7
-rw-r--r--spec/frontend/ide/stores/modules/commit/actions_spec.js76
-rw-r--r--spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js8
-rw-r--r--spec/frontend/ide/stores/modules/terminal/actions/session_status_spec.js6
-rw-r--r--spec/frontend/import_entities/components/group_dropdown_spec.js4
-rw-r--r--spec/frontend/import_entities/components/import_status_spec.js4
-rw-r--r--spec/frontend/import_entities/import_groups/components/import_actions_cell_spec.js43
-rw-r--r--spec/frontend/import_entities/import_groups/components/import_source_cell_spec.js4
-rw-r--r--spec/frontend/import_entities/import_groups/components/import_table_spec.js56
-rw-r--r--spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js5
-rw-r--r--spec/frontend/import_entities/import_groups/graphql/client_factory_spec.js2
-rw-r--r--spec/frontend/import_entities/import_groups/services/status_poller_spec.js6
-rw-r--r--spec/frontend/import_entities/import_projects/components/advanced_settings_spec.js4
-rw-r--r--spec/frontend/import_entities/import_projects/components/provider_repo_table_row_spec.js5
-rw-r--r--spec/frontend/import_entities/import_projects/store/actions_spec.js4
-rw-r--r--spec/frontend/incidents_settings/components/incidents_settings_service_spec.js6
-rw-r--r--spec/frontend/incidents_settings/components/pagerduty_form_spec.js4
-rw-r--r--spec/frontend/integrations/edit/components/active_checkbox_spec.js4
-rw-r--r--spec/frontend/integrations/edit/components/confirmation_modal_spec.js4
-rw-r--r--spec/frontend/integrations/edit/components/dynamic_field_spec.js4
-rw-r--r--spec/frontend/integrations/edit/components/integration_form_spec.js51
-rw-r--r--spec/frontend/integrations/edit/components/jira_issues_fields_spec.js4
-rw-r--r--spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js4
-rw-r--r--spec/frontend/integrations/edit/components/override_dropdown_spec.js4
-rw-r--r--spec/frontend/integrations/edit/components/sections/apple_app_store_spec.js57
-rw-r--r--spec/frontend/integrations/edit/components/sections/configuration_spec.js4
-rw-r--r--spec/frontend/integrations/edit/components/sections/connection_spec.js4
-rw-r--r--spec/frontend/integrations/edit/components/sections/google_play_spec.js54
-rw-r--r--spec/frontend/integrations/edit/components/sections/jira_issues_spec.js4
-rw-r--r--spec/frontend/integrations/edit/components/sections/jira_trigger_spec.js4
-rw-r--r--spec/frontend/integrations/edit/components/sections/trigger_spec.js4
-rw-r--r--spec/frontend/integrations/edit/components/trigger_field_spec.js4
-rw-r--r--spec/frontend/integrations/edit/components/trigger_fields_spec.js4
-rw-r--r--spec/frontend/integrations/edit/components/upload_dropzone_field_spec.js88
-rw-r--r--spec/frontend/integrations/index/components/integrations_list_spec.js4
-rw-r--r--spec/frontend/integrations/index/components/integrations_table_spec.js61
-rw-r--r--spec/frontend/integrations/overrides/components/integration_overrides_spec.js1
-rw-r--r--spec/frontend/integrations/overrides/components/integration_tabs_spec.js4
-rw-r--r--spec/frontend/invite_members/components/confetti_spec.js8
-rw-r--r--spec/frontend/invite_members/components/group_select_spec.js8
-rw-r--r--spec/frontend/invite_members/components/import_project_members_modal_spec.js1
-rw-r--r--spec/frontend/invite_members/components/import_project_members_trigger_spec.js4
-rw-r--r--spec/frontend/invite_members/components/invite_group_trigger_spec.js5
-rw-r--r--spec/frontend/invite_members/components/invite_groups_modal_spec.js17
-rw-r--r--spec/frontend/invite_members/components/invite_members_modal_spec.js30
-rw-r--r--spec/frontend/invite_members/components/invite_members_trigger_spec.js49
-rw-r--r--spec/frontend/invite_members/components/invite_modal_base_spec.js31
-rw-r--r--spec/frontend/invite_members/components/members_token_select_spec.js5
-rw-r--r--spec/frontend/invite_members/components/project_select_spec.js4
-rw-r--r--spec/frontend/invite_members/utils/trigger_successful_invite_alert_spec.js4
-rw-r--r--spec/frontend/issuable/components/csv_export_modal_spec.js18
-rw-r--r--spec/frontend/issuable/components/csv_import_export_buttons_spec.js6
-rw-r--r--spec/frontend/issuable/components/csv_import_modal_spec.js4
-rw-r--r--spec/frontend/issuable/components/issuable_by_email_spec.js2
-rw-r--r--spec/frontend/issuable/components/issuable_header_warnings_spec.js7
-rw-r--r--spec/frontend/issuable/components/issue_assignees_spec.js5
-rw-r--r--spec/frontend/issuable/components/issue_milestone_spec.js159
-rw-r--r--spec/frontend/issuable/components/related_issuable_item_spec.js4
-rw-r--r--spec/frontend/issuable/components/status_box_spec.js5
-rw-r--r--spec/frontend/issuable/popover/components/issue_popover_spec.js4
-rw-r--r--spec/frontend/issuable/popover/components/mr_popover_spec.js4
-rw-r--r--spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js243
-rw-r--r--spec/frontend/issuable/related_issues/components/related_issues_block_spec.js14
-rw-r--r--spec/frontend/issuable/related_issues/components/related_issues_root_spec.js5
-rw-r--r--spec/frontend/issues/create_merge_request_dropdown_spec.js8
-rw-r--r--spec/frontend/issues/dashboard/components/issues_dashboard_app_spec.js20
-rw-r--r--spec/frontend/issues/list/components/issue_card_time_info_spec.js4
-rw-r--r--spec/frontend/issues/list/components/issues_list_app_spec.js80
-rw-r--r--spec/frontend/issues/list/components/jira_issues_import_status_app_spec.js5
-rw-r--r--spec/frontend/issues/list/mock_data.js20
-rw-r--r--spec/frontend/issues/list/utils_spec.js5
-rw-r--r--spec/frontend/issues/new/components/title_suggestions_item_spec.js4
-rw-r--r--spec/frontend/issues/new/components/title_suggestions_spec.js123
-rw-r--r--spec/frontend/issues/new/components/type_popover_spec.js4
-rw-r--r--spec/frontend/issues/new/mock_data.js64
-rw-r--r--spec/frontend/issues/related_merge_requests/components/related_merge_requests_spec.js1
-rw-r--r--spec/frontend/issues/related_merge_requests/store/actions_spec.js4
-rw-r--r--spec/frontend/issues/show/components/app_spec.js579
-rw-r--r--spec/frontend/issues/show/components/delete_issue_modal_spec.js4
-rw-r--r--spec/frontend/issues/show/components/description_spec.js182
-rw-r--r--spec/frontend/issues/show/components/edit_actions_spec.js4
-rw-r--r--spec/frontend/issues/show/components/edited_spec.js4
-rw-r--r--spec/frontend/issues/show/components/fields/description_spec.js28
-rw-r--r--spec/frontend/issues/show/components/fields/description_template_spec.js4
-rw-r--r--spec/frontend/issues/show/components/fields/title_spec.js5
-rw-r--r--spec/frontend/issues/show/components/fields/type_spec.js8
-rw-r--r--spec/frontend/issues/show/components/form_spec.js4
-rw-r--r--spec/frontend/issues/show/components/header_actions_spec.js47
-rw-r--r--spec/frontend/issues/show/components/incidents/create_timeline_events_form_spec.js5
-rw-r--r--spec/frontend/issues/show/components/incidents/incident_tabs_spec.js55
-rw-r--r--spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js5
-rw-r--r--spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js8
-rw-r--r--spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js4
-rw-r--r--spec/frontend/issues/show/components/incidents/utils_spec.js4
-rw-r--r--spec/frontend/issues/show/components/locked_warning_spec.js5
-rw-r--r--spec/frontend/issues/show/components/task_list_item_actions_spec.js2
-rw-r--r--spec/frontend/issues/show/components/title_spec.js89
-rw-r--r--spec/frontend/issues/show/mock_data/mock_data.js57
-rw-r--r--spec/frontend/jira_connect/branches/components/new_branch_form_spec.js4
-rw-r--r--spec/frontend/jira_connect/branches/components/project_dropdown_spec.js4
-rw-r--r--spec/frontend/jira_connect/branches/components/source_branch_dropdown_spec.js4
-rw-r--r--spec/frontend/jira_connect/branches/pages/index_spec.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/api_spec.js3
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/add_namespace_button_spec.js6
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/add_namespace_modal_spec.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_spec.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/app_spec.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/browser_support_alert_spec.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/group_item_name_spec.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/sign_in_legacy_button_spec.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/user_link_spec.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com_spec.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form_spec.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_page_spec.js4
-rw-r--r--spec/frontend/jira_connect/subscriptions/pages/subscriptions_page_spec.js4
-rw-r--r--spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap6
-rw-r--r--spec/frontend/jira_import/components/jira_import_app_spec.js5
-rw-r--r--spec/frontend/jira_import/components/jira_import_form_spec.js1
-rw-r--r--spec/frontend/jira_import/components/jira_import_progress_spec.js5
-rw-r--r--spec/frontend/jira_import/components/jira_import_setup_spec.js5
-rw-r--r--spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js4
-rw-r--r--spec/frontend/jobs/components/filtered_search/tokens/job_status_token_spec.js4
-rw-r--r--spec/frontend/jobs/components/job/artifacts_block_spec.js5
-rw-r--r--spec/frontend/jobs/components/job/commit_block_spec.js4
-rw-r--r--spec/frontend/jobs/components/job/environments_block_spec.js5
-rw-r--r--spec/frontend/jobs/components/job/erased_block_spec.js4
-rw-r--r--spec/frontend/jobs/components/job/job_app_spec.js3
-rw-r--r--spec/frontend/jobs/components/job/job_container_item_spec.js5
-rw-r--r--spec/frontend/jobs/components/job/job_log_controllers_spec.js12
-rw-r--r--spec/frontend/jobs/components/job/job_retry_forward_deployment_modal_spec.js12
-rw-r--r--spec/frontend/jobs/components/job/jobs_container_spec.js4
-rw-r--r--spec/frontend/jobs/components/job/manual_variables_form_spec.js106
-rw-r--r--spec/frontend/jobs/components/job/mock_data.js27
-rw-r--r--spec/frontend/jobs/components/job/sidebar_detail_row_spec.js5
-rw-r--r--spec/frontend/jobs/components/job/sidebar_spec.js4
-rw-r--r--spec/frontend/jobs/components/job/stages_dropdown_spec.js4
-rw-r--r--spec/frontend/jobs/components/job/trigger_block_spec.js4
-rw-r--r--spec/frontend/jobs/components/job/unmet_prerequisites_block_spec.js4
-rw-r--r--spec/frontend/jobs/components/log/collapsible_section_spec.js4
-rw-r--r--spec/frontend/jobs/components/log/duration_badge_spec.js4
-rw-r--r--spec/frontend/jobs/components/log/line_header_spec.js4
-rw-r--r--spec/frontend/jobs/components/log/line_number_spec.js4
-rw-r--r--spec/frontend/jobs/components/log/log_spec.js15
-rw-r--r--spec/frontend/jobs/components/table/cells/duration_cell_spec.js4
-rw-r--r--spec/frontend/jobs/components/table/cells/job_cell_spec.js4
-rw-r--r--spec/frontend/jobs/components/table/cells/pipeline_cell_spec.js4
-rw-r--r--spec/frontend/jobs/components/table/graphql/cache_config_spec.js19
-rw-r--r--spec/frontend/jobs/components/table/job_table_app_spec.js53
-rw-r--r--spec/frontend/jobs/components/table/jobs_table_spec.js4
-rw-r--r--spec/frontend/jobs/components/table/jobs_table_tabs_spec.js4
-rw-r--r--spec/frontend/jobs/mixins/delayed_job_mixin_spec.js5
-rw-r--r--spec/frontend/jobs/mock_data.js2
-rw-r--r--spec/frontend/labels/components/delete_label_modal_spec.js4
-rw-r--r--spec/frontend/labels/components/promote_label_modal_spec.js1
-rw-r--r--spec/frontend/language_switcher/components/app_spec.js4
-rw-r--r--spec/frontend/lib/apollo/suppress_network_errors_during_navigation_link_spec.js5
-rw-r--r--spec/frontend/lib/dompurify_spec.js14
-rw-r--r--spec/frontend/lib/utils/axios_startup_calls_spec.js7
-rw-r--r--spec/frontend/lib/utils/common_utils_spec.js8
-rw-r--r--spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js1
-rw-r--r--spec/frontend/lib/utils/confirm_via_gl_modal/confirm_modal_spec.js4
-rw-r--r--spec/frontend/lib/utils/datetime/timeago_utility_spec.js10
-rw-r--r--spec/frontend/lib/utils/error_message_spec.js65
-rw-r--r--spec/frontend/lib/utils/file_upload_spec.js22
-rw-r--r--spec/frontend/lib/utils/number_utility_spec.js29
-rw-r--r--spec/frontend/lib/utils/ref_validator_spec.js79
-rw-r--r--spec/frontend/lib/utils/text_markdown_spec.js62
-rw-r--r--spec/frontend/lib/utils/text_utility_spec.js32
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js8
-rw-r--r--spec/frontend/lib/utils/vuex_module_mappers_spec.js4
-rw-r--r--spec/frontend/locale/sprintf_spec.js11
-rw-r--r--spec/frontend/members/components/action_buttons/access_request_action_buttons_spec.js4
-rw-r--r--spec/frontend/members/components/action_buttons/approve_access_request_button_spec.js6
-rw-r--r--spec/frontend/members/components/action_buttons/invite_action_buttons_spec.js4
-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.js6
-rw-r--r--spec/frontend/members/components/action_buttons/resend_invite_button_spec.js6
-rw-r--r--spec/frontend/members/components/action_dropdowns/leave_group_dropdown_item_spec.js6
-rw-r--r--spec/frontend/members/components/action_dropdowns/remove_member_dropdown_item_spec.js4
-rw-r--r--spec/frontend/members/components/action_dropdowns/user_action_dropdown_spec.js6
-rw-r--r--spec/frontend/members/components/app_spec.js1
-rw-r--r--spec/frontend/members/components/avatars/group_avatar_spec.js4
-rw-r--r--spec/frontend/members/components/avatars/invite_avatar_spec.js4
-rw-r--r--spec/frontend/members/components/avatars/user_avatar_spec.js4
-rw-r--r--spec/frontend/members/components/filter_sort/sort_dropdown_spec.js2
-rw-r--r--spec/frontend/members/components/members_tabs_spec.js4
-rw-r--r--spec/frontend/members/components/modals/leave_modal_spec.js4
-rw-r--r--spec/frontend/members/components/modals/remove_group_link_modal_spec.js5
-rw-r--r--spec/frontend/members/components/modals/remove_member_modal_spec.js4
-rw-r--r--spec/frontend/members/components/table/created_at_spec.js4
-rw-r--r--spec/frontend/members/components/table/expiration_datepicker_spec.js4
-rw-r--r--spec/frontend/members/components/table/member_action_buttons_spec.js4
-rw-r--r--spec/frontend/members/components/table/member_avatar_spec.js4
-rw-r--r--spec/frontend/members/components/table/member_source_spec.js6
-rw-r--r--spec/frontend/members/components/table/members_table_cell_spec.js5
-rw-r--r--spec/frontend/members/components/table/members_table_spec.js4
-rw-r--r--spec/frontend/members/components/table/role_dropdown_spec.js10
-rw-r--r--spec/frontend/members/index_spec.js3
-rw-r--r--spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js4
-rw-r--r--spec/frontend/merge_conflicts/store/actions_spec.js6
-rw-r--r--spec/frontend/merge_request_spec.js4
-rw-r--r--spec/frontend/merge_request_tabs_spec.js2
-rw-r--r--spec/frontend/merge_requests/components/compare_app_spec.js4
-rw-r--r--spec/frontend/merge_requests/components/compare_dropdown_spec.js1
-rw-r--r--spec/frontend/milestones/components/delete_milestone_modal_spec.js8
-rw-r--r--spec/frontend/milestones/components/milestone_combobox_spec.js5
-rw-r--r--spec/frontend/milestones/components/promote_milestone_modal_spec.js8
-rw-r--r--spec/frontend/ml/experiment_tracking/routes/candidates/show/__snapshots__/ml_candidates_show_spec.js.snap (renamed from spec/frontend/ml/experiment_tracking/components/__snapshots__/ml_candidate_spec.js.snap)21
-rw-r--r--spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js (renamed from spec/frontend/ml/experiment_tracking/components/ml_candidate_spec.js)8
-rw-r--r--spec/frontend/ml/experiment_tracking/routes/experiments/show/ml_experiments_show_spec.js (renamed from spec/frontend/ml/experiment_tracking/components/ml_experiment_spec.js)105
-rw-r--r--spec/frontend/ml/experiment_tracking/routes/experiments/show/mock_data.js52
-rw-r--r--spec/frontend/monitoring/components/charts/column_spec.js4
-rw-r--r--spec/frontend/monitoring/components/charts/gauge_spec.js5
-rw-r--r--spec/frontend/monitoring/components/charts/heatmap_spec.js4
-rw-r--r--spec/frontend/monitoring/components/charts/single_stat_spec.js4
-rw-r--r--spec/frontend/monitoring/components/charts/time_series_spec.js4
-rw-r--r--spec/frontend/monitoring/components/create_dashboard_modal_spec.js4
-rw-r--r--spec/frontend/monitoring/components/dashboard_actions_menu_spec.js5
-rw-r--r--spec/frontend/monitoring/components/dashboard_header_spec.js4
-rw-r--r--spec/frontend/monitoring/components/dashboard_panel_builder_spec.js2
-rw-r--r--spec/frontend/monitoring/components/dashboard_panel_spec.js16
-rw-r--r--spec/frontend/monitoring/components/dashboard_spec.js5
-rw-r--r--spec/frontend/monitoring/components/dashboard_url_time_spec.js4
-rw-r--r--spec/frontend/monitoring/components/graph_group_spec.js4
-rw-r--r--spec/frontend/monitoring/components/group_empty_state_spec.js4
-rw-r--r--spec/frontend/monitoring/components/refresh_button_spec.js1
-rw-r--r--spec/frontend/monitoring/components/variables/dropdown_field_spec.js6
-rw-r--r--spec/frontend/monitoring/components/variables/text_field_spec.js6
-rw-r--r--spec/frontend/monitoring/pages/panel_new_page_spec.js4
-rw-r--r--spec/frontend/monitoring/store/actions_spec.js11
-rw-r--r--spec/frontend/nav/components/new_nav_toggle_spec.js186
-rw-r--r--spec/frontend/nav/components/responsive_app_spec.js4
-rw-r--r--spec/frontend/nav/components/responsive_header_spec.js6
-rw-r--r--spec/frontend/nav/components/responsive_home_spec.js6
-rw-r--r--spec/frontend/nav/components/top_nav_app_spec.js4
-rw-r--r--spec/frontend/nav/components/top_nav_container_view_spec.js4
-rw-r--r--spec/frontend/nav/components/top_nav_dropdown_menu_spec.js4
-rw-r--r--spec/frontend/nav/components/top_nav_menu_sections_spec.js4
-rw-r--r--spec/frontend/nav/components/top_nav_new_dropdown_spec.js29
-rw-r--r--spec/frontend/notebook/cells/code_spec.js4
-rw-r--r--spec/frontend/notebook/cells/markdown_spec.js6
-rw-r--r--spec/frontend/notebook/cells/output/error_spec.js48
-rw-r--r--spec/frontend/notebook/cells/output/index_spec.js4
-rw-r--r--spec/frontend/notebook/cells/prompt_spec.js4
-rw-r--r--spec/frontend/notebook/index_spec.js6
-rw-r--r--spec/frontend/notebook/mock_data.js6
-rw-r--r--spec/frontend/notes/components/comment_form_spec.js120
-rw-r--r--spec/frontend/notes/components/comment_type_dropdown_spec.js4
-rw-r--r--spec/frontend/notes/components/diff_discussion_header_spec.js4
-rw-r--r--spec/frontend/notes/components/discussion_actions_spec.js11
-rw-r--r--spec/frontend/notes/components/discussion_counter_spec.js3
-rw-r--r--spec/frontend/notes/components/discussion_filter_note_spec.js5
-rw-r--r--spec/frontend/notes/components/discussion_navigator_spec.js1
-rw-r--r--spec/frontend/notes/components/discussion_notes_replies_wrapper_spec.js4
-rw-r--r--spec/frontend/notes/components/discussion_notes_spec.js5
-rw-r--r--spec/frontend/notes/components/discussion_reply_placeholder_spec.js4
-rw-r--r--spec/frontend/notes/components/discussion_resolve_button_spec.js4
-rw-r--r--spec/frontend/notes/components/discussion_resolve_with_issue_button_spec.js4
-rw-r--r--spec/frontend/notes/components/email_participants_warning_spec.js5
-rw-r--r--spec/frontend/notes/components/note_actions/reply_button_spec.js5
-rw-r--r--spec/frontend/notes/components/note_actions/timeline_event_button_spec.js4
-rw-r--r--spec/frontend/notes/components/note_actions_spec.js7
-rw-r--r--spec/frontend/notes/components/note_attachment_spec.js5
-rw-r--r--spec/frontend/notes/components/note_body_spec.js4
-rw-r--r--spec/frontend/notes/components/note_edited_text_spec.js69
-rw-r--r--spec/frontend/notes/components/note_form_spec.js4
-rw-r--r--spec/frontend/notes/components/note_header_spec.js5
-rw-r--r--spec/frontend/notes/components/note_signed_out_widget_spec.js4
-rw-r--r--spec/frontend/notes/components/noteable_discussion_spec.js15
-rw-r--r--spec/frontend/notes/components/noteable_note_spec.js4
-rw-r--r--spec/frontend/notes/components/notes_activity_header_spec.js4
-rw-r--r--spec/frontend/notes/components/notes_app_spec.js3
-rw-r--r--spec/frontend/notes/components/toggle_replies_widget_spec.js4
-rw-r--r--spec/frontend/notes/deprecated_notes_spec.js1
-rw-r--r--spec/frontend/notes/stores/actions_spec.js26
-rw-r--r--spec/frontend/notifications/components/custom_notifications_modal_spec.js52
-rw-r--r--spec/frontend/notifications/components/notifications_dropdown_spec.js4
-rw-r--r--spec/frontend/observability/index_spec.js64
-rw-r--r--spec/frontend/observability/observability_app_spec.js144
-rw-r--r--spec/frontend/observability/skeleton_spec.js15
-rw-r--r--spec/frontend/operation_settings/components/metrics_settings_spec.js6
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/delete_image_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_modal_spec.js7
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/details_header_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js43
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js26
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_loader_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/group_empty_state_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js20
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/project_empty_state_spec.js4
-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/mock_data.js3
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/pages/details_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/pages/list_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/dependency_proxy/app_spec.js13
-rw-r--r--spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/dependency_proxy/components/manifest_row_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/details/details_header_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_header_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_row_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_header_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_row_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/pages/list_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/app_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/details_title_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/file_sha_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/terraform_installation_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js10
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/__snapshots__/packages_list_app_spec.js.snap2
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_search_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/infrastructure_title_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_app_spec.js10
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/components/packages_list_spec.js35
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js8
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/shared/package_list_row_spec.js7
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap3
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap10
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/dependency_row_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/file_sha_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/installation_title_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js6
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/metadata/composer_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/metadata/conan_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/metadata/maven_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/metadata/nuget_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/metadata/pypi_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/package_title_spec.js6
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js80
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js17
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/details/version_row_spec.js34
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/functional/delete_packages_spec.js8
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/package_list_row_spec.js9
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/publish_method_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/package_registry/mock_data.js1
-rw-r--r--spec/frontend/packages_and_registries/package_registry/pages/details_spec.js8
-rw-r--r--spec/frontend/packages_and_registries/package_registry/pages/list_spec.js25
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/dependency_proxy_settings_spec.js6
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/exceptions_input_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/group_settings_app_spec.js6
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/package_settings_spec.js6
-rw-r--r--spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js2
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js1
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_spec.js1
-rw-r--r--spec/frontend/packages_and_registries/settings/project/settings/components/registry_settings_app_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/shared/components/cli_commands_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/shared/components/delete_package_modal_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/shared/components/package_path_spec.js7
-rw-r--r--spec/frontend/packages_and_registries/shared/components/packages_list_loader_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/shared/components/persisted_search_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/shared/components/publish_method_spec.js5
-rw-r--r--spec/frontend/packages_and_registries/shared/components/registry_breadcrumb_spec.js4
-rw-r--r--spec/frontend/packages_and_registries/shared/components/registry_list_spec.js21
-rw-r--r--spec/frontend/packages_and_registries/shared/components/settings_block_spec.js4
-rw-r--r--spec/frontend/pages/admin/jobs/index/components/cancel_jobs_spec.js4
-rw-r--r--spec/frontend/pages/groups/new/components/app_spec.js25
-rw-r--r--spec/frontend/pages/groups/new/components/create_group_description_details_spec.js4
-rw-r--r--spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js8
-rw-r--r--spec/frontend/pages/import/history/components/import_error_details_spec.js11
-rw-r--r--spec/frontend/pages/import/history/components/import_history_app_spec.js11
-rw-r--r--spec/frontend/pages/profiles/password_prompt/password_prompt_modal_spec.js7
-rw-r--r--spec/frontend/pages/projects/forks/new/components/app_spec.js4
-rw-r--r--spec/frontend/pages/projects/forks/new/components/fork_form_spec.js7
-rw-r--r--spec/frontend/pages/projects/forks/new/components/project_namespace_spec.js17
-rw-r--r--spec/frontend/pages/projects/graphs/code_coverage_spec.js11
-rw-r--r--spec/frontend/pages/projects/pipeline_schedules/shared/components/interval_pattern_input_spec.js6
-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_feature_settings_spec.js5
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/project_setting_row_spec.js4
-rw-r--r--spec/frontend/pages/projects/shared/permissions/components/settings_panel_spec.js5
-rw-r--r--spec/frontend/pages/shared/wikis/components/wiki_alert_spec.js5
-rw-r--r--spec/frontend/pages/shared/wikis/components/wiki_content_spec.js5
-rw-r--r--spec/frontend/pages/shared/wikis/components/wiki_form_spec.js9
-rw-r--r--spec/frontend/pdf/index_spec.js4
-rw-r--r--spec/frontend/pdf/page_spec.js4
-rw-r--r--spec/frontend/performance_bar/components/add_request_spec.js4
-rw-r--r--spec/frontend/performance_bar/components/detailed_metric_spec.js4
-rw-r--r--spec/frontend/performance_bar/components/request_warning_spec.js4
-rw-r--r--spec/frontend/persistent_user_callout_spec.js4
-rw-r--r--spec/frontend/pipeline_wizard/components/commit_spec.js14
-rw-r--r--spec/frontend/pipeline_wizard/components/editor_spec.js4
-rw-r--r--spec/frontend/pipeline_wizard/components/input_wrapper_spec.js8
-rw-r--r--spec/frontend/pipeline_wizard/components/step_nav_spec.js4
-rw-r--r--spec/frontend/pipeline_wizard/components/widgets/checklist_spec.js4
-rw-r--r--spec/frontend/pipeline_wizard/components/widgets/list_spec.js8
-rw-r--r--spec/frontend/pipeline_wizard/components/wrapper_spec.js21
-rw-r--r--spec/frontend/pipeline_wizard/pipeline_wizard_spec.js4
-rw-r--r--spec/frontend/pipelines/components/dag/dag_annotations_spec.js5
-rw-r--r--spec/frontend/pipelines/components/dag/dag_graph_spec.js5
-rw-r--r--spec/frontend/pipelines/components/dag/dag_spec.js5
-rw-r--r--spec/frontend/pipelines/components/jobs/failed_jobs_app_spec.js8
-rw-r--r--spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js8
-rw-r--r--spec/frontend/pipelines/components/jobs/jobs_app_spec.js8
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js12
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_mini_graph_spec.js15
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js5
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js5
-rw-r--r--spec/frontend/pipelines/components/pipeline_tabs_spec.js4
-rw-r--r--spec/frontend/pipelines/components/pipelines_filtered_search_spec.js2
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/empty_state/ci_templates_spec.js5
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/empty_state/ios_templates_spec.js5
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates_spec.js5
-rw-r--r--spec/frontend/pipelines/empty_state_spec.js5
-rw-r--r--spec/frontend/pipelines/graph/action_component_spec.js1
-rw-r--r--spec/frontend/pipelines/graph/graph_component_wrapper_spec.js4
-rw-r--r--spec/frontend/pipelines/graph/graph_view_selector_spec.js4
-rw-r--r--spec/frontend/pipelines/graph/job_group_dropdown_spec.js4
-rw-r--r--spec/frontend/pipelines/graph/job_item_spec.js47
-rw-r--r--spec/frontend/pipelines/graph/linked_pipeline_spec.js4
-rw-r--r--spec/frontend/pipelines/graph/linked_pipelines_column_spec.js4
-rw-r--r--spec/frontend/pipelines/graph/stage_column_component_spec.js4
-rw-r--r--spec/frontend/pipelines/graph_shared/links_inner_spec.js1
-rw-r--r--spec/frontend/pipelines/graph_shared/links_layer_spec.js4
-rw-r--r--spec/frontend/pipelines/header_component_spec.js5
-rw-r--r--spec/frontend/pipelines/nav_controls_spec.js37
-rw-r--r--spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js4
-rw-r--r--spec/frontend/pipelines/pipeline_labels_spec.js4
-rw-r--r--spec/frontend/pipelines/pipeline_multi_actions_spec.js2
-rw-r--r--spec/frontend/pipelines/pipeline_triggerer_spec.js6
-rw-r--r--spec/frontend/pipelines/pipeline_url_spec.js4
-rw-r--r--spec/frontend/pipelines/pipelines_actions_spec.js7
-rw-r--r--spec/frontend/pipelines/pipelines_artifacts_spec.js5
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js5
-rw-r--r--spec/frontend/pipelines/pipelines_table_spec.js6
-rw-r--r--spec/frontend/pipelines/test_reports/stores/actions_spec.js6
-rw-r--r--spec/frontend/pipelines/test_reports/stores/mutations_spec.js6
-rw-r--r--spec/frontend/pipelines/test_reports/test_case_details_spec.js5
-rw-r--r--spec/frontend/pipelines/test_reports/test_reports_spec.js4
-rw-r--r--spec/frontend/pipelines/test_reports/test_suite_table_spec.js4
-rw-r--r--spec/frontend/pipelines/time_ago_spec.js5
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js5
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_status_token_spec.js5
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js5
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js5
-rw-r--r--spec/frontend/popovers/components/popovers_spec.js5
-rw-r--r--spec/frontend/profile/account/components/delete_account_modal_spec.js6
-rw-r--r--spec/frontend/profile/account/components/update_username_spec.js46
-rw-r--r--spec/frontend/profile/components/activity_calendar_spec.js120
-rw-r--r--spec/frontend/profile/components/followers_tab_spec.js21
-rw-r--r--spec/frontend/profile/components/following_tab_spec.js21
-rw-r--r--spec/frontend/profile/components/overview_tab_spec.js7
-rw-r--r--spec/frontend/profile/components/user_achievements_spec.js102
-rw-r--r--spec/frontend/profile/mock_data.js22
-rw-r--r--spec/frontend/profile/preferences/components/diffs_colors_preview_spec.js5
-rw-r--r--spec/frontend/profile/preferences/components/diffs_colors_spec.js5
-rw-r--r--spec/frontend/profile/preferences/components/integration_view_spec.js5
-rw-r--r--spec/frontend/profile/preferences/components/profile_preferences_spec.js9
-rw-r--r--spec/frontend/profile/utils_spec.js15
-rw-r--r--spec/frontend/projects/clusters_deprecation_slert/components/clusters_deprecation_alert_spec.js4
-rw-r--r--spec/frontend/projects/commit/components/branches_dropdown_spec.js4
-rw-r--r--spec/frontend/projects/commit/components/form_modal_spec.js3
-rw-r--r--spec/frontend/projects/commit/components/projects_dropdown_spec.js15
-rw-r--r--spec/frontend/projects/commit/store/actions_spec.js6
-rw-r--r--spec/frontend/projects/commits/components/author_select_spec.js1
-rw-r--r--spec/frontend/projects/commits/store/actions_spec.js8
-rw-r--r--spec/frontend/projects/compare/components/app_spec.js5
-rw-r--r--spec/frontend/projects/compare/components/repo_dropdown_spec.js5
-rw-r--r--spec/frontend/projects/compare/components/revision_card_spec.js5
-rw-r--r--spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js51
-rw-r--r--spec/frontend/projects/compare/components/revision_dropdown_spec.js48
-rw-r--r--spec/frontend/projects/components/project_delete_button_spec.js5
-rw-r--r--spec/frontend/projects/components/shared/delete_button_spec.js5
-rw-r--r--spec/frontend/projects/details/upload_button_spec.js4
-rw-r--r--spec/frontend/projects/new/components/app_spec.js29
-rw-r--r--spec/frontend/projects/new/components/deployment_target_select_spec.js1
-rw-r--r--spec/frontend/projects/new/components/new_project_push_tip_popover_spec.js1
-rw-r--r--spec/frontend/projects/new/components/new_project_url_select_spec.js21
-rw-r--r--spec/frontend/projects/pipelines/charts/components/__snapshots__/ci_cd_analytics_area_chart_spec.js.snap29
-rw-r--r--spec/frontend/projects/pipelines/charts/components/app_spec.js4
-rw-r--r--spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_area_chart_spec.js5
-rw-r--r--spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js5
-rw-r--r--spec/frontend/projects/pipelines/charts/components/statistics_list_spec.js4
-rw-r--r--spec/frontend/projects/prune_unreachable_objects_button_spec.js7
-rw-r--r--spec/frontend/projects/settings/branch_rules/components/edit/branch_dropdown_spec.js8
-rw-r--r--spec/frontend/projects/settings/branch_rules/components/edit/index_spec.js4
-rw-r--r--spec/frontend/projects/settings/branch_rules/components/edit/protections/index_spec.js4
-rw-r--r--spec/frontend/projects/settings/branch_rules/components/edit/protections/merge_protections_spec.js4
-rw-r--r--spec/frontend/projects/settings/branch_rules/components/edit/protections/push_protections_spec.js4
-rw-r--r--spec/frontend/projects/settings/branch_rules/components/view/index_spec.js48
-rw-r--r--spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js2
-rw-r--r--spec/frontend/projects/settings/branch_rules/components/view/protection_spec.js2
-rw-r--r--spec/frontend/projects/settings/components/default_branch_selector_spec.js4
-rw-r--r--spec/frontend/projects/settings/components/new_access_dropdown_spec.js20
-rw-r--r--spec/frontend/projects/settings/components/shared_runners_toggle_spec.js4
-rw-r--r--spec/frontend/projects/settings/components/transfer_project_form_spec.js4
-rw-r--r--spec/frontend/projects/settings/repository/branch_rules/app_spec.js6
-rw-r--r--spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js4
-rw-r--r--spec/frontend/projects/settings/repository/branch_rules/mock_data.js2
-rw-r--r--spec/frontend/projects/settings/topics/components/topics_token_selector_spec.js1
-rw-r--r--spec/frontend/projects/settings/utils_spec.js24
-rw-r--r--spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js1
-rw-r--r--spec/frontend/projects/terraform_notification/terraform_notification_spec.js4
-rw-r--r--spec/frontend/protected_branches/protected_branch_edit_spec.js6
-rw-r--r--spec/frontend/ref/components/__snapshots__/ref_selector_spec.js.snap80
-rw-r--r--spec/frontend/ref/components/ref_selector_spec.js53
-rw-r--r--spec/frontend/releases/components/app_edit_new_spec.js7
-rw-r--r--spec/frontend/releases/components/app_index_spec.js12
-rw-r--r--spec/frontend/releases/components/app_show_spec.js15
-rw-r--r--spec/frontend/releases/components/asset_links_form_spec.js5
-rw-r--r--spec/frontend/releases/components/confirm_delete_modal_spec.js4
-rw-r--r--spec/frontend/releases/components/evidence_block_spec.js4
-rw-r--r--spec/frontend/releases/components/issuable_stats_spec.js5
-rw-r--r--spec/frontend/releases/components/release_block_footer_spec.js5
-rw-r--r--spec/frontend/releases/components/release_block_header_spec.js4
-rw-r--r--spec/frontend/releases/components/release_block_milestone_info_spec.js5
-rw-r--r--spec/frontend/releases/components/release_block_spec.js4
-rw-r--r--spec/frontend/releases/components/releases_pagination_spec.js4
-rw-r--r--spec/frontend/releases/components/releases_sort_spec.js4
-rw-r--r--spec/frontend/releases/components/tag_field_exsting_spec.js5
-rw-r--r--spec/frontend/releases/components/tag_field_new_spec.js8
-rw-r--r--spec/frontend/releases/components/tag_field_spec.js5
-rw-r--r--spec/frontend/releases/release_notification_service_spec.js8
-rw-r--r--spec/frontend/releases/stores/modules/detail/actions_spec.js20
-rw-r--r--spec/frontend/releases/stores/modules/detail/getters_spec.js81
-rw-r--r--spec/frontend/repository/commits_service_spec.js4
-rw-r--r--spec/frontend/repository/components/blob_button_group_spec.js4
-rw-r--r--spec/frontend/repository/components/blob_content_viewer_spec.js6
-rw-r--r--spec/frontend/repository/components/blob_controls_spec.js2
-rw-r--r--spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js2
-rw-r--r--spec/frontend/repository/components/blob_viewers/notebook_viewer_spec.js22
-rw-r--r--spec/frontend/repository/components/breadcrumbs_spec.js4
-rw-r--r--spec/frontend/repository/components/delete_blob_modal_spec.js8
-rw-r--r--spec/frontend/repository/components/directory_download_links_spec.js4
-rw-r--r--spec/frontend/repository/components/fork_info_spec.js137
-rw-r--r--spec/frontend/repository/components/fork_suggestion_spec.js2
-rw-r--r--spec/frontend/repository/components/fork_sync_conflicts_modal_spec.js42
-rw-r--r--spec/frontend/repository/components/last_commit_spec.js33
-rw-r--r--spec/frontend/repository/components/new_directory_modal_spec.js12
-rw-r--r--spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap42
-rw-r--r--spec/frontend/repository/components/preview/index_spec.js93
-rw-r--r--spec/frontend/repository/components/table/index_spec.js4
-rw-r--r--spec/frontend/repository/components/table/parent_row_spec.js4
-rw-r--r--spec/frontend/repository/components/table/row_spec.js6
-rw-r--r--spec/frontend/repository/components/tree_content_spec.js50
-rw-r--r--spec/frontend/repository/components/upload_blob_modal_spec.js52
-rw-r--r--spec/frontend/repository/mixins/highlight_mixin_spec.js2
-rw-r--r--spec/frontend/repository/mock_data.js6
-rw-r--r--spec/frontend/repository/pages/blob_spec.js4
-rw-r--r--spec/frontend/repository/pages/index_spec.js2
-rw-r--r--spec/frontend/repository/pages/tree_spec.js2
-rw-r--r--spec/frontend/saved_replies/components/__snapshots__/list_item_spec.js.snap48
-rw-r--r--spec/frontend/saved_replies/components/form_spec.js144
-rw-r--r--spec/frontend/saved_replies/components/list_item_spec.js36
-rw-r--r--spec/frontend/saved_replies/components/list_spec.js48
-rw-r--r--spec/frontend/saved_replies/pages/index_spec.js45
-rw-r--r--spec/frontend/search/mock_data.js7
-rw-r--r--spec/frontend/search/sidebar/components/app_spec.js34
-rw-r--r--spec/frontend/search/sidebar/components/checkbox_filter_spec.js9
-rw-r--r--spec/frontend/search/sidebar/components/filters_spec.js21
-rw-r--r--spec/frontend/search/sidebar/components/language_filter_spec.js (renamed from spec/frontend/search/sidebar/components/language_filters_spec.js)27
-rw-r--r--spec/frontend/search/sidebar/components/radio_filter_spec.js10
-rw-r--r--spec/frontend/search/sidebar/components/scope_navigation_spec.js39
-rw-r--r--spec/frontend/search/sort/components/app_spec.js5
-rw-r--r--spec/frontend/search/store/actions_spec.js48
-rw-r--r--spec/frontend/search/store/getters_spec.js47
-rw-r--r--spec/frontend/search/store/utils_spec.js35
-rw-r--r--spec/frontend/search/topbar/components/app_spec.js4
-rw-r--r--spec/frontend/search/topbar/components/group_filter_spec.js4
-rw-r--r--spec/frontend/search/topbar/components/project_filter_spec.js4
-rw-r--r--spec/frontend/search/topbar/components/searchable_dropdown_item_spec.js4
-rw-r--r--spec/frontend/search/topbar/components/searchable_dropdown_spec.js49
-rw-r--r--spec/frontend/search_autocomplete_spec.js1
-rw-r--r--spec/frontend/search_settings/components/search_settings_spec.js4
-rw-r--r--spec/frontend/security_configuration/components/app_spec.js30
-rw-r--r--spec/frontend/security_configuration/components/auto_dev_ops_alert_spec.js4
-rw-r--r--spec/frontend/security_configuration/components/auto_dev_ops_enabled_alert_spec.js4
-rw-r--r--spec/frontend/security_configuration/components/feature_card_spec.js6
-rw-r--r--spec/frontend/security_configuration/components/training_provider_list_spec.js3
-rw-r--r--spec/frontend/security_configuration/components/upgrade_banner_spec.js1
-rw-r--r--spec/frontend/security_configuration/constants.js1
-rw-r--r--spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap35
-rw-r--r--spec/frontend/sentry/index_spec.js7
-rw-r--r--spec/frontend/sentry/legacy_index_spec.js7
-rw-r--r--spec/frontend/sentry/sentry_config_spec.js7
-rw-r--r--spec/frontend/set_status_modal/set_status_modal_wrapper_spec.js7
-rw-r--r--spec/frontend/set_status_modal/user_profile_set_status_wrapper_spec.js4
-rw-r--r--spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js4
-rw-r--r--spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js7
-rw-r--r--spec/frontend/sidebar/components/assignees/assignee_title_spec.js5
-rw-r--r--spec/frontend/sidebar/components/assignees/assignees_realtime_spec.js1
-rw-r--r--spec/frontend/sidebar/components/assignees/assignees_spec.js4
-rw-r--r--spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js4
-rw-r--r--spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js4
-rw-r--r--spec/frontend/sidebar/components/assignees/issuable_assignees_spec.js5
-rw-r--r--spec/frontend/sidebar/components/assignees/sidebar_assignees_spec.js3
-rw-r--r--spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js11
-rw-r--r--spec/frontend/sidebar/components/assignees/sidebar_editable_item_spec.js5
-rw-r--r--spec/frontend/sidebar/components/assignees/sidebar_invite_members_spec.js4
-rw-r--r--spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js10
-rw-r--r--spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js4
-rw-r--r--spec/frontend/sidebar/components/assignees/user_name_with_status_spec.js4
-rw-r--r--spec/frontend/sidebar/components/confidential/sidebar_confidentiality_content_spec.js4
-rw-r--r--spec/frontend/sidebar/components/confidential/sidebar_confidentiality_form_spec.js12
-rw-r--r--spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js7
-rw-r--r--spec/frontend/sidebar/components/copy/copyable_field_spec.js4
-rw-r--r--spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js8
-rw-r--r--spec/frontend/sidebar/components/crm_contacts/crm_contacts_spec.js5
-rw-r--r--spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js15
-rw-r--r--spec/frontend/sidebar/components/date/sidebar_formatted_date_spec.js4
-rw-r--r--spec/frontend/sidebar/components/date/sidebar_inherit_date_spec.js4
-rw-r--r--spec/frontend/sidebar/components/incidents/escalation_status_spec.js4
-rw-r--r--spec/frontend/sidebar/components/incidents/sidebar_escalation_status_spec.js6
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_button_spec.js4
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_contents_create_view_spec.js16
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_contents_labels_view_spec.js4
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_contents_spec.js4
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_title_spec.js4
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_value_collapsed_spec.js6
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_value_spec.js4
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_vue/label_item_spec.js4
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_vue/labels_select_root_spec.js4
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_vue/store/actions_spec.js8
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_create_view_spec.js10
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view_spec.js8
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_spec.js4
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_footer_spec.js4
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_header_spec.js4
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_value_spec.js4
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_widget/embedded_labels_list_spec.js4
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_widget/label_item_spec.js4
-rw-r--r--spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js20
-rw-r--r--spec/frontend/sidebar/components/lock/edit_form_buttons_spec.js13
-rw-r--r--spec/frontend/sidebar/components/lock/edit_form_spec.js5
-rw-r--r--spec/frontend/sidebar/components/lock/issuable_lock_form_spec.js7
-rw-r--r--spec/frontend/sidebar/components/milestone/milestone_dropdown_spec.js4
-rw-r--r--spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js1
-rw-r--r--spec/frontend/sidebar/components/move/move_issue_button_spec.js6
-rw-r--r--spec/frontend/sidebar/components/move/move_issues_button_spec.js21
-rw-r--r--spec/frontend/sidebar/components/participants/participants_spec.js197
-rw-r--r--spec/frontend/sidebar/components/participants/sidebar_participants_widget_spec.js1
-rw-r--r--spec/frontend/sidebar/components/reviewers/reviewer_title_spec.js5
-rw-r--r--spec/frontend/sidebar/components/reviewers/reviewers_spec.js4
-rw-r--r--spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js3
-rw-r--r--spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js4
-rw-r--r--spec/frontend/sidebar/components/severity/sidebar_severity_spec.js13
-rw-r--r--spec/frontend/sidebar/components/sidebar_dropdown_spec.js4
-rw-r--r--spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js11
-rw-r--r--spec/frontend/sidebar/components/status/status_dropdown_spec.js4
-rw-r--r--spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js7
-rw-r--r--spec/frontend/sidebar/components/subscriptions/subscriptions_dropdown_spec.js4
-rw-r--r--spec/frontend/sidebar/components/subscriptions/subscriptions_spec.js5
-rw-r--r--spec/frontend/sidebar/components/time_tracking/create_timelog_form_spec.js4
-rw-r--r--spec/frontend/sidebar/components/time_tracking/report_spec.js5
-rw-r--r--spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js6
-rw-r--r--spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js7
-rw-r--r--spec/frontend/sidebar/components/todo_toggle/todo_button_spec.js1
-rw-r--r--spec/frontend/sidebar/components/todo_toggle/todo_spec.js4
-rw-r--r--spec/frontend/sidebar/components/toggle/toggle_sidebar_spec.js4
-rw-r--r--spec/frontend/sidebar/mock_data.js1
-rw-r--r--spec/frontend/sidebar/sidebar_mediator_spec.js2
-rw-r--r--spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap3
-rw-r--r--spec/frontend/snippets/components/edit_spec.js21
-rw-r--r--spec/frontend/snippets/components/embed_dropdown_spec.js5
-rw-r--r--spec/frontend/snippets/components/show_spec.js4
-rw-r--r--spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js5
-rw-r--r--spec/frontend/snippets/components/snippet_blob_edit_spec.js8
-rw-r--r--spec/frontend/snippets/components/snippet_blob_view_spec.js4
-rw-r--r--spec/frontend/snippets/components/snippet_description_edit_spec.js4
-rw-r--r--spec/frontend/snippets/components/snippet_description_view_spec.js4
-rw-r--r--spec/frontend/snippets/components/snippet_header_spec.js136
-rw-r--r--spec/frontend/snippets/components/snippet_title_spec.js4
-rw-r--r--spec/frontend/snippets/components/snippet_visibility_edit_spec.js4
-rw-r--r--spec/frontend/snippets/mock_data.js19
-rw-r--r--spec/frontend/streaming/chunk_writer_spec.js214
-rw-r--r--spec/frontend/streaming/handle_streamed_anchor_link_spec.js132
-rw-r--r--spec/frontend/streaming/html_stream_spec.js46
-rw-r--r--spec/frontend/streaming/rate_limit_stream_requests_spec.js155
-rw-r--r--spec/frontend/streaming/render_balancer_spec.js69
-rw-r--r--spec/frontend/streaming/render_html_streams_spec.js96
-rw-r--r--spec/frontend/super_sidebar/components/context_switcher_spec.js219
-rw-r--r--spec/frontend/super_sidebar/components/context_switcher_toggle_spec.js50
-rw-r--r--spec/frontend/super_sidebar/components/frequent_items_list_spec.js68
-rw-r--r--spec/frontend/super_sidebar/components/global_search/components/global_search_autocomplete_items_spec.js238
-rw-r--r--spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js102
-rw-r--r--spec/frontend/super_sidebar/components/global_search/components/global_search_scoped_items_spec.js120
-rw-r--r--spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js516
-rw-r--r--spec/frontend/super_sidebar/components/global_search/mock_data.js404
-rw-r--r--spec/frontend/super_sidebar/components/global_search/store/actions_spec.js113
-rw-r--r--spec/frontend/super_sidebar/components/global_search/store/getters_spec.js333
-rw-r--r--spec/frontend/super_sidebar/components/global_search/store/mutations_spec.js63
-rw-r--r--spec/frontend/super_sidebar/components/groups_list_spec.js87
-rw-r--r--spec/frontend/super_sidebar/components/help_center_spec.js11
-rw-r--r--spec/frontend/super_sidebar/components/items_list_spec.js63
-rw-r--r--spec/frontend/super_sidebar/components/nav_item_spec.js49
-rw-r--r--spec/frontend/super_sidebar/components/projects_list_spec.js82
-rw-r--r--spec/frontend/super_sidebar/components/search_results_spec.js57
-rw-r--r--spec/frontend/super_sidebar/components/sidebar_portal_spec.js68
-rw-r--r--spec/frontend/super_sidebar/components/super_sidebar_spec.js27
-rw-r--r--spec/frontend/super_sidebar/components/user_bar_spec.js31
-rw-r--r--spec/frontend/super_sidebar/components/user_menu_spec.js375
-rw-r--r--spec/frontend/super_sidebar/components/user_name_group_spec.js100
-rw-r--r--spec/frontend/super_sidebar/mock_data.js187
-rw-r--r--spec/frontend/super_sidebar/super_sidebar_collapsed_state_manager_spec.js157
-rw-r--r--spec/frontend/super_sidebar/utils_spec.js160
-rw-r--r--spec/frontend/syntax_highlight_spec.js6
-rw-r--r--spec/frontend/tags/components/delete_tag_modal_spec.js4
-rw-r--r--spec/frontend/terms/components/app_spec.js4
-rw-r--r--spec/frontend/terraform/components/empty_state_spec.js4
-rw-r--r--spec/frontend/terraform/components/init_command_modal_spec.js4
-rw-r--r--spec/frontend/terraform/components/states_table_actions_spec.js8
-rw-r--r--spec/frontend/terraform/components/states_table_spec.js7
-rw-r--r--spec/frontend/terraform/components/terraform_list_spec.js5
-rw-r--r--spec/frontend/toggles/index_spec.js1
-rw-r--r--spec/frontend/token_access/inbound_token_access_spec.js4
-rw-r--r--spec/frontend/token_access/opt_in_jwt_spec.js4
-rw-r--r--spec/frontend/token_access/outbound_token_access_spec.js4
-rw-r--r--spec/frontend/token_access/token_access_app_spec.js14
-rw-r--r--spec/frontend/token_access/token_projects_table_spec.js38
-rw-r--r--spec/frontend/tooltips/components/tooltips_spec.js5
-rw-r--r--spec/frontend/usage_quotas/components/usage_quotas_app_spec.js4
-rw-r--r--spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js4
-rw-r--r--spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js3
-rw-r--r--spec/frontend/usage_quotas/storage/components/storage_type_icon_spec.js4
-rw-r--r--spec/frontend/usage_quotas/storage/components/usage_graph_spec.js4
-rw-r--r--spec/frontend/user_lists/components/user_lists_spec.js5
-rw-r--r--spec/frontend/user_lists/components/user_lists_table_spec.js4
-rw-r--r--spec/frontend/user_popovers_spec.js11
-rw-r--r--spec/frontend/validators/length_validator_spec.js91
-rw-r--r--spec/frontend/vue_compat_test_setup.js60
-rw-r--r--spec/frontend/vue_merge_request_widget/components/action_buttons.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/added_commit_message_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/approvals/approvals_spec.js221
-rw-r--r--spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_optional_spec.js5
-rw-r--r--spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js21
-rw-r--r--spec/frontend/vue_merge_request_widget/components/artifacts_list_app_spec.js1
-rw-r--r--spec/frontend/vue_merge_request_widget/components/artifacts_list_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/extensions/child_content_spec.js5
-rw-r--r--spec/frontend/vue_merge_request_widget/components/extensions/status_icon_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_collapsible_extension_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_alert_message_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_author_spec.js1
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_author_time_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_container_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_icon_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_container_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js78
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_related_links_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js5
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_suggest_pipeline_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/review_app_link_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/commit_edit_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/merge_checks_failed_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/merge_failed_pipeline_confirmation_dialog_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js5
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js2
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_checking_spec.js5
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_commit_message_dropdown_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_commits_header_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_failed_to_merge_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_merged_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_missing_branch_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js5
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge_spec.js62
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js5
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js5
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_sha_mismatch_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_squash_before_merge_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions_spec.js8
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/new_ready_to_merge_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/states/work_in_progress_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/action_buttons_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/deployment/deployment_action_button_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js5
-rw-r--r--spec/frontend/vue_merge_request_widget/deployment/deployment_list_spec.js1
-rw-r--r--spec/frontend/vue_merge_request_widget/deployment/deployment_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/deployment/deployment_view_button_spec.js4
-rw-r--r--spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js1
-rw-r--r--spec/frontend/vue_merge_request_widget/extentions/accessibility/index_spec.js1
-rw-r--r--spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js1
-rw-r--r--spec/frontend/vue_merge_request_widget/extentions/terraform/index_spec.js1
-rw-r--r--spec/frontend/vue_merge_request_widget/mr_widget_how_to_merge_modal_spec.js5
-rw-r--r--spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js136
-rw-r--r--spec/frontend/vue_merge_request_widget/stores/get_state_key_spec.js2
-rw-r--r--spec/frontend/vue_shared/alert_details/alert_management_sidebar_todo_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/__snapshots__/file_row_header_spec.js.snap40
-rw-r--r--spec/frontend/vue_shared/components/actions_button_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/alert_details_table_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/awards_list_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/changed_file_icon_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/chronic_duration_input_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/ci_badge_link_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/ci_icon_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/clipboard_button_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/clone_dropdown_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/code_block_highlighted_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/code_block_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/color_picker/color_picker_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/color_select_dropdown/color_item_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/color_select_dropdown/color_select_root_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_color_view_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/color_select_dropdown/dropdown_header_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/color_select_dropdown/dropdown_value_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/commit_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/confidentiality_badge_spec.js17
-rw-r--r--spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/confirm_danger/confirm_danger_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/confirm_fork_modal_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/confirm_modal_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/content_transition_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/date_time_picker/date_time_picker_input_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/date_time_picker/date_time_picker_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/deployment_instance/deployment_instance_spec.js12
-rw-r--r--spec/frontend/vue_shared/components/design_management/design_note_pin_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/diff_viewer/diff_viewer_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/dismissible_alert_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/dismissible_container_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/dom_element_listener_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/dropdown/dropdown_button_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js9
-rw-r--r--spec/frontend/vue_shared/components/dropdown_keyboard_navigation_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/ensure_data_spec.js1
-rw-r--r--spec/frontend/vue_shared/components/entity_select/project_select_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/expand_button_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/file_finder/item_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/file_icon_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/file_row_header_spec.js28
-rw-r--r--spec/frontend/vue_shared/components/file_row_spec.js14
-rw-r--r--spec/frontend/vue_shared/components/file_tree_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js22
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js97
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_contact_token_spec.js7
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_organization_token_spec.js7
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js86
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js139
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js108
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/release_token_spec.js8
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js126
-rw-r--r--spec/frontend/vue_shared/components/form/form_footer_actions_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/form/title_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/gl_countdown_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/header_ci_component_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/help_popover_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/integration_help_text_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/keep_alive_slots_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/local_storage_sync_spec.js1
-rw-r--r--spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/markdown/drawio_toolbar_button_spec.js66
-rw-r--r--spec/frontend/vue_shared/components/markdown/editor_mode_dropdown_spec.js10
-rw-r--r--spec/frontend/vue_shared/components/markdown/field_view_spec.js8
-rw-r--r--spec/frontend/vue_shared/components/markdown/header_spec.js26
-rw-r--r--spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js193
-rw-r--r--spec/frontend/vue_shared/components/markdown/saved_replies_dropdown_spec.js62
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/markdown/toolbar_button_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/markdown/toolbar_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/markdown_drawer/markdown_drawer_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/memory_graph_spec.js20
-rw-r--r--spec/frontend/vue_shared/components/metric_images/store/actions_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/modal_copy_button_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/navigation_tabs_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/new_resource_dropdown/new_resource_dropdown_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/notes/noteable_warning_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/notes/placeholder_note_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/notes/system_note_spec.js1
-rw-r--r--spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/ordered_layout_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/page_size_selector_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/paginated_list_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js63
-rw-r--r--spec/frontend/vue_shared/components/pagination_bar/pagination_bar_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/pagination_links_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/panel_resizer_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/papa_parse_alert_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/project_avatar_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js136
-rw-r--r--spec/frontend/vue_shared/components/project_selector/project_selector_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/registry/code_instruction_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/registry/details_row_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/registry/history_item_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/registry/list_item_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/registry/metadata_item_spec.js7
-rw-r--r--spec/frontend/vue_shared/components/registry/persisted_dropdown_selection_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/registry/registry_search_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/registry/title_area_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap23
-rw-r--r--spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js64
-rw-r--r--spec/frontend/vue_shared/components/rich_timestamp_tooltip_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/instructions/runner_cli_instructions_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js6
-rw-r--r--spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js8
-rw-r--r--spec/frontend/vue_shared/components/security_reports/help_icon_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/security_reports/security_summary_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/segmented_control_button_group_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/settings/settings_block_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/smart_virtual_list_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/source_editor_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/components/chunk_deprecated_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/source_viewer_deprecated_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/stacked_progress_bar_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/table_pagination_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/time_ago_tooltip_spec.js1
-rw-r--r--spec/frontend/vue_shared/components/timezone_dropdown/timezone_dropdown_spec.js36
-rw-r--r--spec/frontend/vue_shared/components/tooltip_on_truncate_spec.js30
-rw-r--r--spec/frontend/vue_shared/components/upload_dropzone/__snapshots__/upload_dropzone_spec.js.snap14
-rw-r--r--spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js7
-rw-r--r--spec/frontend/vue_shared/components/url_sync_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/usage_quotas/usage_banner_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/user_callout_dismisser_spec.js8
-rw-r--r--spec/frontend/vue_shared/components/user_deletion_obstacles/user_deletion_obstacles_list_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/user_popover/user_popover_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/user_select_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/vuex_module_provider_spec.js4
-rw-r--r--spec/frontend/vue_shared/components/web_ide_link_spec.js4
-rw-r--r--spec/frontend/vue_shared/directives/validation_spec.js5
-rw-r--r--spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js4
-rw-r--r--spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js4
-rw-r--r--spec/frontend/vue_shared/issuable/create/components/issuable_label_selector_spec.js15
-rw-r--r--spec/frontend/vue_shared/issuable/issuable_blocked_icon_spec.js20
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar_spec.js1
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js6
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js4
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_tabs_spec.js1
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_body_spec.js6
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_description_spec.js4
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js3
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js1
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_show_root_spec.js4
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js28
-rw-r--r--spec/frontend/vue_shared/issuable/sidebar/components/issuable_sidebar_root_spec.js1
-rw-r--r--spec/frontend/vue_shared/new_namespace/components/legacy_container_spec.js2
-rw-r--r--spec/frontend/vue_shared/new_namespace/components/welcome_spec.js2
-rw-r--r--spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js9
-rw-r--r--spec/frontend/vue_shared/plugins/global_toast_spec.js22
-rw-r--r--spec/frontend/vue_shared/security_configuration/components/section_layout_spec.js4
-rw-r--r--spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js4
-rw-r--r--spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js5
-rw-r--r--spec/frontend/vue_shared/security_reports/security_reports_app_spec.js8
-rw-r--r--spec/frontend/webhooks/components/form_url_app_spec.js4
-rw-r--r--spec/frontend/whats_new/components/app_spec.js3
-rw-r--r--spec/frontend/whats_new/components/feature_spec.js5
-rw-r--r--spec/frontend/whats_new/utils/get_drawer_body_height_spec.js4
-rw-r--r--spec/frontend/work_items/components/app_spec.js4
-rw-r--r--spec/frontend/work_items/components/item_state_spec.js4
-rw-r--r--spec/frontend/work_items/components/item_title_spec.js4
-rw-r--r--spec/frontend/work_items/components/notes/__snapshots__/work_item_note_replying_spec.js.snap2
-rw-r--r--spec/frontend/work_items/components/notes/activity_filter_spec.js74
-rw-r--r--spec/frontend/work_items/components/notes/work_item_activity_sort_filter_spec.js109
-rw-r--r--spec/frontend/work_items/components/notes/work_item_discussion_spec.js26
-rw-r--r--spec/frontend/work_items/components/notes/work_item_history_only_filter_note_spec.js44
-rw-r--r--spec/frontend/work_items/components/notes/work_item_note_actions_spec.js98
-rw-r--r--spec/frontend/work_items/components/notes/work_item_note_spec.js12
-rw-r--r--spec/frontend/work_items/components/notes/work_item_notes_activity_header_spec.js63
-rw-r--r--spec/frontend/work_items/components/work_item_actions_spec.js4
-rw-r--r--spec/frontend/work_items/components/work_item_assignees_spec.js4
-rw-r--r--spec/frontend/work_items/components/work_item_description_rendered_spec.js4
-rw-r--r--spec/frontend/work_items/components/work_item_description_spec.js9
-rw-r--r--spec/frontend/work_items/components/work_item_detail_modal_spec.js4
-rw-r--r--spec/frontend/work_items/components/work_item_detail_spec.js49
-rw-r--r--spec/frontend/work_items/components/work_item_due_date_spec.js4
-rw-r--r--spec/frontend/work_items/components/work_item_labels_spec.js4
-rw-r--r--spec/frontend/work_items/components/work_item_links/okr_actions_split_button_spec.js4
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js34
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js4
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_links_menu_spec.js4
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_links_spec.js13
-rw-r--r--spec/frontend/work_items/components/work_item_notes_spec.js71
-rw-r--r--spec/frontend/work_items/components/work_item_state_spec.js4
-rw-r--r--spec/frontend/work_items/components/work_item_title_spec.js4
-rw-r--r--spec/frontend/work_items/components/work_item_type_icon_spec.js6
-rw-r--r--spec/frontend/work_items/mock_data.js236
-rw-r--r--spec/frontend/work_items/pages/create_work_item_spec.js24
-rw-r--r--spec/frontend/work_items/pages/work_item_root_spec.js4
-rw-r--r--spec/frontend/work_items/router_spec.js2
-rw-r--r--spec/frontend/work_items_hierarchy/components/app_spec.js4
-rw-r--r--spec/frontend/work_items_hierarchy/components/hierarchy_spec.js4
-rw-r--r--spec/frontend/zen_mode_spec.js29
1634 files changed, 18131 insertions, 11415 deletions
diff --git a/spec/frontend/.eslintrc.yml b/spec/frontend/.eslintrc.yml
index 45639f4c948..200f539fb3e 100644
--- a/spec/frontend/.eslintrc.yml
+++ b/spec/frontend/.eslintrc.yml
@@ -12,6 +12,7 @@ settings:
jest:
jestConfigFile: 'jest.config.js'
rules:
+ '@gitlab/vtu-no-explicit-wrapper-destroy': error
jest/expect-expect:
- off
- assertFunctionNames:
diff --git a/spec/frontend/__helpers__/create_mock_source_editor_extension.js b/spec/frontend/__helpers__/create_mock_source_editor_extension.js
new file mode 100644
index 00000000000..fa529604d6f
--- /dev/null
+++ b/spec/frontend/__helpers__/create_mock_source_editor_extension.js
@@ -0,0 +1,12 @@
+export const createMockSourceEditorExtension = (ActualExtension) => {
+ const { extensionName } = ActualExtension;
+ const providedKeys = Object.keys(new ActualExtension().provides());
+
+ const mockedMethods = Object.fromEntries(providedKeys.map((key) => [key, jest.fn()]));
+ const MockExtension = function MockExtension() {};
+ MockExtension.extensionName = extensionName;
+ MockExtension.mockedMethods = mockedMethods;
+ MockExtension.prototype.provides = jest.fn().mockReturnValue(mockedMethods);
+
+ return MockExtension;
+};
diff --git a/spec/frontend/__helpers__/experimentation_helper.js b/spec/frontend/__helpers__/experimentation_helper.js
index d5044be88d7..7e8dd463d28 100644
--- a/spec/frontend/__helpers__/experimentation_helper.js
+++ b/spec/frontend/__helpers__/experimentation_helper.js
@@ -2,16 +2,9 @@ import { merge } from 'lodash';
// This helper is for specs that use `gitlab/experimentation` module
export function withGonExperiment(experimentKey, value = true) {
- let origGon;
-
beforeEach(() => {
- origGon = window.gon;
window.gon = merge({}, window.gon || {}, { experiments: { [experimentKey]: value } });
});
-
- afterEach(() => {
- window.gon = origGon;
- });
}
// The following helper is for specs that use `gitlab-experiment` utilities,
diff --git a/spec/frontend/__helpers__/gon_helper.js b/spec/frontend/__helpers__/gon_helper.js
new file mode 100644
index 00000000000..51d5ece5fc1
--- /dev/null
+++ b/spec/frontend/__helpers__/gon_helper.js
@@ -0,0 +1,5 @@
+export const createGon = (IS_EE) => {
+ return {
+ ee: IS_EE,
+ };
+};
diff --git a/spec/frontend/__helpers__/keep_alive_component_helper_spec.js b/spec/frontend/__helpers__/keep_alive_component_helper_spec.js
index 54d397d0997..8b6cdedfd9f 100644
--- a/spec/frontend/__helpers__/keep_alive_component_helper_spec.js
+++ b/spec/frontend/__helpers__/keep_alive_component_helper_spec.js
@@ -12,10 +12,6 @@ describe('keepAlive', () => {
wrapper = mount(keepAlive(component));
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('converts a component to a keep-alive component', async () => {
const { element } = wrapper.findComponent(component);
diff --git a/spec/frontend/__helpers__/shared_test_setup.js b/spec/frontend/__helpers__/shared_test_setup.js
index 2fe9fe89a90..0217835b2a3 100644
--- a/spec/frontend/__helpers__/shared_test_setup.js
+++ b/spec/frontend/__helpers__/shared_test_setup.js
@@ -1,10 +1,12 @@
/* Common setup for both unit and integration test environments */
+import { ReadableStream, WritableStream } from 'node:stream/web';
import * as jqueryMatchers from 'custom-jquery-matchers';
import Vue from 'vue';
import { enableAutoDestroy } from '@vue/test-utils';
import 'jquery';
import Translate from '~/vue_shared/translate';
import setWindowLocation from './set_window_location_helper';
+import { createGon } from './gon_helper';
import { setGlobalDateToFakeDate } from './fake_date';
import { TEST_HOST } from './test_constants';
import * as customMatchers from './matchers';
@@ -13,6 +15,9 @@ import './dom_shims';
import './jquery';
import '~/commons/bootstrap';
+global.ReadableStream = ReadableStream;
+global.WritableStream = WritableStream;
+
enableAutoDestroy(afterEach);
// This module has some fairly decent visual test coverage in it's own repository.
@@ -67,8 +72,13 @@ beforeEach(() => {
// eslint-disable-next-line jest/no-standalone-expect
expect.hasAssertions();
- // Reset the mocked window.location. This ensures tests don't interfere with
- // each other, and removes the need to tidy up if it was changed for a given
- // test.
+ // Reset globals: This ensures tests don't interfere with
+ // each other, and removes the need to tidy up if it was
+ // changed for a given test.
+
+ // Reset the mocked window.location
setWindowLocation(TEST_HOST);
+
+ // Reset window.gon object
+ window.gon = createGon(window.IS_EE);
});
diff --git a/spec/frontend/__helpers__/vue_mock_directive.js b/spec/frontend/__helpers__/vue_mock_directive.js
index e952f258c4d..e7a2aa7f10d 100644
--- a/spec/frontend/__helpers__/vue_mock_directive.js
+++ b/spec/frontend/__helpers__/vue_mock_directive.js
@@ -2,7 +2,7 @@ export const getKey = (name) => `$_gl_jest_${name}`;
export const getBinding = (el, name) => el[getKey(name)];
-const writeBindingToElement = (el, { name, value, arg, modifiers }) => {
+const writeBindingToElement = (el, name, { value, arg, modifiers }) => {
el[getKey(name)] = {
value,
arg,
@@ -10,16 +10,24 @@ const writeBindingToElement = (el, { name, value, arg, modifiers }) => {
};
};
-export const createMockDirective = () => ({
- bind(el, binding) {
- writeBindingToElement(el, binding);
- },
+export const createMockDirective = (name) => {
+ if (!name) {
+ throw new Error(
+ 'Vue 3 no longer passes the name of the directive to its hooks, an explicit name is required',
+ );
+ }
- update(el, binding) {
- writeBindingToElement(el, binding);
- },
+ return {
+ bind(el, binding) {
+ writeBindingToElement(el, name, binding);
+ },
- unbind(el, { name }) {
- delete el[getKey(name)];
- },
-});
+ update(el, binding) {
+ writeBindingToElement(el, name, binding);
+ },
+
+ unbind(el) {
+ delete el[getKey(name)];
+ },
+ };
+};
diff --git a/spec/frontend/__helpers__/vuex_action_helper.js b/spec/frontend/__helpers__/vuex_action_helper.js
index bdd5a0a9034..94164814879 100644
--- a/spec/frontend/__helpers__/vuex_action_helper.js
+++ b/spec/frontend/__helpers__/vuex_action_helper.js
@@ -78,6 +78,8 @@ export default (
}
actions.push(dispatchedAction);
+
+ return Promise.resolve();
};
const validateResults = () => {
diff --git a/spec/frontend/__helpers__/vuex_action_helper_spec.js b/spec/frontend/__helpers__/vuex_action_helper_spec.js
index 4bd21ff150a..64081ca11a3 100644
--- a/spec/frontend/__helpers__/vuex_action_helper_spec.js
+++ b/spec/frontend/__helpers__/vuex_action_helper_spec.js
@@ -83,6 +83,20 @@ describe.each([testActionFn, testActionFnWithOptionsArg])(
});
});
+ describe('given an async action (chaining off a dispatch)', () => {
+ it('mocks dispatch accurately', () => {
+ const asyncAction = ({ commit, dispatch }) => {
+ return dispatch('ACTION').then(() => {
+ commit('MUTATION');
+ });
+ };
+
+ assertion = { actions: [{ type: 'ACTION' }], mutations: [{ type: 'MUTATION' }] };
+
+ return testAction(asyncAction, null, {}, assertion.mutations, assertion.actions);
+ });
+ });
+
describe('given an async action (returning a promise)', () => {
const data = { FOO: 'BAR' };
diff --git a/spec/frontend/__mocks__/@gitlab/ui.js b/spec/frontend/__mocks__/@gitlab/ui.js
index 4d893bcd0bd..c51f37db384 100644
--- a/spec/frontend/__mocks__/@gitlab/ui.js
+++ b/spec/frontend/__mocks__/@gitlab/ui.js
@@ -13,13 +13,18 @@ export * from '@gitlab/ui';
* are imported internally in `@gitlab/ui`.
*/
-jest.mock('@gitlab/ui/dist/directives/tooltip.js', () => ({
+/* eslint-disable global-require */
+
+jest.mock('@gitlab/ui/src/directives/tooltip.js', () => ({
GlTooltipDirective: {
bind() {},
},
}));
+jest.mock('@gitlab/ui/dist/directives/tooltip.js', () =>
+ require('@gitlab/ui/src/directives/tooltip'),
+);
-jest.mock('@gitlab/ui/dist/components/base/tooltip/tooltip.js', () => ({
+jest.mock('@gitlab/ui/src/components/base/tooltip/tooltip.vue', () => ({
props: ['target', 'id', 'triggers', 'placement', 'container', 'boundary', 'disabled', 'show'],
render(h) {
return h(
@@ -33,7 +38,11 @@ jest.mock('@gitlab/ui/dist/components/base/tooltip/tooltip.js', () => ({
},
}));
-jest.mock('@gitlab/ui/dist/components/base/popover/popover.js', () => ({
+jest.mock('@gitlab/ui/dist/components/base/tooltip/tooltip.js', () =>
+ require('@gitlab/ui/src/components/base/tooltip/tooltip.vue'),
+);
+
+jest.mock('@gitlab/ui/src/components/base/popover/popover.vue', () => ({
props: {
cssClasses: {
type: Array,
@@ -65,3 +74,6 @@ jest.mock('@gitlab/ui/dist/components/base/popover/popover.js', () => ({
);
},
}));
+jest.mock('@gitlab/ui/dist/components/base/popover/popover.js', () =>
+ require('@gitlab/ui/src/components/base/popover/popover.vue'),
+);
diff --git a/spec/frontend/__mocks__/lodash/debounce.js b/spec/frontend/__mocks__/lodash/debounce.js
index d4fe2ce5406..15f806fc31a 100644
--- a/spec/frontend/__mocks__/lodash/debounce.js
+++ b/spec/frontend/__mocks__/lodash/debounce.js
@@ -9,9 +9,22 @@
// Further reference: https://github.com/facebook/jest/issues/3465
export default (fn) => {
- const debouncedFn = jest.fn().mockImplementation(fn);
- debouncedFn.cancel = jest.fn();
- debouncedFn.flush = jest.fn().mockImplementation(() => {
+ let id;
+ const debouncedFn = jest.fn(function run(...args) {
+ // this is calculated in runtime so beforeAll hook works in tests
+ const timeout = global.JEST_DEBOUNCE_THROTTLE_TIMEOUT;
+ if (timeout) {
+ id = setTimeout(() => {
+ fn.apply(this, args);
+ }, timeout);
+ } else {
+ fn.apply(this, args);
+ }
+ });
+ debouncedFn.cancel = jest.fn(() => {
+ clearTimeout(id);
+ });
+ debouncedFn.flush = jest.fn(() => {
const errorMessage =
"The .flush() method returned by lodash.debounce is not yet implemented/mocked by the mock in 'spec/frontend/__mocks__/lodash/debounce.js'.";
diff --git a/spec/frontend/__mocks__/lodash/throttle.js b/spec/frontend/__mocks__/lodash/throttle.js
index e8a82654c78..b1014662918 100644
--- a/spec/frontend/__mocks__/lodash/throttle.js
+++ b/spec/frontend/__mocks__/lodash/throttle.js
@@ -1,4 +1,4 @@
// Similar to `lodash/debounce`, `lodash/throttle` also causes flaky specs.
// See `./debounce.js` for more details.
-export default (fn) => fn;
+export { default } from './debounce';
diff --git a/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js b/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js
index ec20088c443..5de5f495f01 100644
--- a/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js
+++ b/spec/frontend/abuse_reports/components/abuse_category_selector_spec.js
@@ -33,10 +33,6 @@ describe('AbuseCategorySelector', () => {
createComponent({ showDrawer: true });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findDrawer = () => wrapper.findComponent(GlDrawer);
const findTitle = () => wrapper.findByTestId('category-drawer-title');
diff --git a/spec/frontend/access_tokens/components/expires_at_field_spec.js b/spec/frontend/access_tokens/components/expires_at_field_spec.js
index 491d2a0e323..6605faadc17 100644
--- a/spec/frontend/access_tokens/components/expires_at_field_spec.js
+++ b/spec/frontend/access_tokens/components/expires_at_field_spec.js
@@ -25,10 +25,6 @@ describe('~/access_tokens/components/expires_at_field', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render datepicker with input info', () => {
createComponent();
diff --git a/spec/frontend/access_tokens/components/new_access_token_app_spec.js b/spec/frontend/access_tokens/components/new_access_token_app_spec.js
index e4313bdfa26..fb92cc34ce9 100644
--- a/spec/frontend/access_tokens/components/new_access_token_app_spec.js
+++ b/spec/frontend/access_tokens/components/new_access_token_app_spec.js
@@ -4,12 +4,12 @@ import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import NewAccessTokenApp from '~/access_tokens/components/new_access_token_app.vue';
import { EVENT_ERROR, EVENT_SUCCESS, FORM_SELECTOR } from '~/access_tokens/components/constants';
-import { createAlert, VARIANT_INFO } from '~/flash';
+import { createAlert, VARIANT_INFO } from '~/alert';
import { __, sprintf } from '~/locale';
import DomElementListener from '~/vue_shared/components/dom_element_listener.vue';
import InputCopyToggleVisibility from '~/vue_shared/components/form/input_copy_toggle_visibility.vue';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('~/access_tokens/components/new_access_token_app', () => {
let wrapper;
@@ -52,7 +52,6 @@ describe('~/access_tokens/components/new_access_token_app', () => {
afterEach(() => {
resetHTMLFixture();
- wrapper.destroy();
createAlert.mockClear();
});
diff --git a/spec/frontend/access_tokens/components/token_spec.js b/spec/frontend/access_tokens/components/token_spec.js
index 1af21aaa8cd..f62f7d72e3b 100644
--- a/spec/frontend/access_tokens/components/token_spec.js
+++ b/spec/frontend/access_tokens/components/token_spec.js
@@ -23,10 +23,6 @@ describe('Token', () => {
wrapper = mountExtended(Token, { propsData: defaultPropsData, slots: defaultSlots });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders title slot', () => {
createComponent();
diff --git a/spec/frontend/access_tokens/components/tokens_app_spec.js b/spec/frontend/access_tokens/components/tokens_app_spec.js
index d7acfbb47eb..6e7dee6a2cc 100644
--- a/spec/frontend/access_tokens/components/tokens_app_spec.js
+++ b/spec/frontend/access_tokens/components/tokens_app_spec.js
@@ -54,10 +54,6 @@ describe('TokensApp', () => {
expect(container.props()).toMatchObject(expectedProps);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders all enabled tokens', () => {
createComponent();
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 1d57473943b..5e96da9af7e 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
@@ -55,10 +55,6 @@ describe('AddContextCommitsModal', () => {
wrapper = createWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders modal with 2 tabs', () => {
expect(wrapper.element).toMatchSnapshot();
});
diff --git a/spec/frontend/add_context_commits_modal/components/review_tab_container_spec.js b/spec/frontend/add_context_commits_modal/components/review_tab_container_spec.js
index f679576182f..975f115c4bb 100644
--- a/spec/frontend/add_context_commits_modal/components/review_tab_container_spec.js
+++ b/spec/frontend/add_context_commits_modal/components/review_tab_container_spec.js
@@ -26,10 +26,6 @@ describe('ReviewTabContainer', () => {
createWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('shows loading icon when commits are being loaded', () => {
createWrapper({ isLoading: true });
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
diff --git a/spec/frontend/add_context_commits_modal/store/actions_spec.js b/spec/frontend/add_context_commits_modal/store/actions_spec.js
index 27c8d760a96..3863eee3795 100644
--- a/spec/frontend/add_context_commits_modal/store/actions_spec.js
+++ b/spec/frontend/add_context_commits_modal/store/actions_spec.js
@@ -31,10 +31,10 @@ describe('AddContextCommitsModalStoreActions', () => {
short_id: 'abcdef',
committed_date: '2020-06-12',
};
- gon.api_version = 'v4';
let mock;
beforeEach(() => {
+ gon.api_version = 'v4';
mock = new MockAdapter(axios);
});
diff --git a/spec/frontend/admin/abuse_reports/components/abuse_report_row_spec.js b/spec/frontend/admin/abuse_reports/components/abuse_report_row_spec.js
new file mode 100644
index 00000000000..d32fa25d238
--- /dev/null
+++ b/spec/frontend/admin/abuse_reports/components/abuse_report_row_spec.js
@@ -0,0 +1,43 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import AbuseReportRow from '~/admin/abuse_reports/components/abuse_report_row.vue';
+import ListItem from '~/vue_shared/components/registry/list_item.vue';
+import { getTimeago } from '~/lib/utils/datetime_utility';
+import { mockAbuseReports } from '../mock_data';
+
+describe('AbuseReportRow', () => {
+ let wrapper;
+ const mockAbuseReport = mockAbuseReports[0];
+
+ const findListItem = () => wrapper.findComponent(ListItem);
+ const findTitle = () => wrapper.findByTestId('title');
+ const findUpdatedAt = () => wrapper.findByTestId('updated-at');
+
+ const createComponent = () => {
+ wrapper = shallowMountExtended(AbuseReportRow, {
+ propsData: {
+ report: mockAbuseReport,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders a ListItem', () => {
+ expect(findListItem().exists()).toBe(true);
+ });
+
+ it('displays correctly formatted title', () => {
+ const { reporter, reportedUser, category } = mockAbuseReport;
+ expect(findTitle().text()).toMatchInterpolatedText(
+ `${reportedUser.name} reported for ${category} by ${reporter.name}`,
+ );
+ });
+
+ it('displays correctly formatted updated at', () => {
+ expect(findUpdatedAt().text()).toMatchInterpolatedText(
+ `Updated ${getTimeago().format(mockAbuseReport.updatedAt)}`,
+ );
+ });
+});
diff --git a/spec/frontend/admin/abuse_reports/components/abuse_reports_filtered_search_bar_spec.js b/spec/frontend/admin/abuse_reports/components/abuse_reports_filtered_search_bar_spec.js
new file mode 100644
index 00000000000..9efab8403a0
--- /dev/null
+++ b/spec/frontend/admin/abuse_reports/components/abuse_reports_filtered_search_bar_spec.js
@@ -0,0 +1,214 @@
+import { shallowMount } from '@vue/test-utils';
+import setWindowLocation from 'helpers/set_window_location_helper';
+import { redirectTo, updateHistory } from '~/lib/utils/url_utility';
+import AbuseReportsFilteredSearchBar from '~/admin/abuse_reports/components/abuse_reports_filtered_search_bar.vue';
+import {
+ FILTERED_SEARCH_TOKENS,
+ FILTERED_SEARCH_TOKEN_USER,
+ FILTERED_SEARCH_TOKEN_REPORTER,
+ FILTERED_SEARCH_TOKEN_STATUS,
+ FILTERED_SEARCH_TOKEN_CATEGORY,
+ DEFAULT_SORT,
+ SORT_OPTIONS,
+} from '~/admin/abuse_reports/constants';
+import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
+import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
+import { buildFilteredSearchCategoryToken } from '~/admin/abuse_reports/utils';
+
+jest.mock('~/lib/utils/url_utility', () => {
+ const urlUtility = jest.requireActual('~/lib/utils/url_utility');
+
+ return {
+ __esModule: true,
+ ...urlUtility,
+ redirectTo: jest.fn(),
+ updateHistory: jest.fn(),
+ };
+});
+
+describe('AbuseReportsFilteredSearchBar', () => {
+ let wrapper;
+
+ const CATEGORIES = ['spam', 'phishing'];
+
+ const createComponent = () => {
+ wrapper = shallowMount(AbuseReportsFilteredSearchBar, {
+ provide: { categories: CATEGORIES },
+ });
+ };
+
+ const findFilteredSearchBar = () => wrapper.findComponent(FilteredSearchBar);
+
+ beforeEach(() => {
+ setWindowLocation('https://localhost');
+ });
+
+ it('passes correct props to `FilteredSearchBar` component', () => {
+ createComponent();
+
+ const categoryToken = buildFilteredSearchCategoryToken(CATEGORIES);
+
+ expect(findFilteredSearchBar().props()).toMatchObject({
+ namespace: 'abuse_reports',
+ recentSearchesStorageKey: 'abuse_reports',
+ searchInputPlaceholder: 'Filter reports',
+ tokens: [...FILTERED_SEARCH_TOKENS, categoryToken],
+ initialSortBy: DEFAULT_SORT,
+ sortOptions: SORT_OPTIONS,
+ });
+ });
+
+ it('sets status=open query when there is no initial status query', () => {
+ createComponent();
+
+ expect(updateHistory).toHaveBeenCalledWith({
+ url: 'https://localhost/?status=open',
+ replace: true,
+ });
+
+ expect(findFilteredSearchBar().props('initialFilterValue')).toMatchObject([
+ {
+ type: FILTERED_SEARCH_TOKEN_STATUS.type,
+ value: { data: 'open', operator: '=' },
+ },
+ ]);
+ });
+
+ it('parses and passes search param to `FilteredSearchBar` component as `initialFilterValue` prop', () => {
+ setWindowLocation('?status=closed&user=mr_abuser&reporter=ms_nitch');
+
+ createComponent();
+
+ expect(findFilteredSearchBar().props('initialFilterValue')).toMatchObject([
+ {
+ type: FILTERED_SEARCH_TOKEN_USER.type,
+ value: { data: 'mr_abuser', operator: '=' },
+ },
+ {
+ type: FILTERED_SEARCH_TOKEN_REPORTER.type,
+ value: { data: 'ms_nitch', operator: '=' },
+ },
+ {
+ type: FILTERED_SEARCH_TOKEN_STATUS.type,
+ value: { data: 'closed', operator: '=' },
+ },
+ ]);
+ });
+
+ describe('initial sort', () => {
+ it.each(
+ SORT_OPTIONS.flatMap(({ sortDirection: { descending, ascending } }) => [
+ descending,
+ ascending,
+ ]),
+ )(
+ 'parses sort=%s query and passes it to `FilteredSearchBar` component as initialSortBy',
+ (sortBy) => {
+ setWindowLocation(`?sort=${sortBy}`);
+
+ createComponent();
+
+ expect(findFilteredSearchBar().props('initialSortBy')).toEqual(sortBy);
+ },
+ );
+
+ it(`uses ${DEFAULT_SORT} as initialSortBy when sort query param is invalid`, () => {
+ setWindowLocation(`?sort=unknown`);
+
+ createComponent();
+
+ expect(findFilteredSearchBar().props('initialSortBy')).toEqual(DEFAULT_SORT);
+ });
+ });
+
+ describe('onFilter', () => {
+ const USER_FILTER_TOKEN = {
+ type: FILTERED_SEARCH_TOKEN_USER.type,
+ value: { data: 'mr_abuser', operator: '=' },
+ };
+ const REPORTER_FILTER_TOKEN = {
+ type: FILTERED_SEARCH_TOKEN_REPORTER.type,
+ value: { data: 'ms_nitch', operator: '=' },
+ };
+ const STATUS_FILTER_TOKEN = {
+ type: FILTERED_SEARCH_TOKEN_STATUS.type,
+ value: { data: 'open', operator: '=' },
+ };
+ const CATEGORY_FILTER_TOKEN = {
+ type: FILTERED_SEARCH_TOKEN_CATEGORY.type,
+ value: { data: 'spam', operator: '=' },
+ };
+
+ const createComponentAndFilter = (filterTokens, initialLocation) => {
+ if (initialLocation) {
+ setWindowLocation(initialLocation);
+ }
+
+ createComponent();
+
+ findFilteredSearchBar().vm.$emit('onFilter', filterTokens);
+ };
+
+ it.each([USER_FILTER_TOKEN, REPORTER_FILTER_TOKEN, STATUS_FILTER_TOKEN, CATEGORY_FILTER_TOKEN])(
+ 'redirects with $type query param',
+ (filterToken) => {
+ createComponentAndFilter([filterToken]);
+ const { type, value } = filterToken;
+ expect(redirectTo).toHaveBeenCalledWith(`https://localhost/?${type}=${value.data}`);
+ },
+ );
+
+ it('ignores search query param', () => {
+ const searchFilterToken = { type: FILTERED_SEARCH_TERM, value: { data: 'ignored' } };
+ createComponentAndFilter([USER_FILTER_TOKEN, searchFilterToken]);
+ expect(redirectTo).toHaveBeenCalledWith('https://localhost/?user=mr_abuser');
+ });
+
+ it('redirects without page query param', () => {
+ createComponentAndFilter([USER_FILTER_TOKEN], '?page=2');
+ expect(redirectTo).toHaveBeenCalledWith('https://localhost/?user=mr_abuser');
+ });
+
+ it('redirects with existing sort query param', () => {
+ createComponentAndFilter([USER_FILTER_TOKEN], `?sort=${DEFAULT_SORT}`);
+ expect(redirectTo).toHaveBeenCalledWith(
+ `https://localhost/?user=mr_abuser&sort=${DEFAULT_SORT}`,
+ );
+ });
+ });
+
+ describe('onSort', () => {
+ const SORT_VALUE = 'updated_at_asc';
+ const EXISTING_QUERY = 'status=closed&user=mr_abuser';
+
+ const createComponentAndSort = (initialLocation) => {
+ setWindowLocation(initialLocation);
+ createComponent();
+ findFilteredSearchBar().vm.$emit('onSort', SORT_VALUE);
+ };
+
+ it('redirects to URL with existing query params and the sort query param', () => {
+ createComponentAndSort(`?${EXISTING_QUERY}`);
+
+ expect(redirectTo).toHaveBeenCalledWith(
+ `https://localhost/?${EXISTING_QUERY}&sort=${SORT_VALUE}`,
+ );
+ });
+
+ it('redirects without page query param', () => {
+ createComponentAndSort(`?${EXISTING_QUERY}&page=2`);
+
+ expect(redirectTo).toHaveBeenCalledWith(
+ `https://localhost/?${EXISTING_QUERY}&sort=${SORT_VALUE}`,
+ );
+ });
+
+ it('redirects with existing sort query param replaced with the new one', () => {
+ createComponentAndSort(`?${EXISTING_QUERY}&sort=created_at_desc`);
+
+ expect(redirectTo).toHaveBeenCalledWith(
+ `https://localhost/?${EXISTING_QUERY}&sort=${SORT_VALUE}`,
+ );
+ });
+ });
+});
diff --git a/spec/frontend/admin/abuse_reports/components/app_spec.js b/spec/frontend/admin/abuse_reports/components/app_spec.js
new file mode 100644
index 00000000000..41728baaf33
--- /dev/null
+++ b/spec/frontend/admin/abuse_reports/components/app_spec.js
@@ -0,0 +1,104 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlEmptyState, GlPagination } from '@gitlab/ui';
+import { queryToObject, objectToQuery } from '~/lib/utils/url_utility';
+import setWindowLocation from 'helpers/set_window_location_helper';
+import AbuseReportsApp from '~/admin/abuse_reports/components/app.vue';
+import AbuseReportsFilteredSearchBar from '~/admin/abuse_reports/components/abuse_reports_filtered_search_bar.vue';
+import AbuseReportRow from '~/admin/abuse_reports/components/abuse_report_row.vue';
+import { mockAbuseReports } from '../mock_data';
+
+describe('AbuseReportsApp', () => {
+ let wrapper;
+
+ const findFilteredSearchBar = () => wrapper.findComponent(AbuseReportsFilteredSearchBar);
+ const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+ const findAbuseReportRows = () => wrapper.findAllComponents(AbuseReportRow);
+ const findPagination = () => wrapper.findComponent(GlPagination);
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(AbuseReportsApp, {
+ propsData: {
+ abuseReports: mockAbuseReports,
+ pagination: { currentPage: 1, perPage: 20, totalItems: mockAbuseReports.length },
+ ...props,
+ },
+ });
+ };
+
+ it('renders AbuseReportsFilteredSearchBar', () => {
+ createComponent();
+
+ expect(findFilteredSearchBar().exists()).toBe(true);
+ });
+
+ it('renders one AbuseReportRow for each abuse report', () => {
+ createComponent();
+
+ expect(findEmptyState().exists()).toBe(false);
+ expect(findAbuseReportRows().length).toBe(mockAbuseReports.length);
+ });
+
+ it('renders empty state when there are no reports', () => {
+ createComponent({
+ abuseReports: [],
+ pagination: { currentPage: 1, perPage: 20, totalItems: 0 },
+ });
+
+ expect(findEmptyState().exists()).toBe(true);
+ });
+
+ describe('pagination', () => {
+ const pagination = {
+ currentPage: 1,
+ perPage: 1,
+ totalItems: mockAbuseReports.length,
+ };
+
+ it('renders GlPagination with the correct props when needed', () => {
+ createComponent({ pagination });
+
+ expect(findPagination().exists()).toBe(true);
+ expect(findPagination().props()).toMatchObject({
+ value: pagination.currentPage,
+ perPage: pagination.perPage,
+ totalItems: pagination.totalItems,
+ prevText: 'Prev',
+ nextText: 'Next',
+ labelNextPage: 'Go to next page',
+ labelPrevPage: 'Go to previous page',
+ align: 'center',
+ });
+ });
+
+ it('does not render GlPagination when not needed', () => {
+ createComponent({ pagination: { currentPage: 1, perPage: 2, totalItems: 2 } });
+
+ expect(findPagination().exists()).toBe(false);
+ });
+
+ describe('linkGen prop', () => {
+ const existingQuery = {
+ user: 'mr_okay',
+ status: 'closed',
+ };
+ const expectedGeneratedQuery = {
+ ...existingQuery,
+ page: '2',
+ };
+
+ beforeEach(() => {
+ setWindowLocation(`https://localhost?${objectToQuery(existingQuery)}`);
+ });
+
+ it('generates the correct page URL', () => {
+ createComponent({ pagination });
+
+ const linkGen = findPagination().props('linkGen');
+ const generatedUrl = linkGen(expectedGeneratedQuery.page);
+ const [, generatedQuery] = generatedUrl.split('?');
+
+ expect(queryToObject(generatedQuery)).toMatchObject(expectedGeneratedQuery);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/admin/abuse_reports/mock_data.js b/spec/frontend/admin/abuse_reports/mock_data.js
new file mode 100644
index 00000000000..778f055eb82
--- /dev/null
+++ b/spec/frontend/admin/abuse_reports/mock_data.js
@@ -0,0 +1,14 @@
+export const mockAbuseReports = [
+ {
+ category: 'spam',
+ updatedAt: '2022-12-07T06:45:39.977Z',
+ reporter: { name: 'Ms. Admin' },
+ reportedUser: { name: 'Mr. Abuser' },
+ },
+ {
+ category: 'phishing',
+ updatedAt: '2022-12-07T06:45:39.977Z',
+ reporter: { name: 'Ms. Reporter' },
+ reportedUser: { name: 'Mr. Phisher' },
+ },
+];
diff --git a/spec/frontend/admin/abuse_reports/utils_spec.js b/spec/frontend/admin/abuse_reports/utils_spec.js
new file mode 100644
index 00000000000..17f0b9acb26
--- /dev/null
+++ b/spec/frontend/admin/abuse_reports/utils_spec.js
@@ -0,0 +1,13 @@
+import { FILTERED_SEARCH_TOKEN_CATEGORY } from '~/admin/abuse_reports/constants';
+import { buildFilteredSearchCategoryToken } from '~/admin/abuse_reports/utils';
+
+describe('buildFilteredSearchCategoryToken', () => {
+ it('adds correctly formatted options to FILTERED_SEARCH_TOKEN_CATEGORY', () => {
+ const categories = ['tuxedo', 'tabby'];
+
+ expect(buildFilteredSearchCategoryToken(categories)).toMatchObject({
+ ...FILTERED_SEARCH_TOKEN_CATEGORY,
+ options: categories.map((c) => ({ value: c, title: c })),
+ });
+ });
+});
diff --git a/spec/frontend/admin/analytics/devops_score/components/devops_score_callout_spec.js b/spec/frontend/admin/analytics/devops_score/components/devops_score_callout_spec.js
index c9a899ab78b..06f9ffeffcd 100644
--- a/spec/frontend/admin/analytics/devops_score/components/devops_score_callout_spec.js
+++ b/spec/frontend/admin/analytics/devops_score/components/devops_score_callout_spec.js
@@ -19,10 +19,6 @@ describe('DevopsScoreCallout', () => {
const findBanner = () => wrapper.findComponent(GlBanner);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with no cookie set', () => {
beforeEach(() => {
utils.setCookie = jest.fn();
diff --git a/spec/frontend/admin/application_settings/inactive_project_deletion/components/form_spec.js b/spec/frontend/admin/application_settings/inactive_project_deletion/components/form_spec.js
index 2db997942a7..969844f981c 100644
--- a/spec/frontend/admin/application_settings/inactive_project_deletion/components/form_spec.js
+++ b/spec/frontend/admin/application_settings/inactive_project_deletion/components/form_spec.js
@@ -29,10 +29,6 @@ describe('Form component', () => {
wrapper = mountFn(SettingsForm, { propsData });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Enable inactive project deletion', () => {
it('has the checkbox', () => {
createComponent();
diff --git a/spec/frontend/admin/application_settings/network_outbound_spec.js b/spec/frontend/admin/application_settings/network_outbound_spec.js
new file mode 100644
index 00000000000..2c06a3fd67f
--- /dev/null
+++ b/spec/frontend/admin/application_settings/network_outbound_spec.js
@@ -0,0 +1,70 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
+
+import initNetworkOutbound from '~/admin/application_settings/network_outbound';
+
+describe('initNetworkOutbound', () => {
+ const findAllowCheckboxes = () => document.querySelectorAll('.js-allow-local-requests');
+ const findDenyCheckbox = () => document.querySelector('.js-deny-all-requests');
+ const findWarningBanner = () => document.querySelector('.js-deny-all-requests-warning');
+ const clickDenyCheckbox = () => {
+ findDenyCheckbox().click();
+ };
+
+ const createFixture = (denyAll = false) => {
+ setHTMLFixture(`
+ <input class="js-deny-all-requests" type="checkbox" name="application_setting[deny_all_requests_except_allowed]" ${
+ denyAll ? 'checked="checked"' : ''
+ }/>
+ <div class="js-deny-all-requests-warning ${denyAll ? '' : 'gl-display-none'}"></div>
+ <input class="js-allow-local-requests" type="checkbox" name="application_setting[allow_local_requests_from_web_hooks_and_services]" />
+ <input class="js-allow-local-requests" type="checkbox" name="application_setting[allow_local_requests_from_system_hooks]" />
+ `);
+ };
+
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
+ describe('when the checkbox is not checked', () => {
+ beforeEach(() => {
+ createFixture();
+ initNetworkOutbound();
+ });
+
+ it('shows banner and disables allow checkboxes on change', () => {
+ expect(findDenyCheckbox().checked).toBe(false);
+ expect(findWarningBanner().classList).toContain('gl-display-none');
+
+ clickDenyCheckbox();
+
+ expect(findDenyCheckbox().checked).toBe(true);
+ expect(findWarningBanner().classList).not.toContain('gl-display-none');
+ const allowCheckboxes = findAllowCheckboxes();
+ allowCheckboxes.forEach((checkbox) => {
+ expect(checkbox.checked).toBe(false);
+ expect(checkbox.disabled).toBe(true);
+ });
+ });
+ });
+
+ describe('when the checkbox is checked', () => {
+ beforeEach(() => {
+ createFixture(true);
+ initNetworkOutbound();
+ });
+
+ it('hides banner and enables allow checkboxes on change', () => {
+ expect(findDenyCheckbox().checked).toBe(true);
+ expect(findWarningBanner().classList).not.toContain('gl-display-none');
+
+ clickDenyCheckbox();
+
+ expect(findDenyCheckbox().checked).toBe(false);
+ expect(findWarningBanner().classList).toContain('gl-display-none');
+ const allowCheckboxes = findAllowCheckboxes();
+ allowCheckboxes.forEach((checkbox) => {
+ expect(checkbox.disabled).toBe(false);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/admin/applications/components/delete_application_spec.js b/spec/frontend/admin/applications/components/delete_application_spec.js
index 1a400a101b5..315c38a2bbc 100644
--- a/spec/frontend/admin/applications/components/delete_application_spec.js
+++ b/spec/frontend/admin/applications/components/delete_application_spec.js
@@ -31,7 +31,6 @@ describe('DeleteApplication', () => {
});
afterEach(() => {
- wrapper.destroy();
resetHTMLFixture();
});
diff --git a/spec/frontend/admin/background_migrations/components/database_listbox_spec.js b/spec/frontend/admin/background_migrations/components/database_listbox_spec.js
index 212f4c0842c..d7b319a3d5e 100644
--- a/spec/frontend/admin/background_migrations/components/database_listbox_spec.js
+++ b/spec/frontend/admin/background_migrations/components/database_listbox_spec.js
@@ -26,10 +26,6 @@ describe('BackgroundMigrationsDatabaseListbox', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGlCollapsibleListbox = () => wrapper.findComponent(GlCollapsibleListbox);
describe('template always', () => {
diff --git a/spec/frontend/admin/broadcast_messages/components/base_spec.js b/spec/frontend/admin/broadcast_messages/components/base_spec.js
index d69bf4a22bf..50d8eeb563d 100644
--- a/spec/frontend/admin/broadcast_messages/components/base_spec.js
+++ b/spec/frontend/admin/broadcast_messages/components/base_spec.js
@@ -4,7 +4,7 @@ import AxiosMockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { redirectTo } from '~/lib/utils/url_utility';
@@ -12,7 +12,7 @@ import BroadcastMessagesBase from '~/admin/broadcast_messages/components/base.vu
import MessagesTable from '~/admin/broadcast_messages/components/messages_table.vue';
import { generateMockMessages, MOCK_MESSAGES } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility');
describe('BroadcastMessagesBase', () => {
@@ -41,7 +41,6 @@ describe('BroadcastMessagesBase', () => {
afterEach(() => {
axiosMock.restore();
- wrapper.destroy();
});
it('renders the table and pagination when there are existing messages', () => {
diff --git a/spec/frontend/admin/broadcast_messages/components/message_form_spec.js b/spec/frontend/admin/broadcast_messages/components/message_form_spec.js
index 36c0ac303ba..292575c984b 100644
--- a/spec/frontend/admin/broadcast_messages/components/message_form_spec.js
+++ b/spec/frontend/admin/broadcast_messages/components/message_form_spec.js
@@ -1,7 +1,7 @@
import { mount } from '@vue/test-utils';
import { GlBroadcastMessage, GlForm } from '@gitlab/ui';
import AxiosMockAdapter from 'axios-mock-adapter';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_BAD_REQUEST } from '~/lib/utils/http_status';
import MessageForm from '~/admin/broadcast_messages/components/message_form.vue';
@@ -15,7 +15,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { MOCK_TARGET_ACCESS_LEVELS } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('MessageForm', () => {
let wrapper;
diff --git a/spec/frontend/admin/broadcast_messages/components/messages_table_spec.js b/spec/frontend/admin/broadcast_messages/components/messages_table_spec.js
index 349fab03853..432bfefeb18 100644
--- a/spec/frontend/admin/broadcast_messages/components/messages_table_spec.js
+++ b/spec/frontend/admin/broadcast_messages/components/messages_table_spec.js
@@ -21,10 +21,6 @@ describe('MessagesTable', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a table row for each message', () => {
createComponent();
diff --git a/spec/frontend/admin/deploy_keys/components/table_spec.js b/spec/frontend/admin/deploy_keys/components/table_spec.js
index 4d4a2caedde..a05654a1d25 100644
--- a/spec/frontend/admin/deploy_keys/components/table_spec.js
+++ b/spec/frontend/admin/deploy_keys/components/table_spec.js
@@ -9,10 +9,10 @@ import { stubComponent } from 'helpers/stub_component';
import DeployKeysTable from '~/admin/deploy_keys/components/table.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import Api, { DEFAULT_PER_PAGE } from '~/api';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
jest.mock('~/api');
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
describe('DeployKeysTable', () => {
@@ -91,10 +91,6 @@ describe('DeployKeysTable', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders page title', () => {
createComponent();
@@ -242,7 +238,7 @@ describe('DeployKeysTable', () => {
itRendersTheEmptyState();
- it('displays flash', () => {
+ it('displays alert', () => {
expect(createAlert).toHaveBeenCalledWith({
message: DeployKeysTable.i18n.apiErrorMessage,
captureError: true,
diff --git a/spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js b/spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js
index eecc21e206b..9e55716cc30 100644
--- a/spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js
+++ b/spec/frontend/admin/signup_restrictions/components/signup_checkbox_spec.js
@@ -28,10 +28,6 @@ describe('Signup Form', () => {
const findCheckboxLabel = () => findByTestId('label');
const findHelpText = () => findByTestId('helpText');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Signup Checkbox', () => {
beforeEach(() => {
mountComponent();
diff --git a/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js b/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js
index f2a951bcc76..9192fc12401 100644
--- a/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js
+++ b/spec/frontend/admin/signup_restrictions/components/signup_form_spec.js
@@ -40,8 +40,6 @@ describe('Signup Form', () => {
const findModal = () => wrapper.findComponent(GlModal);
afterEach(() => {
- wrapper.destroy();
-
formSubmitSpy = null;
});
diff --git a/spec/frontend/admin/statistics_panel/components/app_spec.js b/spec/frontend/admin/statistics_panel/components/app_spec.js
index 4c362a31068..60e46cddd7e 100644
--- a/spec/frontend/admin/statistics_panel/components/app_spec.js
+++ b/spec/frontend/admin/statistics_panel/components/app_spec.js
@@ -30,10 +30,6 @@ describe('Admin statistics app', () => {
store = createStore();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findStats = (idx) => wrapper.findAll('.js-stats').at(idx);
describe('template', () => {
diff --git a/spec/frontend/admin/topics/components/remove_avatar_spec.js b/spec/frontend/admin/topics/components/remove_avatar_spec.js
index 97d257c682c..c069203d046 100644
--- a/spec/frontend/admin/topics/components/remove_avatar_spec.js
+++ b/spec/frontend/admin/topics/components/remove_avatar_spec.js
@@ -20,7 +20,7 @@ describe('RemoveAvatar', () => {
name,
},
directives: {
- GlModal: createMockDirective(),
+ GlModal: createMockDirective('gl-modal'),
},
stubs: {
GlSprintf,
@@ -36,10 +36,6 @@ describe('RemoveAvatar', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('the button component', () => {
it('displays the remove button', () => {
const button = findButton();
diff --git a/spec/frontend/admin/topics/components/topic_select_spec.js b/spec/frontend/admin/topics/components/topic_select_spec.js
index 738cbd88c4c..113a0e3d404 100644
--- a/spec/frontend/admin/topics/components/topic_select_spec.js
+++ b/spec/frontend/admin/topics/components/topic_select_spec.js
@@ -59,7 +59,6 @@ describe('TopicSelect', () => {
}
afterEach(() => {
- wrapper.destroy();
jest.clearAllMocks();
});
diff --git a/spec/frontend/admin/users/components/actions/actions_spec.js b/spec/frontend/admin/users/components/actions/actions_spec.js
index 8e9652332c1..4aeaa5356b4 100644
--- a/spec/frontend/admin/users/components/actions/actions_spec.js
+++ b/spec/frontend/admin/users/components/actions/actions_spec.js
@@ -22,11 +22,6 @@ describe('Action components', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('CONFIRMATION_ACTIONS', () => {
it.each(CONFIRMATION_ACTIONS)('renders a dropdown item for "%s"', (action) => {
initComponent({
diff --git a/spec/frontend/admin/users/components/app_spec.js b/spec/frontend/admin/users/components/app_spec.js
index 913732aae42..d40089edc82 100644
--- a/spec/frontend/admin/users/components/app_spec.js
+++ b/spec/frontend/admin/users/components/app_spec.js
@@ -17,11 +17,6 @@ describe('AdminUsersApp component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when initialized', () => {
beforeEach(() => {
initComponent();
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 2e892e292d7..efb951f4ad2 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
@@ -73,11 +73,6 @@ describe('Delete user modal', () => {
formSubmitSpy = jest.spyOn(HTMLFormElement.prototype, 'submit').mockImplementation();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders modal with form included', () => {
createComponent();
expect(findForm().element).toMatchSnapshot();
diff --git a/spec/frontend/admin/users/components/user_actions_spec.js b/spec/frontend/admin/users/components/user_actions_spec.js
index 1b080b05c95..1a2cc3e5c34 100644
--- a/spec/frontend/admin/users/components/user_actions_spec.js
+++ b/spec/frontend/admin/users/components/user_actions_spec.js
@@ -32,16 +32,11 @@ describe('AdminUserActions component', () => {
showButtonLabels,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('edit button', () => {
describe('when the user has an edit action attached', () => {
beforeEach(() => {
diff --git a/spec/frontend/admin/users/components/user_avatar_spec.js b/spec/frontend/admin/users/components/user_avatar_spec.js
index 94fac875fbe..02e648d2b77 100644
--- a/spec/frontend/admin/users/components/user_avatar_spec.js
+++ b/spec/frontend/admin/users/components/user_avatar_spec.js
@@ -26,7 +26,7 @@ describe('AdminUserAvatar component', () => {
...props,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
stubs: {
GlAvatarLabeled,
@@ -34,11 +34,6 @@ describe('AdminUserAvatar component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when initialized', () => {
beforeEach(() => {
initComponent();
diff --git a/spec/frontend/admin/users/components/user_date_spec.js b/spec/frontend/admin/users/components/user_date_spec.js
index 73be33d5a9d..19c1cd38a50 100644
--- a/spec/frontend/admin/users/components/user_date_spec.js
+++ b/spec/frontend/admin/users/components/user_date_spec.js
@@ -17,11 +17,6 @@ describe('FormatDate component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it.each`
date | dateFormat | output
${mockDate} | ${undefined} | ${'Nov 13, 2020'}
diff --git a/spec/frontend/admin/users/components/users_table_spec.js b/spec/frontend/admin/users/components/users_table_spec.js
index a0aec347b6b..6f658fd2e59 100644
--- a/spec/frontend/admin/users/components/users_table_spec.js
+++ b/spec/frontend/admin/users/components/users_table_spec.js
@@ -10,12 +10,12 @@ import AdminUserActions from '~/admin/users/components/user_actions.vue';
import AdminUserAvatar from '~/admin/users/components/user_avatar.vue';
import AdminUsersTable from '~/admin/users/components/users_table.vue';
import getUsersGroupCountsQuery from '~/admin/users/graphql/queries/get_users_group_counts.query.graphql';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import AdminUserDate from '~/vue_shared/components/user_date.vue';
import { users, paths, createGroupCountResponse } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(VueApollo);
@@ -57,11 +57,6 @@ describe('AdminUsersTable component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when there are users', () => {
beforeEach(() => {
initComponent();
@@ -134,7 +129,7 @@ describe('AdminUsersTable component', () => {
await waitForPromises();
});
- it('creates a flash message and captures the error', () => {
+ it('creates an alert message and captures the error', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'Could not load user group counts. Please refresh the page to try again.',
captureError: true,
diff --git a/spec/frontend/admin/users/index_spec.js b/spec/frontend/admin/users/index_spec.js
index b51858d5129..d8a94ee5e1d 100644
--- a/spec/frontend/admin/users/index_spec.js
+++ b/spec/frontend/admin/users/index_spec.js
@@ -19,8 +19,6 @@ describe('initAdminUsersApp', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
el = null;
});
@@ -47,8 +45,6 @@ describe('initAdminUserActions', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
el = null;
});
diff --git a/spec/frontend/airflow/dags/components/dags_spec.js b/spec/frontend/airflow/dags/components/dags_spec.js
deleted file mode 100644
index f9cf4fc87af..00000000000
--- a/spec/frontend/airflow/dags/components/dags_spec.js
+++ /dev/null
@@ -1,115 +0,0 @@
-import { GlAlert, GlPagination, GlTableLite } from '@gitlab/ui';
-import { mountExtended } from 'helpers/vue_test_utils_helper';
-import { TEST_HOST } from 'helpers/test_constants';
-import AirflowDags from '~/airflow/dags/components/dags.vue';
-import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
-import { mockDags } from './mock_data';
-
-describe('AirflowDags', () => {
- let wrapper;
-
- const createWrapper = (
- dags = [],
- pagination = { page: 1, isLastPage: false, per_page: 2, totalItems: 0 },
- ) => {
- wrapper = mountExtended(AirflowDags, {
- propsData: {
- dags,
- pagination,
- },
- });
- };
-
- const findAlert = () => wrapper.findComponent(GlAlert);
- const findEmptyState = () => wrapper.findByText('There are no DAGs to show');
- const findPagination = () => wrapper.findComponent(GlPagination);
-
- describe('default (no dags)', () => {
- beforeEach(() => {
- createWrapper();
- });
-
- it('shows incubation warning', () => {
- expect(findAlert().exists()).toBe(true);
- });
-
- it('shows empty state', () => {
- expect(findEmptyState().exists()).toBe(true);
- });
-
- it('does not show pagination', () => {
- expect(findPagination().exists()).toBe(false);
- });
- });
-
- describe('with dags', () => {
- const createWrapperWithDags = (pagination = {}) => {
- createWrapper(mockDags, {
- page: 1,
- isLastPage: false,
- per_page: 2,
- totalItems: 5,
- ...pagination,
- });
- };
-
- const findDagsData = () => {
- return wrapper
- .findComponent(GlTableLite)
- .findAll('tbody tr')
- .wrappers.map((tr) => {
- return tr.findAll('td').wrappers.map((td) => {
- const timeAgo = td.findComponent(TimeAgo);
-
- if (timeAgo.exists()) {
- return {
- type: 'time',
- value: timeAgo.props('time'),
- };
- }
-
- return {
- type: 'text',
- value: td.text(),
- };
- });
- });
- };
-
- it('renders the table of Dags with data', () => {
- createWrapperWithDags();
-
- expect(findDagsData()).toEqual(
- mockDags.map((x) => [
- { type: 'text', value: x.dag_name },
- { type: 'text', value: x.schedule },
- { type: 'time', value: x.next_run },
- { type: 'text', value: String(x.is_active) },
- { type: 'text', value: String(x.is_paused) },
- { type: 'text', value: x.fileloc },
- ]),
- );
- });
-
- describe('Pagination behaviour', () => {
- it.each`
- pagination | expected
- ${{}} | ${{ value: 1, prevPage: null, nextPage: 2 }}
- ${{ page: 2 }} | ${{ value: 2, prevPage: 1, nextPage: 3 }}
- ${{ isLastPage: true, page: 2 }} | ${{ value: 2, prevPage: 1, nextPage: null }}
- `('with $pagination, sets pagination props', ({ pagination, expected }) => {
- createWrapperWithDags({ ...pagination });
-
- expect(findPagination().props()).toMatchObject(expected);
- });
-
- it('generates link for each page', () => {
- createWrapperWithDags();
-
- const generateLink = findPagination().props('linkGen');
-
- expect(generateLink(3)).toBe(`${TEST_HOST}/?page=3`);
- });
- });
- });
-});
diff --git a/spec/frontend/airflow/dags/components/mock_data.js b/spec/frontend/airflow/dags/components/mock_data.js
deleted file mode 100644
index 9547282517d..00000000000
--- a/spec/frontend/airflow/dags/components/mock_data.js
+++ /dev/null
@@ -1,67 +0,0 @@
-export const mockDags = [
- {
- id: 1,
- project_id: 7,
- created_at: '2023-01-05T14:07:02.975Z',
- updated_at: '2023-01-05T14:07:02.975Z',
- has_import_errors: false,
- is_active: false,
- is_paused: true,
- next_run: '2023-01-05T14:07:02.975Z',
- dag_name: 'Dag number 1',
- schedule: 'Manual',
- fileloc: '/opt/dag.py',
- },
- {
- id: 2,
- project_id: 7,
- created_at: '2023-01-05T14:07:02.975Z',
- updated_at: '2023-01-05T14:07:02.975Z',
- has_import_errors: false,
- is_active: false,
- is_paused: true,
- next_run: '2023-01-05T14:07:02.975Z',
- dag_name: 'Dag number 2',
- schedule: 'Manual',
- fileloc: '/opt/dag.py',
- },
- {
- id: 3,
- project_id: 7,
- created_at: '2023-01-05T14:07:02.975Z',
- updated_at: '2023-01-05T14:07:02.975Z',
- has_import_errors: false,
- is_active: false,
- is_paused: true,
- next_run: '2023-01-05T14:07:02.975Z',
- dag_name: 'Dag number 3',
- schedule: 'Manual',
- fileloc: '/opt/dag.py',
- },
- {
- id: 4,
- project_id: 7,
- created_at: '2023-01-05T14:07:02.975Z',
- updated_at: '2023-01-05T14:07:02.975Z',
- has_import_errors: false,
- is_active: false,
- is_paused: true,
- next_run: '2023-01-05T14:07:02.975Z',
- dag_name: 'Dag number 4',
- schedule: 'Manual',
- fileloc: '/opt/dag.py',
- },
- {
- id: 5,
- project_id: 7,
- created_at: '2023-01-05T14:07:02.975Z',
- updated_at: '2023-01-05T14:07:02.975Z',
- has_import_errors: false,
- is_active: false,
- is_paused: true,
- next_run: '2023-01-05T14:07:02.975Z',
- dag_name: 'Dag number 5',
- schedule: 'Manual',
- fileloc: '/opt/dag.py',
- },
-];
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 7fb4f2d2463..3f709d8c9f5 100644
--- a/spec/frontend/alert_management/components/alert_management_table_spec.js
+++ b/spec/frontend/alert_management/components/alert_management_table_spec.js
@@ -68,7 +68,7 @@ describe('AlertManagementTable', () => {
},
stubs,
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
}),
);
diff --git a/spec/frontend/flash_spec.js b/spec/frontend/alert_spec.js
index 17d6cea23df..1ae8373016b 100644
--- a/spec/frontend/flash_spec.js
+++ b/spec/frontend/alert_spec.js
@@ -1,6 +1,6 @@
import * as Sentry from '@sentry/browser';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import { createAlert, VARIANT_WARNING } from '~/flash';
+import { createAlert, VARIANT_WARNING } from '~/alert';
jest.mock('@sentry/browser');
diff --git a/spec/frontend/alerts_settings/components/__snapshots__/alerts_form_spec.js.snap b/spec/frontend/alerts_settings/components/__snapshots__/alerts_form_spec.js.snap
index 0e402e61bcc..4a60d605cae 100644
--- a/spec/frontend/alerts_settings/components/__snapshots__/alerts_form_spec.js.snap
+++ b/spec/frontend/alerts_settings/components/__snapshots__/alerts_form_spec.js.snap
@@ -51,36 +51,23 @@ exports[`Alert integration settings form default state should match the default
</gl-link-stub>
</label>
- <gl-dropdown-stub
+ <gl-collapsible-listbox-stub
block="true"
category="primary"
- clearalltext="Clear all"
- clearalltextclass="gl-px-5"
data-qa-selector="incident_templates_dropdown"
headertext=""
- hideheaderborder="true"
- highlighteditemstitle="Selected"
- highlighteditemstitleclass="gl-px-5"
+ icon=""
id="alert-integration-settings-issue-template"
+ items="[object Object]"
+ noresultstext="No results found"
+ placement="left"
+ resetbuttonlabel=""
+ searchplaceholder="Search"
+ selected="selecte_tmpl"
size="medium"
- text="selecte_tmpl"
+ toggletext=""
variant="default"
- >
- <gl-dropdown-item-stub
- avatarurl=""
- data-qa-selector="incident_templates_item"
- iconcolor=""
- iconname=""
- iconrightarialabel=""
- iconrightname=""
- ischeckitem="true"
- secondarytext=""
- >
-
- No template selected
-
- </gl-dropdown-item-stub>
- </gl-dropdown-stub>
+ />
</gl-form-group-stub>
<gl-form-group-stub
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 a15c78cc456..67d8619f157 100644
--- a/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
+++ b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js
@@ -30,7 +30,7 @@ import {
INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR,
DELETE_INTEGRATION_ERROR,
} from '~/alerts_settings/utils/error_messages';
-import { createAlert, VARIANT_SUCCESS } from '~/flash';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_FORBIDDEN, HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status';
import {
@@ -48,7 +48,7 @@ import {
} from './mocks/apollo_mock';
import mockIntegrations from './mocks/integrations.json';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('AlertsSettingsWrapper', () => {
let wrapper;
@@ -128,10 +128,6 @@ describe('AlertsSettingsWrapper', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
beforeEach(() => {
createComponent({
@@ -478,7 +474,7 @@ describe('AlertsSettingsWrapper', () => {
expect(destroyIntegrationHandler).toHaveBeenCalled();
});
- it('displays flash if mutation had a recoverable error', async () => {
+ it('displays alert if mutation had a recoverable error', async () => {
createComponentWithApollo({
destroyHandler: jest.fn().mockResolvedValue(destroyIntegrationResponseWithErrors),
});
@@ -489,7 +485,7 @@ describe('AlertsSettingsWrapper', () => {
expect(createAlert).toHaveBeenCalledWith({ message: 'Houston, we have a problem' });
});
- it('displays flash if mutation had a non-recoverable error', async () => {
+ it('displays alert if mutation had a non-recoverable error', async () => {
createComponentWithApollo({
destroyHandler: jest.fn().mockRejectedValue('Error'),
});
diff --git a/spec/frontend/analytics/components/activity_chart_spec.js b/spec/frontend/analytics/components/activity_chart_spec.js
index c26407f5c1d..4f8126aaacf 100644
--- a/spec/frontend/analytics/components/activity_chart_spec.js
+++ b/spec/frontend/analytics/components/activity_chart_spec.js
@@ -13,11 +13,6 @@ describe('Activity Chart Bundle', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findChart = () => wrapper.findComponent(GlColumnChart);
const findNoData = () => wrapper.find('[data-testid="noActivityChartData"]');
diff --git a/spec/frontend/analytics/cycle_analytics/base_spec.js b/spec/frontend/analytics/cycle_analytics/base_spec.js
index 58588ff49ce..033916eabcd 100644
--- a/spec/frontend/analytics/cycle_analytics/base_spec.js
+++ b/spec/frontend/analytics/cycle_analytics/base_spec.js
@@ -31,13 +31,15 @@ Vue.use(Vuex);
let wrapper;
-const { id: groupId, path: groupPath } = currentGroup;
+const { path } = currentGroup;
+const groupPath = `groups/${path}`;
const defaultState = {
currentGroup,
createdBefore,
createdAfter,
stageCounts,
- endpoints: { fullPath, groupId, groupPath },
+ groupPath,
+ namespace: { fullPath },
};
function createStore({ initialState = {}, initialGetters = {} }) {
@@ -93,11 +95,6 @@ describe('Value stream analytics component', () => {
wrapper = createComponent({ initialState: { selectedStage, selectedStageEvents, pagination } });
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders the path navigation component', () => {
expect(findPathNavigation().exists()).toBe(true);
});
@@ -139,7 +136,6 @@ describe('Value stream analytics component', () => {
it('passes the paths to the filter bar', () => {
expect(findFilters().props()).toEqual({
- groupId,
groupPath,
endDate: createdBefore,
hasDateRangeFilter: true,
@@ -157,6 +153,10 @@ describe('Value stream analytics component', () => {
expect(findPagination().exists()).toBe(true);
});
+ it('does not render a link to the value streams dashboard', () => {
+ expect(findOverviewMetrics().props('dashboardsPath')).toBeNull();
+ });
+
describe('with `cycleAnalyticsForGroups=true` license', () => {
beforeEach(() => {
wrapper = createComponent({ initialState: { features: { cycleAnalyticsForGroups: true } } });
@@ -167,6 +167,23 @@ describe('Value stream analytics component', () => {
});
});
+ describe('with `groupAnalyticsDashboardsPage=true` and `groupLevelAnalyticsDashboard=true` license', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ initialState: {
+ features: { groupAnalyticsDashboardsPage: true, groupLevelAnalyticsDashboard: true },
+ },
+ });
+ });
+
+ it('renders a link to the value streams dashboard', () => {
+ expect(findOverviewMetrics().props('dashboardsPath')).toBeDefined();
+ expect(findOverviewMetrics().props('dashboardsPath')).toBe(
+ '/groups/foo/-/analytics/dashboards/value_streams_dashboard?query=full/path/to/foo',
+ );
+ });
+ });
+
describe('isLoading = true', () => {
beforeEach(() => {
wrapper = createComponent({
diff --git a/spec/frontend/analytics/cycle_analytics/filter_bar_spec.js b/spec/frontend/analytics/cycle_analytics/filter_bar_spec.js
index 2b26b202882..da7824adbf9 100644
--- a/spec/frontend/analytics/cycle_analytics/filter_bar_spec.js
+++ b/spec/frontend/analytics/cycle_analytics/filter_bar_spec.js
@@ -98,7 +98,6 @@ describe('Filter bar', () => {
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
});
diff --git a/spec/frontend/analytics/cycle_analytics/formatted_stage_count_spec.js b/spec/frontend/analytics/cycle_analytics/formatted_stage_count_spec.js
index 9be92bb92bc..6dd7e2e6223 100644
--- a/spec/frontend/analytics/cycle_analytics/formatted_stage_count_spec.js
+++ b/spec/frontend/analytics/cycle_analytics/formatted_stage_count_spec.js
@@ -16,10 +16,6 @@ describe('Formatted Stage Count', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
stageCount | expectedOutput
${null} | ${'-'}
diff --git a/spec/frontend/analytics/cycle_analytics/mock_data.js b/spec/frontend/analytics/cycle_analytics/mock_data.js
index f820f755400..216e07844b8 100644
--- a/spec/frontend/analytics/cycle_analytics/mock_data.js
+++ b/spec/frontend/analytics/cycle_analytics/mock_data.js
@@ -219,6 +219,8 @@ export const group = {
};
export const currentGroup = convertObjectPropsToCamelCase(group, { deep: true });
+export const groupNamespace = { id: currentGroup.id, fullPath: `groups/${currentGroup.path}` };
+export const projectNamespace = { fullPath: 'some/cool/path' };
export const selectedProjects = [
{
diff --git a/spec/frontend/analytics/cycle_analytics/path_navigation_spec.js b/spec/frontend/analytics/cycle_analytics/path_navigation_spec.js
index 107e62035c3..9a598ee0ad1 100644
--- a/spec/frontend/analytics/cycle_analytics/path_navigation_spec.js
+++ b/spec/frontend/analytics/cycle_analytics/path_navigation_spec.js
@@ -50,8 +50,6 @@ describe('Project PathNavigation', () => {
afterEach(() => {
unmockTracking();
- wrapper.destroy();
- wrapper = null;
});
describe('displays correctly', () => {
diff --git a/spec/frontend/analytics/cycle_analytics/stage_table_spec.js b/spec/frontend/analytics/cycle_analytics/stage_table_spec.js
index cfccce7eae9..fbc63a80de8 100644
--- a/spec/frontend/analytics/cycle_analytics/stage_table_spec.js
+++ b/spec/frontend/analytics/cycle_analytics/stage_table_spec.js
@@ -51,10 +51,6 @@ function createComponent(props = {}, shallow = false) {
}
describe('StageTable', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('is loaded with data', () => {
beforeEach(() => {
wrapper = createComponent();
@@ -258,7 +254,6 @@ describe('StageTable', () => {
afterEach(() => {
unmockTracking();
- wrapper.destroy();
});
it('will display the pagination component', () => {
@@ -305,7 +300,6 @@ describe('StageTable', () => {
afterEach(() => {
unmockTracking();
- wrapper.destroy();
});
it('can sort the end event or duration', () => {
diff --git a/spec/frontend/analytics/cycle_analytics/store/actions_spec.js b/spec/frontend/analytics/cycle_analytics/store/actions_spec.js
index 3030fca126b..b2ce8596c22 100644
--- a/spec/frontend/analytics/cycle_analytics/store/actions_spec.js
+++ b/spec/frontend/analytics/cycle_analytics/store/actions_spec.js
@@ -13,21 +13,13 @@ import {
createdBefore,
initialPaginationState,
reviewEvents,
+ projectNamespace as namespace,
} from '../mock_data';
-const { id: groupId, path: groupPath } = currentGroup;
-const mockMilestonesPath = 'mock-milestones.json';
-const mockLabelsPath = 'mock-labels.json';
-const mockRequestPath = 'some/cool/path';
+const { path: groupPath } = currentGroup;
+const mockMilestonesPath = `/${namespace.fullPath}/-/milestones.json`;
+const mockLabelsPath = `/${namespace.fullPath}/-/labels.json`;
const mockFullPath = '/namespace/-/analytics/value_stream_analytics/value_streams';
-const mockEndpoints = {
- fullPath: mockFullPath,
- requestPath: mockRequestPath,
- labelsPath: mockLabelsPath,
- milestonesPath: mockMilestonesPath,
- groupId,
- groupPath,
-};
const mockSetDateActionCommit = {
payload: { createdAfter, createdBefore },
type: 'SET_DATE_RANGE',
@@ -35,6 +27,7 @@ const mockSetDateActionCommit = {
const defaultState = {
...getters,
+ namespace,
selectedValueStream,
createdAfter,
createdBefore,
@@ -81,7 +74,8 @@ describe('Project Value Stream Analytics actions', () => {
const selectedAssigneeList = ['Assignee 1', 'Assignee 2'];
const selectedLabelList = ['Label 1', 'Label 2'];
const payload = {
- endpoints: mockEndpoints,
+ namespace,
+ groupPath,
selectedAuthor,
selectedMilestone,
selectedAssigneeList,
@@ -92,7 +86,7 @@ describe('Project Value Stream Analytics actions', () => {
groupEndpoint: 'foo',
labelsEndpoint: mockLabelsPath,
milestonesEndpoint: mockMilestonesPath,
- projectEndpoint: '/namespace/-/analytics/value_stream_analytics/value_streams',
+ projectEndpoint: namespace.fullPath,
};
it('will dispatch fetchValueStreams actions and commit SET_LOADING and INITIALIZE_VSA', () => {
@@ -193,7 +187,6 @@ describe('Project Value Stream Analytics actions', () => {
beforeEach(() => {
state = {
...defaultState,
- endpoints: mockEndpoints,
selectedStage,
};
mock = new MockAdapter(axios);
@@ -219,7 +212,6 @@ describe('Project Value Stream Analytics actions', () => {
beforeEach(() => {
state = {
...defaultState,
- endpoints: mockEndpoints,
selectedStage,
};
mock = new MockAdapter(axios);
@@ -243,7 +235,6 @@ describe('Project Value Stream Analytics actions', () => {
beforeEach(() => {
state = {
...defaultState,
- endpoints: mockEndpoints,
selectedStage,
};
mock = new MockAdapter(axios);
@@ -265,9 +256,7 @@ describe('Project Value Stream Analytics actions', () => {
const mockValueStreamPath = /\/analytics\/value_stream_analytics\/value_streams/;
beforeEach(() => {
- state = {
- endpoints: mockEndpoints,
- };
+ state = { namespace };
mock = new MockAdapter(axios);
mock.onGet(mockValueStreamPath).reply(HTTP_STATUS_OK);
});
@@ -333,7 +322,7 @@ describe('Project Value Stream Analytics actions', () => {
beforeEach(() => {
state = {
- endpoints: mockEndpoints,
+ namespace,
selectedValueStream,
};
mock = new MockAdapter(axios);
diff --git a/spec/frontend/analytics/cycle_analytics/store/mutations_spec.js b/spec/frontend/analytics/cycle_analytics/store/mutations_spec.js
index 567fac81e1f..70b7454f4a0 100644
--- a/spec/frontend/analytics/cycle_analytics/store/mutations_spec.js
+++ b/spec/frontend/analytics/cycle_analytics/store/mutations_spec.js
@@ -17,12 +17,14 @@ import {
rawStageCounts,
stageCounts,
initialPaginationState as pagination,
+ projectNamespace as mockNamespace,
} from '../mock_data';
let state;
const rawEvents = rawIssueEvents.events;
const convertedEvents = issueEvents.events;
-const mockRequestPath = 'fake/request/path';
+const mockGroupPath = 'groups/path';
+const mockFeatures = { some: 'feature' };
const mockCreatedAfter = '2020-06-18';
const mockCreatedBefore = '2020-07-18';
@@ -64,19 +66,22 @@ describe('Project Value Stream Analytics mutations', () => {
const mockSetDatePayload = { createdAfter: mockCreatedAfter, createdBefore: mockCreatedBefore };
const mockInitialPayload = {
- endpoints: { requestPath: mockRequestPath },
currentGroup: { title: 'cool-group' },
id: 1337,
+ groupPath: mockGroupPath,
+ namespace: mockNamespace,
+ features: mockFeatures,
...mockSetDatePayload,
};
const mockInitializedObj = {
- endpoints: { requestPath: mockRequestPath },
...mockSetDatePayload,
};
it.each`
mutation | stateKey | value
- ${types.INITIALIZE_VSA} | ${'endpoints'} | ${{ requestPath: mockRequestPath }}
+ ${types.INITIALIZE_VSA} | ${'features'} | ${mockFeatures}
+ ${types.INITIALIZE_VSA} | ${'namespace'} | ${mockNamespace}
+ ${types.INITIALIZE_VSA} | ${'groupPath'} | ${mockGroupPath}
${types.INITIALIZE_VSA} | ${'createdAfter'} | ${mockCreatedAfter}
${types.INITIALIZE_VSA} | ${'createdBefore'} | ${mockCreatedBefore}
`('$mutation will set $stateKey', ({ mutation, stateKey, value }) => {
diff --git a/spec/frontend/analytics/cycle_analytics/total_time_spec.js b/spec/frontend/analytics/cycle_analytics/total_time_spec.js
index 47ee7aad8c4..6597b6fa3d5 100644
--- a/spec/frontend/analytics/cycle_analytics/total_time_spec.js
+++ b/spec/frontend/analytics/cycle_analytics/total_time_spec.js
@@ -10,10 +10,6 @@ describe('TotalTime', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with a valid time object', () => {
it.each`
time
diff --git a/spec/frontend/analytics/cycle_analytics/utils_spec.js b/spec/frontend/analytics/cycle_analytics/utils_spec.js
index fe412bf7498..e6d17edcadc 100644
--- a/spec/frontend/analytics/cycle_analytics/utils_spec.js
+++ b/spec/frontend/analytics/cycle_analytics/utils_spec.js
@@ -91,9 +91,9 @@ describe('Value stream analytics utils', () => {
const projectId = '5';
const createdAfter = '2021-09-01';
const createdBefore = '2021-11-06';
- const groupId = '146';
const groupPath = 'fake-group';
- const fullPath = 'fake-group/fake-project';
+ const namespaceName = 'Fake project';
+ const namespaceFullPath = 'fake-group/fake-project';
const labelsPath = '/fake-group/fake-project/-/labels.json';
const milestonesPath = '/fake-group/fake-project/-/milestones.json';
const requestPath = '/fake-group/fake-project/-/value_stream_analytics';
@@ -102,11 +102,11 @@ describe('Value stream analytics utils', () => {
projectId,
createdBefore,
createdAfter,
- fullPath,
+ namespaceName,
+ namespaceFullPath,
requestPath,
labelsPath,
milestonesPath,
- groupId,
groupPath,
};
@@ -124,14 +124,13 @@ describe('Value stream analytics utils', () => {
expect(res.createdAfter).toEqual(new Date(createdAfter));
});
+ it('sets the namespace', () => {
+ expect(res.namespace.name).toBe(namespaceName);
+ expect(res.namespace.fullPath).toBe(namespaceFullPath);
+ });
+
it('sets the endpoints', () => {
- const { endpoints } = res;
- expect(endpoints.fullPath).toBe(fullPath);
- expect(endpoints.requestPath).toBe(requestPath);
- expect(endpoints.labelsPath).toBe(labelsPath);
- expect(endpoints.milestonesPath).toBe(milestonesPath);
- expect(endpoints.groupId).toBe(parseInt(groupId, 10));
- expect(endpoints.groupPath).toBe(groupPath);
+ expect(res.groupPath).toBe(`groups/${groupPath}`);
});
it('returns null when there is no stage', () => {
@@ -164,7 +163,7 @@ describe('Value stream analytics utils', () => {
...rawData,
gon: { licensed_features: fakeFeatures },
});
- expect(res.features).toEqual(fakeFeatures);
+ expect(res.features).toMatchObject(fakeFeatures);
});
});
});
diff --git a/spec/frontend/analytics/cycle_analytics/value_stream_filters_spec.js b/spec/frontend/analytics/cycle_analytics/value_stream_filters_spec.js
index 4f333e95d89..160f6ce0563 100644
--- a/spec/frontend/analytics/cycle_analytics/value_stream_filters_spec.js
+++ b/spec/frontend/analytics/cycle_analytics/value_stream_filters_spec.js
@@ -34,11 +34,6 @@ describe('ValueStreamFilters', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('will render the filter bar', () => {
expect(findFilterBar().exists()).toBe(true);
});
diff --git a/spec/frontend/analytics/cycle_analytics/value_stream_metrics_spec.js b/spec/frontend/analytics/cycle_analytics/value_stream_metrics_spec.js
index 948dc5c9be2..6a64737bc80 100644
--- a/spec/frontend/analytics/cycle_analytics/value_stream_metrics_spec.js
+++ b/spec/frontend/analytics/cycle_analytics/value_stream_metrics_spec.js
@@ -8,10 +8,11 @@ import { METRIC_TYPE_SUMMARY } from '~/api/analytics_api';
import { VSA_METRICS_GROUPS, METRICS_POPOVER_CONTENT } from '~/analytics/shared/constants';
import { prepareTimeMetricsData } from '~/analytics/shared/utils';
import MetricTile from '~/analytics/shared/components/metric_tile.vue';
-import { createAlert } from '~/flash';
+import ValueStreamsDashboardLink from '~/analytics/shared/components/value_streams_dashboard_link.vue';
+import { createAlert } from '~/alert';
import { group } from './mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('ValueStreamMetrics', () => {
let wrapper;
@@ -37,6 +38,7 @@ describe('ValueStreamMetrics', () => {
});
};
+ const findVSDLink = () => wrapper.findComponent(ValueStreamsDashboardLink);
const findMetrics = () => wrapper.findAllComponents(MetricTile);
const findMetricsGroups = () => wrapper.findAllByTestId('vsa-metrics-group');
@@ -48,10 +50,6 @@ describe('ValueStreamMetrics', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with successful requests', () => {
beforeEach(() => {
mockGetValueStreamSummaryMetrics = jest.fn().mockResolvedValue({ data: metricsData });
@@ -168,6 +166,25 @@ describe('ValueStreamMetrics', () => {
});
});
+ describe('Value Streams Dashboard Link', () => {
+ it('will render when a dashboardsPath is set', async () => {
+ wrapper = createComponent({ groupBy: VSA_METRICS_GROUPS, dashboardsPath: 'fake-group-path' });
+ await waitForPromises();
+
+ const vsdLink = findVSDLink();
+
+ expect(vsdLink.exists()).toBe(true);
+ expect(vsdLink.props()).toEqual({ requestPath: 'fake-group-path' });
+ });
+
+ it('does not render without a dashboardsPath', async () => {
+ wrapper = createComponent({ groupBy: VSA_METRICS_GROUPS });
+ await waitForPromises();
+
+ expect(findVSDLink().exists()).toBe(false);
+ });
+ });
+
describe('with a request failing', () => {
beforeEach(async () => {
mockGetValueStreamSummaryMetrics = jest.fn().mockRejectedValue();
diff --git a/spec/frontend/analytics/devops_reports/components/service_ping_disabled_spec.js b/spec/frontend/analytics/devops_reports/components/service_ping_disabled_spec.js
index c62bfb11f7b..70bfce41c82 100644
--- a/spec/frontend/analytics/devops_reports/components/service_ping_disabled_spec.js
+++ b/spec/frontend/analytics/devops_reports/components/service_ping_disabled_spec.js
@@ -6,10 +6,6 @@ import ServicePingDisabled from '~/analytics/devops_reports/components/service_p
describe('~/analytics/devops_reports/components/service_ping_disabled.vue', () => {
let wrapper;
- afterEach(() => {
- wrapper.destroy();
- });
-
const createWrapper = ({ isAdmin = false } = {}) => {
wrapper = mountExtended(ServicePingDisabled, {
provide: {
diff --git a/spec/frontend/analytics/shared/components/daterange_spec.js b/spec/frontend/analytics/shared/components/daterange_spec.js
index 562e86529ee..5f0847e0db6 100644
--- a/spec/frontend/analytics/shared/components/daterange_spec.js
+++ b/spec/frontend/analytics/shared/components/daterange_spec.js
@@ -22,10 +22,6 @@ describe('Daterange component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findDaterangePicker = () => wrapper.findComponent(GlDaterangePicker);
const findDateRangeIndicator = () => wrapper.findByTestId('daterange-picker-indicator');
@@ -90,18 +86,19 @@ describe('Daterange component', () => {
});
describe('set', () => {
- it('emits the change event with an object containing startDate and endDate', () => {
+ it('emits the change event with an object containing startDate and endDate', async () => {
const startDate = new Date('2019-10-01');
const endDate = new Date('2019-10-05');
- wrapper.vm.dateRange = { startDate, endDate };
- expect(wrapper.emitted().change).toEqual([[{ startDate, endDate }]]);
+ await findDaterangePicker().vm.$emit('input', { startDate, endDate });
+
+ expect(wrapper.emitted('change')).toEqual([[{ startDate, endDate }]]);
});
});
describe('get', () => {
- it("returns value of dateRange from state's startDate and endDate", () => {
- expect(wrapper.vm.dateRange).toEqual({
+ it("datepicker to have default of dateRange from state's startDate and endDate", () => {
+ expect(findDaterangePicker().props('value')).toEqual({
startDate: defaultProps.startDate,
endDate: defaultProps.endDate,
});
diff --git a/spec/frontend/analytics/shared/components/metric_popover_spec.js b/spec/frontend/analytics/shared/components/metric_popover_spec.js
index e0bfff3e664..d7e6606cdc6 100644
--- a/spec/frontend/analytics/shared/components/metric_popover_spec.js
+++ b/spec/frontend/analytics/shared/components/metric_popover_spec.js
@@ -34,10 +34,6 @@ describe('MetricPopover', () => {
const findMetricDocsLinkIcon = () => findMetricDocsLink().findComponent(GlIcon);
const findMetricDetailsIcon = () => findMetricLink().findComponent(GlIcon);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the metric label', () => {
wrapper = createComponent({ metric: MOCK_METRIC });
expect(findMetricLabel().text()).toBe(MOCK_METRIC.label);
diff --git a/spec/frontend/analytics/shared/components/metric_tile_spec.js b/spec/frontend/analytics/shared/components/metric_tile_spec.js
index 980dfad9eb0..00e82cff0f0 100644
--- a/spec/frontend/analytics/shared/components/metric_tile_spec.js
+++ b/spec/frontend/analytics/shared/components/metric_tile_spec.js
@@ -21,10 +21,6 @@ describe('MetricTile', () => {
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', () => {
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 3871fd530d8..d2cbe0d39e4 100644
--- a/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js
+++ b/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js
@@ -70,10 +70,6 @@ describe('ProjectsDropdownFilter component', () => {
return waitForPromises();
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findHighlightedItems = () => wrapper.findByTestId('vsa-highlighted-items');
const findUnhighlightedItems = () => wrapper.findByTestId('vsa-default-items');
const findClearAllButton = () => wrapper.findByText('Clear all');
diff --git a/spec/frontend/analytics/shared/utils_spec.js b/spec/frontend/analytics/shared/utils_spec.js
index b48e2d971b5..24af7b836d5 100644
--- a/spec/frontend/analytics/shared/utils_spec.js
+++ b/spec/frontend/analytics/shared/utils_spec.js
@@ -5,6 +5,7 @@ import {
extractPaginationQueryParameters,
getDataZoomOption,
prepareTimeMetricsData,
+ generateValueStreamsDashboardLink,
} from '~/analytics/shared/utils';
import { slugify } from '~/lib/utils/text_utility';
import { objectToQuery } from '~/lib/utils/url_utility';
@@ -212,3 +213,30 @@ describe('prepareTimeMetricsData', () => {
]);
});
});
+
+describe('generateValueStreamsDashboardLink', () => {
+ it.each`
+ groupPath | projectPaths | result
+ ${''} | ${[]} | ${''}
+ ${'groups/fake-group'} | ${[]} | ${'/groups/fake-group/-/analytics/dashboards/value_streams_dashboard'}
+ ${'groups/fake-group'} | ${['fake-path/project_1']} | ${'/groups/fake-group/-/analytics/dashboards/value_streams_dashboard?query=fake-path/project_1'}
+ ${'groups/fake-group'} | ${['fake-path/project_1', 'fake-path/project_2']} | ${'/groups/fake-group/-/analytics/dashboards/value_streams_dashboard?query=fake-path/project_1,fake-path/project_2'}
+ `(
+ 'generates the dashboard link when groupPath=$groupPath and projectPaths=$projectPaths',
+ ({ groupPath, projectPaths, result }) => {
+ expect(generateValueStreamsDashboardLink(groupPath, projectPaths)).toBe(result);
+ },
+ );
+
+ describe('with a relative url rool set', () => {
+ beforeEach(() => {
+ gon.relative_url_root = '/foobar';
+ });
+
+ it('with includes a relative path if one is set', () => {
+ expect(generateValueStreamsDashboardLink('groups/fake-path', ['project_1'])).toBe(
+ '/foobar/groups/fake-path/-/analytics/dashboards/value_streams_dashboard?query=project_1',
+ );
+ });
+ });
+});
diff --git a/spec/frontend/analytics/usage_trends/components/app_spec.js b/spec/frontend/analytics/usage_trends/components/app_spec.js
index c732dc22322..f9338661ebf 100644
--- a/spec/frontend/analytics/usage_trends/components/app_spec.js
+++ b/spec/frontend/analytics/usage_trends/components/app_spec.js
@@ -15,11 +15,6 @@ describe('UsageTrendsApp', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('displays the usage counts component', () => {
expect(wrapper.findComponent(UsageCounts).exists()).toBe(true);
});
diff --git a/spec/frontend/analytics/usage_trends/components/usage_counts_spec.js b/spec/frontend/analytics/usage_trends/components/usage_counts_spec.js
index f4cbc56be5c..a71ce090955 100644
--- a/spec/frontend/analytics/usage_trends/components/usage_counts_spec.js
+++ b/spec/frontend/analytics/usage_trends/components/usage_counts_spec.js
@@ -26,10 +26,6 @@ describe('UsageCounts', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findAllSingleStats = () => wrapper.findAllComponents(GlSingleStat);
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 ad6089f74b5..322d05e663a 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
@@ -45,11 +45,6 @@ describe('UsageTrendsCountChart', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findLoader = () => wrapper.findComponent(ChartSkeletonLoader);
const findChart = () => wrapper.findComponent(GlLineChart);
const findAlert = () => wrapper.findComponent(GlAlert);
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 e7abd4d4323..20836d7cc70 100644
--- a/spec/frontend/analytics/usage_trends/components/users_chart_spec.js
+++ b/spec/frontend/analytics/usage_trends/components/users_chart_spec.js
@@ -42,11 +42,6 @@ describe('UsersChart', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findLoader = () => wrapper.findComponent(ChartSkeletonLoader);
const findAlert = () => wrapper.findComponent(GlAlert);
const findChart = () => wrapper.findComponent(GlAreaChart);
diff --git a/spec/frontend/api/alert_management_alerts_api_spec.js b/spec/frontend/api/alert_management_alerts_api_spec.js
index 507f659a170..86052a05b76 100644
--- a/spec/frontend/api/alert_management_alerts_api_spec.js
+++ b/spec/frontend/api/alert_management_alerts_api_spec.js
@@ -9,7 +9,6 @@ import {
describe('~/api/alert_management_alerts_api.js', () => {
let mock;
- let originalGon;
const projectId = 1;
const alertIid = 2;
@@ -19,13 +18,11 @@ describe('~/api/alert_management_alerts_api.js', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
- originalGon = window.gon;
window.gon = { api_version: 'v4' };
});
afterEach(() => {
mock.restore();
- window.gon = originalGon;
});
describe('fetchAlertMetricImages', () => {
diff --git a/spec/frontend/api/groups_api_spec.js b/spec/frontend/api/groups_api_spec.js
index 0315db02cf2..642edb33624 100644
--- a/spec/frontend/api/groups_api_spec.js
+++ b/spec/frontend/api/groups_api_spec.js
@@ -10,23 +10,18 @@ const mockUrlRoot = '/gitlab';
const mockGroupId = '99';
describe('GroupsApi', () => {
- let originalGon;
let mock;
- const dummyGon = {
- api_version: mockApiVersion,
- relative_url_root: mockUrlRoot,
- };
-
beforeEach(() => {
mock = new MockAdapter(axios);
- originalGon = window.gon;
- window.gon = { ...dummyGon };
+ window.gon = {
+ api_version: mockApiVersion,
+ relative_url_root: mockUrlRoot,
+ };
});
afterEach(() => {
mock.restore();
- window.gon = originalGon;
});
describe('updateGroup', () => {
diff --git a/spec/frontend/api/packages_api_spec.js b/spec/frontend/api/packages_api_spec.js
index 5f517bcf358..37c4b926ec2 100644
--- a/spec/frontend/api/packages_api_spec.js
+++ b/spec/frontend/api/packages_api_spec.js
@@ -6,22 +6,18 @@ import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
describe('Api', () => {
const dummyApiVersion = 'v3000';
const dummyUrlRoot = '/gitlab';
- const dummyGon = {
- api_version: dummyApiVersion,
- relative_url_root: dummyUrlRoot,
- };
- let originalGon;
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
- originalGon = window.gon;
- window.gon = { ...dummyGon };
+ window.gon = {
+ api_version: dummyApiVersion,
+ relative_url_root: dummyUrlRoot,
+ };
});
afterEach(() => {
mock.restore();
- window.gon = originalGon;
});
describe('packages', () => {
diff --git a/spec/frontend/api/projects_api_spec.js b/spec/frontend/api/projects_api_spec.js
index 2d4ed39dad0..2de56fae0c2 100644
--- a/spec/frontend/api/projects_api_spec.js
+++ b/spec/frontend/api/projects_api_spec.js
@@ -7,7 +7,6 @@ import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
describe('~/api/projects_api.js', () => {
let mock;
- let originalGon;
const projectId = 1;
const setfullPathProjectSearch = (value) => {
@@ -17,13 +16,11 @@ describe('~/api/projects_api.js', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
- originalGon = window.gon;
window.gon = { api_version: 'v7', features: { fullPathProjectSearch: true } };
});
afterEach(() => {
mock.restore();
- window.gon = originalGon;
});
describe('getProjects', () => {
diff --git a/spec/frontend/api/tags_api_spec.js b/spec/frontend/api/tags_api_spec.js
index af3533f52b7..0a1177d4f60 100644
--- a/spec/frontend/api/tags_api_spec.js
+++ b/spec/frontend/api/tags_api_spec.js
@@ -5,20 +5,17 @@ import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
describe('~/api/tags_api.js', () => {
let mock;
- let originalGon;
const projectId = 1;
beforeEach(() => {
mock = new MockAdapter(axios);
- originalGon = window.gon;
window.gon = { api_version: 'v7' };
});
afterEach(() => {
mock.restore();
- window.gon = originalGon;
});
describe('getTag', () => {
diff --git a/spec/frontend/api/user_api_spec.js b/spec/frontend/api/user_api_spec.js
index 4d0252aad23..6636d77a09b 100644
--- a/spec/frontend/api/user_api_spec.js
+++ b/spec/frontend/api/user_api_spec.js
@@ -12,19 +12,16 @@ import { timeRanges } from '~/vue_shared/constants';
describe('~/api/user_api', () => {
let axiosMock;
- let originalGon;
beforeEach(() => {
axiosMock = new MockAdapter(axios);
- originalGon = window.gon;
window.gon = { api_version: 'v4' };
});
afterEach(() => {
axiosMock.restore();
axiosMock.resetHistory();
- window.gon = originalGon;
});
describe('followUser', () => {
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index 6fd106502c4..4ef37311e51 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -10,27 +10,22 @@ import {
HTTP_STATUS_OK,
} from '~/lib/utils/http_status';
-jest.mock('~/flash');
-
describe('Api', () => {
const dummyApiVersion = 'v3000';
const dummyUrlRoot = '/gitlab';
- const dummyGon = {
- api_version: dummyApiVersion,
- relative_url_root: dummyUrlRoot,
- };
- let originalGon;
+
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
- originalGon = window.gon;
- window.gon = { ...dummyGon };
+ window.gon = {
+ api_version: dummyApiVersion,
+ relative_url_root: dummyUrlRoot,
+ };
});
afterEach(() => {
mock.restore();
- window.gon = originalGon;
});
describe('buildUrl', () => {
@@ -1423,7 +1418,7 @@ describe('Api', () => {
describe('when service data increment counter is called with feature flag disabled', () => {
beforeEach(() => {
- gon.features = { ...gon.features, usageDataApi: false };
+ gon.features = { usageDataApi: false };
});
it('returns null', () => {
@@ -1437,7 +1432,7 @@ describe('Api', () => {
describe('when service data increment counter is called', () => {
beforeEach(() => {
- gon.features = { ...gon.features, usageDataApi: true };
+ gon.features = { usageDataApi: true };
});
it('resolves the Promise', () => {
@@ -1468,7 +1463,7 @@ describe('Api', () => {
describe('when service data increment unique users is called with feature flag disabled', () => {
beforeEach(() => {
- gon.features = { ...gon.features, usageDataApi: false };
+ gon.features = { usageDataApi: false };
});
it('returns null and does not call the endpoint', () => {
@@ -1483,7 +1478,7 @@ describe('Api', () => {
describe('when service data increment unique users is called', () => {
beforeEach(() => {
- gon.features = { ...gon.features, usageDataApi: true };
+ gon.features = { usageDataApi: true };
});
it('resolves the Promise', () => {
@@ -1500,7 +1495,7 @@ describe('Api', () => {
describe('when user is not set and feature flag enabled', () => {
beforeEach(() => {
- gon.features = { ...gon.features, usageDataApi: true };
+ gon.features = { usageDataApi: true };
});
it('returns null and does not call the endpoint', () => {
diff --git a/spec/frontend/approvals/mock_data.js b/spec/frontend/approvals/mock_data.js
new file mode 100644
index 00000000000..e0e90c09791
--- /dev/null
+++ b/spec/frontend/approvals/mock_data.js
@@ -0,0 +1,10 @@
+import approvedByCurrentUser from 'test_fixtures/graphql/merge_requests/approvals/approvals.query.graphql.json';
+
+export const createCanApproveResponse = () => {
+ const response = JSON.parse(JSON.stringify(approvedByCurrentUser));
+ response.data.project.mergeRequest.userPermissions.canApprove = true;
+ response.data.project.mergeRequest.approved = false;
+ response.data.project.mergeRequest.approvedBy.nodes = [];
+
+ return response;
+};
diff --git a/spec/frontend/artifacts/components/artifact_row_spec.js b/spec/frontend/artifacts/components/artifact_row_spec.js
index 2a7156bf480..268772ed4c0 100644
--- a/spec/frontend/artifacts/components/artifact_row_spec.js
+++ b/spec/frontend/artifacts/components/artifact_row_spec.js
@@ -1,9 +1,10 @@
-import { GlBadge, GlButton, GlFriendlyWrap } from '@gitlab/ui';
+import { GlBadge, GlButton, GlFriendlyWrap, GlFormCheckbox } from '@gitlab/ui';
import mockGetJobArtifactsResponse from 'test_fixtures/graphql/artifacts/graphql/queries/get_job_artifacts.query.graphql.json';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import ArtifactRow from '~/artifacts/components/artifact_row.vue';
+import { BULK_DELETE_FEATURE_FLAG } from '~/artifacts/constants';
describe('ArtifactRow component', () => {
let wrapper;
@@ -15,23 +16,21 @@ describe('ArtifactRow component', () => {
const findSize = () => wrapper.findByTestId('job-artifact-row-size');
const findDownloadButton = () => wrapper.findByTestId('job-artifact-row-download-button');
const findDeleteButton = () => wrapper.findByTestId('job-artifact-row-delete-button');
+ const findCheckbox = () => wrapper.findComponent(GlFormCheckbox);
- const createComponent = ({ canDestroyArtifacts = true } = {}) => {
+ const createComponent = ({ canDestroyArtifacts = true, glFeatures = {} } = {}) => {
wrapper = shallowMountExtended(ArtifactRow, {
propsData: {
artifact,
+ isSelected: false,
isLoading: false,
isLastRow: false,
},
- provide: { canDestroyArtifacts },
+ provide: { canDestroyArtifacts, glFeatures },
stubs: { GlBadge, GlButton, GlFriendlyWrap },
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('artifact details', () => {
beforeEach(async () => {
createComponent();
@@ -77,4 +76,30 @@ describe('ArtifactRow component', () => {
expect(wrapper.emitted('delete')).toBeDefined();
});
});
+
+ describe('bulk delete checkbox', () => {
+ describe('with permission and feature flag enabled', () => {
+ beforeEach(() => {
+ createComponent({ glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true } });
+ });
+
+ it('emits selectArtifact when toggled', () => {
+ findCheckbox().vm.$emit('input', true);
+
+ expect(wrapper.emitted('selectArtifact')).toStrictEqual([[artifact, true]]);
+ });
+ });
+
+ it('is not shown without permission', () => {
+ createComponent({ canDestroyArtifacts: false });
+
+ expect(findCheckbox().exists()).toBe(false);
+ });
+
+ it('is not shown with feature flag disabled', () => {
+ createComponent();
+
+ expect(findCheckbox().exists()).toBe(false);
+ });
+ });
});
diff --git a/spec/frontend/artifacts/components/artifacts_bulk_delete_spec.js b/spec/frontend/artifacts/components/artifacts_bulk_delete_spec.js
new file mode 100644
index 00000000000..876906b2c3c
--- /dev/null
+++ b/spec/frontend/artifacts/components/artifacts_bulk_delete_spec.js
@@ -0,0 +1,96 @@
+import { GlSprintf, GlModal } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import mockGetJobArtifactsResponse from 'test_fixtures/graphql/artifacts/graphql/queries/get_job_artifacts.query.graphql.json';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import ArtifactsBulkDelete from '~/artifacts/components/artifacts_bulk_delete.vue';
+import bulkDestroyArtifactsMutation from '~/artifacts/graphql/mutations/bulk_destroy_job_artifacts.mutation.graphql';
+
+Vue.use(VueApollo);
+
+describe('ArtifactsBulkDelete component', () => {
+ let wrapper;
+ let requestHandlers;
+
+ const projectId = '123';
+ const selectedArtifacts = [
+ mockGetJobArtifactsResponse.data.project.jobs.nodes[0].artifacts.nodes[0].id,
+ mockGetJobArtifactsResponse.data.project.jobs.nodes[0].artifacts.nodes[1].id,
+ ];
+
+ const findText = () => wrapper.findComponent(GlSprintf).text();
+ const findDeleteButton = () => wrapper.findByTestId('bulk-delete-delete-button');
+ const findClearButton = () => wrapper.findByTestId('bulk-delete-clear-button');
+ const findModal = () => wrapper.findComponent(GlModal);
+
+ const createComponent = ({
+ handlers = {
+ bulkDestroyArtifactsMutation: jest.fn(),
+ },
+ } = {}) => {
+ requestHandlers = handlers;
+ wrapper = mountExtended(ArtifactsBulkDelete, {
+ apolloProvider: createMockApollo([
+ [bulkDestroyArtifactsMutation, requestHandlers.bulkDestroyArtifactsMutation],
+ ]),
+ propsData: {
+ selectedArtifacts,
+ queryVariables: {},
+ isLoading: false,
+ isLastRow: false,
+ },
+ provide: { projectId },
+ });
+ };
+
+ describe('selected artifacts box', () => {
+ beforeEach(async () => {
+ createComponent();
+ await waitForPromises();
+ });
+
+ it('displays selected artifacts count', () => {
+ expect(findText()).toContain(String(selectedArtifacts.length));
+ });
+
+ it('opens the confirmation modal when the delete button is clicked', async () => {
+ expect(findModal().props('visible')).toBe(false);
+
+ findDeleteButton().trigger('click');
+ await waitForPromises();
+
+ expect(findModal().props('visible')).toBe(true);
+ });
+
+ it('emits clearSelectedArtifacts event when the clear button is clicked', () => {
+ findClearButton().trigger('click');
+
+ expect(wrapper.emitted('clearSelectedArtifacts')).toBeDefined();
+ });
+ });
+
+ describe('bulk delete confirmation modal', () => {
+ beforeEach(async () => {
+ createComponent();
+ findDeleteButton().trigger('click');
+ await waitForPromises();
+ });
+
+ it('calls the bulk delete mutation with the selected artifacts on confirm', () => {
+ findModal().vm.$emit('primary');
+
+ expect(requestHandlers.bulkDestroyArtifactsMutation).toHaveBeenCalledWith({
+ projectId: `gid://gitlab/Project/${projectId}`,
+ ids: selectedArtifacts,
+ });
+ });
+
+ it('does not call the bulk delete mutation on cancel', () => {
+ findModal().vm.$emit('cancel');
+
+ expect(requestHandlers.bulkDestroyArtifactsMutation).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/artifacts/components/artifacts_table_row_details_spec.js b/spec/frontend/artifacts/components/artifacts_table_row_details_spec.js
index d006e0285d2..6bf3498f9b0 100644
--- a/spec/frontend/artifacts/components/artifacts_table_row_details_spec.js
+++ b/spec/frontend/artifacts/components/artifacts_table_row_details_spec.js
@@ -10,9 +10,9 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import destroyArtifactMutation from '~/artifacts/graphql/mutations/destroy_artifact.mutation.graphql';
import { I18N_DESTROY_ERROR, I18N_MODAL_TITLE } from '~/artifacts/constants';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
-jest.mock('~/flash');
+jest.mock('~/alert');
const { artifacts } = getJobArtifactsResponse.data.project.jobs.nodes[0];
const refetchArtifacts = jest.fn();
@@ -25,11 +25,12 @@ describe('ArtifactsTableRowDetails component', () => {
const findModal = () => wrapper.findComponent(GlModal);
- const createComponent = (
+ const createComponent = ({
handlers = {
destroyArtifactMutation: jest.fn(),
},
- ) => {
+ selectedArtifacts = [],
+ } = {}) => {
requestHandlers = handlers;
wrapper = mountExtended(ArtifactsTableRowDetails, {
apolloProvider: createMockApollo([
@@ -37,6 +38,7 @@ describe('ArtifactsTableRowDetails component', () => {
]),
propsData: {
artifacts,
+ selectedArtifacts,
refetchArtifacts,
queryVariables: {},
},
@@ -47,10 +49,6 @@ describe('ArtifactsTableRowDetails component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('passes correct props', () => {
beforeEach(() => {
createComponent();
@@ -92,7 +90,7 @@ describe('ArtifactsTableRowDetails component', () => {
});
});
- it('displays a flash message and refetches artifacts when the mutation fails', async () => {
+ it('displays an alert message and refetches artifacts when the mutation fails', async () => {
createComponent({
destroyArtifactMutation: jest.fn().mockRejectedValue(new Error('Error!')),
});
@@ -120,4 +118,20 @@ describe('ArtifactsTableRowDetails component', () => {
expect(requestHandlers.destroyArtifactMutation).not.toHaveBeenCalled();
});
});
+
+ describe('bulk delete selection', () => {
+ it('is not selected for unselected artifact', async () => {
+ createComponent();
+ await waitForPromises();
+
+ expect(wrapper.findAllComponents(ArtifactRow).at(0).props('isSelected')).toBe(false);
+ });
+
+ it('is selected for selected artifacts', async () => {
+ createComponent({ selectedArtifacts: [artifacts.nodes[0].id] });
+ await waitForPromises();
+
+ expect(wrapper.findAllComponents(ArtifactRow).at(0).props('isSelected')).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/artifacts/components/feedback_banner_spec.js b/spec/frontend/artifacts/components/feedback_banner_spec.js
index 3421486020a..af9599daefa 100644
--- a/spec/frontend/artifacts/components/feedback_banner_spec.js
+++ b/spec/frontend/artifacts/components/feedback_banner_spec.js
@@ -32,10 +32,6 @@ describe('Artifacts management feedback banner', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('is displayed with the correct props', () => {
createComponent();
diff --git a/spec/frontend/artifacts/components/job_artifacts_table_spec.js b/spec/frontend/artifacts/components/job_artifacts_table_spec.js
index dbe4598f599..40f3c9633ab 100644
--- a/spec/frontend/artifacts/components/job_artifacts_table_spec.js
+++ b/spec/frontend/artifacts/components/job_artifacts_table_spec.js
@@ -1,4 +1,12 @@
-import { GlLoadingIcon, GlTable, GlLink, GlBadge, GlPagination, GlModal } from '@gitlab/ui';
+import {
+ GlLoadingIcon,
+ GlTable,
+ GlLink,
+ GlBadge,
+ GlPagination,
+ GlModal,
+ GlFormCheckbox,
+} from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import getJobArtifactsResponse from 'test_fixtures/graphql/artifacts/graphql/queries/get_job_artifacts.query.graphql.json';
@@ -8,15 +16,22 @@ import JobArtifactsTable from '~/artifacts/components/job_artifacts_table.vue';
import FeedbackBanner from '~/artifacts/components/feedback_banner.vue';
import ArtifactsTableRowDetails from '~/artifacts/components/artifacts_table_row_details.vue';
import ArtifactDeleteModal from '~/artifacts/components/artifact_delete_modal.vue';
+import ArtifactsBulkDelete from '~/artifacts/components/artifacts_bulk_delete.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import getJobArtifactsQuery from '~/artifacts/graphql/queries/get_job_artifacts.query.graphql';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { ARCHIVE_FILE_TYPE, JOBS_PER_PAGE, I18N_FETCH_ERROR } from '~/artifacts/constants';
+import {
+ ARCHIVE_FILE_TYPE,
+ JOBS_PER_PAGE,
+ I18N_FETCH_ERROR,
+ INITIAL_CURRENT_PAGE,
+ BULK_DELETE_FEATURE_FLAG,
+} from '~/artifacts/constants';
import { totalArtifactsSizeForJob } from '~/artifacts/utils';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(VueApollo);
@@ -24,6 +39,8 @@ describe('JobArtifactsTable component', () => {
let wrapper;
let requestHandlers;
+ const mockToastShow = jest.fn();
+
const findBanner = () => wrapper.findComponent(FeedbackBanner);
const findLoadingState = () => wrapper.findComponent(GlLoadingIcon);
@@ -55,6 +72,11 @@ describe('JobArtifactsTable component', () => {
const findDeleteButton = () => wrapper.findByTestId('job-artifacts-delete-button');
const findArtifactDeleteButton = () => wrapper.findByTestId('job-artifact-row-delete-button');
+ // first checkbox is a "select all", this finder should get the first job checkbox
+ const findJobCheckbox = () => wrapper.findAllComponents(GlFormCheckbox).at(1);
+ const findAnyCheckbox = () => wrapper.findComponent(GlFormCheckbox);
+ const findBulkDelete = () => wrapper.findComponent(ArtifactsBulkDelete);
+
const findPagination = () => wrapper.findComponent(GlPagination);
const setPage = async (page) => {
findPagination().vm.$emit('input', page);
@@ -69,7 +91,14 @@ describe('JobArtifactsTable component', () => {
];
}
const getJobArtifactsResponseThatPaginates = {
- data: { project: { jobs: { nodes: enoughJobsToPaginate } } },
+ data: {
+ project: {
+ jobs: {
+ nodes: enoughJobsToPaginate,
+ pageInfo: { ...getJobArtifactsResponse.data.project.jobs.pageInfo, hasNextPage: true },
+ },
+ },
+ },
};
const job = getJobArtifactsResponse.data.project.jobs.nodes[0];
@@ -77,13 +106,14 @@ describe('JobArtifactsTable component', () => {
(artifact) => artifact.fileType === ARCHIVE_FILE_TYPE,
);
- const createComponent = (
+ const createComponent = ({
handlers = {
getJobArtifactsQuery: jest.fn().mockResolvedValue(getJobArtifactsResponse),
},
data = {},
canDestroyArtifacts = true,
- ) => {
+ glFeatures = {},
+ } = {}) => {
requestHandlers = handlers;
wrapper = mountExtended(JobArtifactsTable, {
apolloProvider: createMockApollo([
@@ -91,8 +121,15 @@ describe('JobArtifactsTable component', () => {
]),
provide: {
projectPath: 'project/path',
+ projectId: 'gid://projects/id',
canDestroyArtifacts,
artifactsManagementFeedbackImagePath: 'banner/image/path',
+ glFeatures,
+ },
+ mocks: {
+ $toast: {
+ show: mockToastShow,
+ },
},
data() {
return data;
@@ -100,10 +137,6 @@ describe('JobArtifactsTable component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders feedback banner', () => {
createComponent();
@@ -118,7 +151,9 @@ describe('JobArtifactsTable component', () => {
it('on error, shows an alert', async () => {
createComponent({
- getJobArtifactsQuery: jest.fn().mockRejectedValue(new Error('Error!')),
+ handlers: {
+ getJobArtifactsQuery: jest.fn().mockRejectedValue(new Error('Error!')),
+ },
});
await waitForPromises();
@@ -259,10 +294,10 @@ describe('JobArtifactsTable component', () => {
archive: { downloadPath: null },
};
- createComponent(
- { getJobArtifactsQuery: jest.fn() },
- { jobArtifacts: [jobWithoutDownloadPath] },
- );
+ createComponent({
+ handlers: { getJobArtifactsQuery: jest.fn() },
+ data: { jobArtifacts: [jobWithoutDownloadPath] },
+ });
await waitForPromises();
@@ -285,10 +320,10 @@ describe('JobArtifactsTable component', () => {
browseArtifactsPath: null,
};
- createComponent(
- { getJobArtifactsQuery: jest.fn() },
- { jobArtifacts: [jobWithoutBrowsePath] },
- );
+ createComponent({
+ handlers: { getJobArtifactsQuery: jest.fn() },
+ data: { jobArtifacts: [jobWithoutBrowsePath] },
+ });
await waitForPromises();
@@ -298,7 +333,7 @@ describe('JobArtifactsTable component', () => {
describe('delete button', () => {
it('does not show when user does not have permission', async () => {
- createComponent({}, {}, false);
+ createComponent({ canDestroyArtifacts: false });
await waitForPromises();
@@ -314,50 +349,125 @@ describe('JobArtifactsTable component', () => {
});
});
+ describe('bulk delete', () => {
+ describe('with permission and feature flag enabled', () => {
+ beforeEach(async () => {
+ createComponent({
+ canDestroyArtifacts: true,
+ glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true },
+ });
+
+ await waitForPromises();
+ });
+
+ it('shows selected artifacts when a job is checked', async () => {
+ expect(findBulkDelete().exists()).toBe(false);
+
+ await findJobCheckbox().vm.$emit('input', true);
+
+ expect(findBulkDelete().exists()).toBe(true);
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual(
+ job.artifacts.nodes.map((node) => node.id),
+ );
+ });
+
+ it('disappears when selected artifacts are cleared', async () => {
+ await findJobCheckbox().vm.$emit('input', true);
+
+ expect(findBulkDelete().exists()).toBe(true);
+
+ await findBulkDelete().vm.$emit('clearSelectedArtifacts');
+
+ expect(findBulkDelete().exists()).toBe(false);
+ });
+
+ it('shows a toast when artifacts are deleted', async () => {
+ const count = job.artifacts.nodes.length;
+
+ await findJobCheckbox().vm.$emit('input', true);
+ findBulkDelete().vm.$emit('deleted', count);
+
+ expect(mockToastShow).toHaveBeenCalledWith(`${count} selected artifacts deleted`);
+ });
+ });
+
+ it('shows no checkboxes without permission', async () => {
+ createComponent({
+ canDestroyArtifacts: false,
+ glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true },
+ });
+
+ await waitForPromises();
+
+ expect(findAnyCheckbox().exists()).toBe(false);
+ });
+
+ it('shows no checkboxes with feature flag disabled', async () => {
+ createComponent({
+ canDestroyArtifacts: true,
+ glFeatures: { [BULK_DELETE_FEATURE_FLAG]: false },
+ });
+
+ await waitForPromises();
+
+ expect(findAnyCheckbox().exists()).toBe(false);
+ });
+ });
+
describe('pagination', () => {
- const { pageInfo } = getJobArtifactsResponse.data.project.jobs;
+ const { pageInfo } = getJobArtifactsResponseThatPaginates.data.project.jobs;
+ const query = jest.fn().mockResolvedValue(getJobArtifactsResponseThatPaginates);
beforeEach(async () => {
- createComponent(
- {
- getJobArtifactsQuery: jest.fn().mockResolvedValue(getJobArtifactsResponseThatPaginates),
+ createComponent({
+ handlers: {
+ getJobArtifactsQuery: query,
},
- {
- count: enoughJobsToPaginate.length,
- pageInfo,
- },
- );
+ data: { pageInfo },
+ });
await waitForPromises();
});
it('renders pagination and passes page props', () => {
- expect(findPagination().exists()).toBe(true);
expect(findPagination().props()).toMatchObject({
- value: wrapper.vm.pagination.currentPage,
- prevPage: wrapper.vm.prevPage,
- nextPage: wrapper.vm.nextPage,
+ value: INITIAL_CURRENT_PAGE,
+ prevPage: Number(pageInfo.hasPreviousPage),
+ nextPage: Number(pageInfo.hasNextPage),
+ });
+
+ expect(query).toHaveBeenCalledWith({
+ projectPath: 'project/path',
+ firstPageSize: JOBS_PER_PAGE,
+ lastPageSize: null,
+ nextPageCursor: '',
+ prevPageCursor: '',
});
});
- it('updates query variables when going to previous page', () => {
- return setPage(1).then(() => {
- expect(wrapper.vm.queryVariables).toMatchObject({
- projectPath: 'project/path',
- nextPageCursor: undefined,
- prevPageCursor: pageInfo.startCursor,
- });
+ it('updates query variables when going to previous page', async () => {
+ await setPage(1);
+
+ expect(query).toHaveBeenLastCalledWith({
+ projectPath: 'project/path',
+ firstPageSize: null,
+ lastPageSize: JOBS_PER_PAGE,
+ prevPageCursor: pageInfo.startCursor,
});
+ expect(findPagination().props('value')).toEqual(1);
});
- it('updates query variables when going to next page', () => {
- return setPage(2).then(() => {
- expect(wrapper.vm.queryVariables).toMatchObject({
- lastPageSize: null,
- nextPageCursor: pageInfo.endCursor,
- prevPageCursor: '',
- });
+ it('updates query variables when going to next page', async () => {
+ await setPage(2);
+
+ expect(query).toHaveBeenLastCalledWith({
+ projectPath: 'project/path',
+ firstPageSize: JOBS_PER_PAGE,
+ lastPageSize: null,
+ prevPageCursor: '',
+ nextPageCursor: pageInfo.endCursor,
});
+ expect(findPagination().props('value')).toEqual(2);
});
});
});
diff --git a/spec/frontend/artifacts/components/job_checkbox_spec.js b/spec/frontend/artifacts/components/job_checkbox_spec.js
new file mode 100644
index 00000000000..95cc548b8c8
--- /dev/null
+++ b/spec/frontend/artifacts/components/job_checkbox_spec.js
@@ -0,0 +1,71 @@
+import { GlFormCheckbox } from '@gitlab/ui';
+import mockGetJobArtifactsResponse from 'test_fixtures/graphql/artifacts/graphql/queries/get_job_artifacts.query.graphql.json';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import JobCheckbox from '~/artifacts/components/job_checkbox.vue';
+
+describe('JobCheckbox component', () => {
+ let wrapper;
+
+ const mockArtifactNodes = mockGetJobArtifactsResponse.data.project.jobs.nodes[0].artifacts.nodes;
+ const mockSelectedArtifacts = [mockArtifactNodes[0], mockArtifactNodes[1]];
+ const mockUnselectedArtifacts = [mockArtifactNodes[2]];
+
+ const findCheckbox = () => wrapper.findComponent(GlFormCheckbox);
+
+ const createComponent = ({
+ hasArtifacts = true,
+ selectedArtifacts = mockSelectedArtifacts,
+ unselectedArtifacts = mockUnselectedArtifacts,
+ } = {}) => {
+ wrapper = shallowMountExtended(JobCheckbox, {
+ propsData: {
+ hasArtifacts,
+ selectedArtifacts,
+ unselectedArtifacts,
+ },
+ mocks: { GlFormCheckbox },
+ });
+ };
+
+ it('is disabled when the job has no artifacts', () => {
+ createComponent({ hasArtifacts: false });
+
+ expect(findCheckbox().attributes('disabled')).toBe('true');
+ });
+
+ describe('when some artifacts are selected', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('is indeterminate', () => {
+ expect(findCheckbox().attributes('indeterminate')).toBe('true');
+ expect(findCheckbox().attributes('checked')).toBeUndefined();
+ });
+
+ it('selects the unselected artifacts on click', () => {
+ findCheckbox().vm.$emit('input', true);
+
+ expect(wrapper.emitted('selectArtifact')).toMatchObject([[mockUnselectedArtifacts[0], true]]);
+ });
+ });
+
+ describe('when all artifacts are selected', () => {
+ beforeEach(() => {
+ createComponent({ unselectedArtifacts: [] });
+ });
+
+ it('is checked', () => {
+ expect(findCheckbox().attributes('checked')).toBe('true');
+ });
+
+ it('deselects the selected artifacts on click', () => {
+ findCheckbox().vm.$emit('input', false);
+
+ expect(wrapper.emitted('selectArtifact')).toMatchObject([
+ [mockSelectedArtifacts[0], false],
+ [mockSelectedArtifacts[1], false],
+ ]);
+ });
+ });
+});
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 ca94acfa444..efdebe5f3b0 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
@@ -78,8 +78,6 @@ describe('Keep latest artifact checkbox', () => {
};
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
apolloProvider = null;
});
diff --git a/spec/frontend/authentication/u2f/authenticate_spec.js b/spec/frontend/authentication/u2f/authenticate_spec.js
deleted file mode 100644
index 3ae7fcf1c49..00000000000
--- a/spec/frontend/authentication/u2f/authenticate_spec.js
+++ /dev/null
@@ -1,104 +0,0 @@
-import $ from 'jquery';
-import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import U2FAuthenticate from '~/authentication/u2f/authenticate';
-import 'vendor/u2f';
-import MockU2FDevice from './mock_u2f_device';
-
-describe('U2FAuthenticate', () => {
- let u2fDevice;
- let container;
- let component;
-
- beforeEach(() => {
- loadHTMLFixture('u2f/authenticate.html');
- u2fDevice = new MockU2FDevice();
- container = $('#js-authenticate-token-2fa');
- component = new U2FAuthenticate(
- container,
- '#js-login-token-2fa-form',
- {
- sign_requests: [],
- },
- document.querySelector('#js-login-2fa-device'),
- document.querySelector('.js-2fa-form'),
- );
- });
-
- afterEach(() => {
- resetHTMLFixture();
- });
-
- describe('with u2f unavailable', () => {
- let oldu2f;
-
- beforeEach(() => {
- jest.spyOn(component, 'switchToFallbackUI').mockImplementation(() => {});
- oldu2f = window.u2f;
- window.u2f = null;
- });
-
- afterEach(() => {
- window.u2f = oldu2f;
- });
-
- it('falls back to normal 2fa', async () => {
- await component.start();
- expect(component.switchToFallbackUI).toHaveBeenCalled();
- });
- });
-
- describe('with u2f available', () => {
- beforeEach(() => {
- // bypass automatic form submission within renderAuthenticated
- jest.spyOn(component, 'renderAuthenticated').mockReturnValue(true);
- u2fDevice = new MockU2FDevice();
-
- return component.start();
- });
-
- it('allows authenticating via a U2F device', () => {
- const inProgressMessage = container.find('p');
-
- expect(inProgressMessage.text()).toContain('Trying to communicate with your device');
- u2fDevice.respondToAuthenticateRequest({
- deviceData: 'this is data from the device',
- });
-
- expect(component.renderAuthenticated).toHaveBeenCalledWith(
- '{"deviceData":"this is data from the device"}',
- );
- });
-
- describe('errors', () => {
- it('displays an error message', () => {
- const setupButton = container.find('#js-login-2fa-device');
- setupButton.trigger('click');
- u2fDevice.respondToAuthenticateRequest({
- errorCode: 'error!',
- });
- const errorMessage = container.find('p');
-
- expect(errorMessage.text()).toContain('There was a problem communicating with your device');
- });
-
- it('allows retrying authentication after an error', () => {
- let setupButton = container.find('#js-login-2fa-device');
- setupButton.trigger('click');
- u2fDevice.respondToAuthenticateRequest({
- errorCode: 'error!',
- });
- const retryButton = container.find('#js-token-2fa-try-again');
- retryButton.trigger('click');
- setupButton = container.find('#js-login-2fa-device');
- setupButton.trigger('click');
- u2fDevice.respondToAuthenticateRequest({
- deviceData: 'this is data from the device',
- });
-
- expect(component.renderAuthenticated).toHaveBeenCalledWith(
- '{"deviceData":"this is data from the device"}',
- );
- });
- });
- });
-});
diff --git a/spec/frontend/authentication/u2f/mock_u2f_device.js b/spec/frontend/authentication/u2f/mock_u2f_device.js
deleted file mode 100644
index ec8425a4e3e..00000000000
--- a/spec/frontend/authentication/u2f/mock_u2f_device.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable no-unused-expressions */
-
-export default class MockU2FDevice {
- constructor() {
- this.respondToAuthenticateRequest = this.respondToAuthenticateRequest.bind(this);
- this.respondToRegisterRequest = this.respondToRegisterRequest.bind(this);
- window.u2f || (window.u2f = {});
- window.u2f.register = (appId, registerRequests, signRequests, callback) => {
- this.registerCallback = callback;
- };
- window.u2f.sign = (appId, challenges, signRequests, callback) => {
- this.authenticateCallback = callback;
- };
- }
-
- respondToRegisterRequest(params) {
- return this.registerCallback(params);
- }
-
- respondToAuthenticateRequest(params) {
- return this.authenticateCallback(params);
- }
-}
diff --git a/spec/frontend/authentication/u2f/register_spec.js b/spec/frontend/authentication/u2f/register_spec.js
deleted file mode 100644
index 23d1e5c7dee..00000000000
--- a/spec/frontend/authentication/u2f/register_spec.js
+++ /dev/null
@@ -1,84 +0,0 @@
-import $ from 'jquery';
-import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import { trimText } from 'helpers/text_helper';
-import U2FRegister from '~/authentication/u2f/register';
-import 'vendor/u2f';
-import MockU2FDevice from './mock_u2f_device';
-
-describe('U2FRegister', () => {
- let u2fDevice;
- let container;
- let component;
-
- beforeEach(() => {
- loadHTMLFixture('u2f/register.html');
- u2fDevice = new MockU2FDevice();
- container = $('#js-register-token-2fa');
- component = new U2FRegister(container, {});
- return component.start();
- });
-
- afterEach(() => {
- resetHTMLFixture();
- });
-
- it('allows registering a U2F device', () => {
- const setupButton = container.find('#js-setup-token-2fa-device');
-
- expect(trimText(setupButton.text())).toBe('Set up new device');
- setupButton.trigger('click');
- const inProgressMessage = container.children('p');
-
- expect(inProgressMessage.text()).toContain('Trying to communicate with your device');
- u2fDevice.respondToRegisterRequest({
- deviceData: 'this is data from the device',
- });
- const registeredMessage = container.find('p');
- const deviceResponse = container.find('#js-device-response');
-
- expect(registeredMessage.text()).toContain('Your device was successfully set up!');
- expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}');
- });
-
- describe('errors', () => {
- it("doesn't allow the same device to be registered twice (for the same user", () => {
- const setupButton = container.find('#js-setup-token-2fa-device');
- setupButton.trigger('click');
- u2fDevice.respondToRegisterRequest({
- errorCode: 4,
- });
- const errorMessage = container.find('p');
-
- expect(errorMessage.text()).toContain('already been registered with us');
- });
-
- it('displays an error message for other errors', () => {
- const setupButton = container.find('#js-setup-token-2fa-device');
- setupButton.trigger('click');
- u2fDevice.respondToRegisterRequest({
- errorCode: 'error!',
- });
- const errorMessage = container.find('p');
-
- expect(errorMessage.text()).toContain('There was a problem communicating with your device');
- });
-
- it('allows retrying registration after an error', () => {
- let setupButton = container.find('#js-setup-token-2fa-device');
- setupButton.trigger('click');
- u2fDevice.respondToRegisterRequest({
- errorCode: 'error!',
- });
- const retryButton = container.find('#js-token-2fa-try-again');
- retryButton.trigger('click');
- setupButton = container.find('#js-setup-token-2fa-device');
- setupButton.trigger('click');
- u2fDevice.respondToRegisterRequest({
- deviceData: 'this is data from the device',
- });
- const registeredMessage = container.find('p');
-
- expect(registeredMessage.text()).toContain('Your device was successfully set up!');
- });
- });
-});
diff --git a/spec/frontend/authentication/u2f/util_spec.js b/spec/frontend/authentication/u2f/util_spec.js
deleted file mode 100644
index 67fd4c73243..00000000000
--- a/spec/frontend/authentication/u2f/util_spec.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import { canInjectU2fApi } from '~/authentication/u2f/util';
-
-describe('U2F Utils', () => {
- describe('canInjectU2fApi', () => {
- it('returns false for Chrome < 41', () => {
- const userAgent =
- 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.28 Safari/537.36';
-
- expect(canInjectU2fApi(userAgent)).toBe(false);
- });
-
- it('returns true for Chrome >= 41', () => {
- const userAgent =
- 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36';
-
- expect(canInjectU2fApi(userAgent)).toBe(true);
- });
-
- it('returns false for Opera < 40', () => {
- const userAgent =
- 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36 OPR/32.0.1948.25';
-
- expect(canInjectU2fApi(userAgent)).toBe(false);
- });
-
- it('returns true for Opera >= 40', () => {
- const userAgent =
- 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 OPR/43.0.2442.991';
-
- expect(canInjectU2fApi(userAgent)).toBe(true);
- });
-
- it('returns false for Safari', () => {
- const userAgent =
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4';
-
- expect(canInjectU2fApi(userAgent)).toBe(false);
- });
-
- it('returns false for Chrome on Android', () => {
- const userAgent =
- 'Mozilla/5.0 (Linux; Android 7.0; VS988 Build/NRD90U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3145.0 Mobile Safari/537.36';
-
- expect(canInjectU2fApi(userAgent)).toBe(false);
- });
-
- it('returns false for Chrome on iOS', () => {
- const userAgent =
- 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/56.0.2924.75 Mobile/14E5239e Safari/602.1';
-
- expect(canInjectU2fApi(userAgent)).toBe(false);
- });
-
- it('returns false for Safari on iOS', () => {
- const userAgent =
- 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A356 Safari/604.1';
-
- expect(canInjectU2fApi(userAgent)).toBe(false);
- });
- });
-});
diff --git a/spec/frontend/authentication/webauthn/components/registration_spec.js b/spec/frontend/authentication/webauthn/components/registration_spec.js
new file mode 100644
index 00000000000..1221626db7d
--- /dev/null
+++ b/spec/frontend/authentication/webauthn/components/registration_spec.js
@@ -0,0 +1,255 @@
+import { nextTick } from 'vue';
+import { GlAlert, GlButton, GlForm, GlLoadingIcon } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import Registration from '~/authentication/webauthn/components/registration.vue';
+import {
+ I18N_BUTTON_REGISTER,
+ I18N_BUTTON_SETUP,
+ I18N_BUTTON_TRY_AGAIN,
+ I18N_ERROR_HTTP,
+ I18N_ERROR_UNSUPPORTED_BROWSER,
+ I18N_INFO_TEXT,
+ I18N_STATUS_SUCCESS,
+ I18N_STATUS_WAITING,
+ STATE_ERROR,
+ STATE_READY,
+ STATE_SUCCESS,
+ STATE_UNSUPPORTED,
+ STATE_WAITING,
+ WEBAUTHN_REGISTER,
+} from '~/authentication/webauthn/constants';
+import * as WebAuthnUtils from '~/authentication/webauthn/util';
+import WebAuthnError from '~/authentication/webauthn/error';
+
+const csrfToken = 'mock-csrf-token';
+jest.mock('~/lib/utils/csrf', () => ({ token: csrfToken }));
+jest.mock('~/authentication/webauthn/util');
+jest.mock('~/authentication/webauthn/error');
+
+describe('Registration', () => {
+ const initialError = null;
+ const passwordRequired = true;
+ const targetPath = '/-/profile/two_factor_auth/create_webauthn';
+ let wrapper;
+
+ const createComponent = (provide = {}) => {
+ wrapper = shallowMountExtended(Registration, {
+ provide: { initialError, passwordRequired, targetPath, ...provide },
+ });
+ };
+
+ const findButton = () => wrapper.findComponent(GlButton);
+
+ describe(`when ${STATE_UNSUPPORTED} state`, () => {
+ it('shows an error if using unsecure scheme (HTTP)', () => {
+ // `supported` function returns false for HTTP because `navigator.credentials` is undefined.
+ WebAuthnUtils.supported.mockReturnValue(false);
+ WebAuthnUtils.isHTTPS.mockReturnValue(false);
+ createComponent();
+
+ const alert = wrapper.findComponent(GlAlert);
+ expect(alert.props('variant')).toBe('danger');
+ expect(alert.text()).toBe(I18N_ERROR_HTTP);
+ });
+
+ it('shows an error if using unsupported browser', () => {
+ WebAuthnUtils.supported.mockReturnValue(false);
+ WebAuthnUtils.isHTTPS.mockReturnValue(true);
+ createComponent();
+
+ const alert = wrapper.findComponent(GlAlert);
+ expect(alert.props('variant')).toBe('danger');
+ expect(alert.text()).toBe(I18N_ERROR_UNSUPPORTED_BROWSER);
+ });
+ });
+
+ describe('when scheme or browser are supported', () => {
+ const mockCreate = jest.fn();
+
+ const clickSetupDeviceButton = () => {
+ findButton().vm.$emit('click');
+ return nextTick();
+ };
+
+ const setupDevice = () => {
+ clickSetupDeviceButton();
+ return waitForPromises();
+ };
+
+ beforeEach(() => {
+ WebAuthnUtils.isHTTPS.mockReturnValue(true);
+ WebAuthnUtils.supported.mockReturnValue(true);
+ global.navigator.credentials = { create: mockCreate };
+ gon.webauthn = { options: {} };
+ });
+
+ afterEach(() => {
+ global.navigator.credentials = undefined;
+ });
+
+ describe(`when ${STATE_READY} state`, () => {
+ it('shows button and explanation text', () => {
+ createComponent();
+
+ expect(findButton().text()).toBe(I18N_BUTTON_SETUP);
+ expect(wrapper.text()).toContain(I18N_INFO_TEXT);
+ });
+ });
+
+ describe(`when ${STATE_WAITING} state`, () => {
+ it('shows loading icon and message after pressing the button', async () => {
+ createComponent();
+
+ await clickSetupDeviceButton();
+
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.text()).toContain(I18N_STATUS_WAITING);
+ });
+ });
+
+ describe(`when ${STATE_SUCCESS} state`, () => {
+ const credentials = 1;
+
+ const findCurrentPasswordInput = () => wrapper.findByTestId('current-password-input');
+ const findDeviceNameInput = () => wrapper.findByTestId('device-name-input');
+
+ beforeEach(() => {
+ mockCreate.mockResolvedValueOnce(true);
+ WebAuthnUtils.convertCreateResponse.mockReturnValue(credentials);
+ });
+
+ describe('registration form', () => {
+ it('has correct action', async () => {
+ createComponent();
+
+ await setupDevice();
+
+ expect(wrapper.findComponent(GlForm).attributes('action')).toBe(targetPath);
+ });
+
+ describe('when password is required', () => {
+ it('shows device name and password fields', async () => {
+ createComponent();
+
+ await setupDevice();
+
+ expect(wrapper.text()).toContain(I18N_STATUS_SUCCESS);
+
+ // Visible inputs
+ expect(findCurrentPasswordInput().attributes('name')).toBe('current_password');
+ expect(findDeviceNameInput().attributes('name')).toBe('device_registration[name]');
+
+ // Hidden inputs
+ expect(
+ wrapper
+ .find('input[name="device_registration[device_response]"]')
+ .attributes('value'),
+ ).toBe(`${credentials}`);
+ expect(wrapper.find('input[name=authenticity_token]').attributes('value')).toBe(
+ csrfToken,
+ );
+
+ expect(findButton().text()).toBe(I18N_BUTTON_REGISTER);
+ });
+
+ it('enables the register device button when device name and password are filled', async () => {
+ createComponent();
+
+ await setupDevice();
+
+ expect(findButton().props('disabled')).toBe(true);
+
+ // Visible inputs
+ findCurrentPasswordInput().vm.$emit('input', 'my current password');
+ findDeviceNameInput().vm.$emit('input', 'my device name');
+ await nextTick();
+
+ expect(findButton().props('disabled')).toBe(false);
+ });
+ });
+
+ describe('when password is not required', () => {
+ it('shows a device name field', async () => {
+ createComponent({ passwordRequired: false });
+
+ await setupDevice();
+
+ expect(wrapper.text()).toContain(I18N_STATUS_SUCCESS);
+
+ // Visible inputs
+ expect(findCurrentPasswordInput().exists()).toBe(false);
+ expect(findDeviceNameInput().attributes('name')).toBe('device_registration[name]');
+
+ // Hidden inputs
+ expect(
+ wrapper
+ .find('input[name="device_registration[device_response]"]')
+ .attributes('value'),
+ ).toBe(`${credentials}`);
+ expect(wrapper.find('input[name=authenticity_token]').attributes('value')).toBe(
+ csrfToken,
+ );
+
+ expect(findButton().text()).toBe(I18N_BUTTON_REGISTER);
+ });
+
+ it('enables the register device button when device name is filled', async () => {
+ createComponent({ passwordRequired: false });
+
+ await setupDevice();
+
+ expect(findButton().props('disabled')).toBe(true);
+
+ findDeviceNameInput().vm.$emit('input', 'my device name');
+ await nextTick();
+
+ expect(findButton().props('disabled')).toBe(false);
+ });
+ });
+ });
+ });
+
+ describe(`when ${STATE_ERROR} state`, () => {
+ it('shows an initial error message and a retry button', async () => {
+ const myError = 'my error';
+ createComponent({ initialError: myError });
+
+ const alert = wrapper.findComponent(GlAlert);
+ expect(alert.props()).toMatchObject({
+ variant: 'danger',
+ secondaryButtonText: I18N_BUTTON_TRY_AGAIN,
+ });
+ expect(alert.text()).toContain(myError);
+ });
+
+ it('shows an error message and a retry button', async () => {
+ createComponent();
+ const error = new Error();
+ mockCreate.mockRejectedValueOnce(error);
+
+ await setupDevice();
+
+ expect(WebAuthnError).toHaveBeenCalledWith(error, WEBAUTHN_REGISTER);
+ expect(wrapper.findComponent(GlAlert).props()).toMatchObject({
+ variant: 'danger',
+ secondaryButtonText: I18N_BUTTON_TRY_AGAIN,
+ });
+ });
+
+ it('recovers after an error (error to success state)', async () => {
+ createComponent();
+ mockCreate.mockRejectedValueOnce(new Error()).mockResolvedValueOnce(true);
+
+ await setupDevice();
+
+ expect(wrapper.findComponent(GlAlert).props('variant')).toBe('danger');
+
+ wrapper.findComponent(GlAlert).vm.$emit('secondaryAction');
+ await waitForPromises();
+
+ expect(wrapper.findComponent(GlAlert).props('variant')).toBe('info');
+ });
+ });
+ });
+});
diff --git a/spec/frontend/authentication/webauthn/error_spec.js b/spec/frontend/authentication/webauthn/error_spec.js
index 9b71f77dde2..b979173edc6 100644
--- a/spec/frontend/authentication/webauthn/error_spec.js
+++ b/spec/frontend/authentication/webauthn/error_spec.js
@@ -1,16 +1,17 @@
import setWindowLocation from 'helpers/set_window_location_helper';
import WebAuthnError from '~/authentication/webauthn/error';
+import { WEBAUTHN_AUTHENTICATE, WEBAUTHN_REGISTER } from '~/authentication/webauthn/constants';
describe('WebAuthnError', () => {
it.each([
[
'NotSupportedError',
'Your device is not compatible with GitLab. Please try another device',
- 'authenticate',
+ WEBAUTHN_AUTHENTICATE,
],
- ['InvalidStateError', 'This device has not been registered with us.', 'authenticate'],
- ['InvalidStateError', 'This device has already been registered with us.', 'register'],
- ['UnknownError', 'There was a problem communicating with your device.', 'register'],
+ ['InvalidStateError', 'This device has not been registered with us.', WEBAUTHN_AUTHENTICATE],
+ ['InvalidStateError', 'This device has already been registered with us.', WEBAUTHN_REGISTER],
+ ['UnknownError', 'There was a problem communicating with your device.', WEBAUTHN_REGISTER],
])('exception %s will have message %s, flow type: %s', (exception, expectedMessage, flowType) => {
expect(new WebAuthnError(new DOMException('', exception), flowType).message()).toEqual(
expectedMessage,
@@ -24,7 +25,7 @@ describe('WebAuthnError', () => {
const expectedMessage =
'WebAuthn only works with HTTPS-enabled websites. Contact your administrator for more details.';
expect(
- new WebAuthnError(new DOMException('', 'SecurityError'), 'authenticate').message(),
+ new WebAuthnError(new DOMException('', 'SecurityError'), WEBAUTHN_AUTHENTICATE).message(),
).toEqual(expectedMessage);
});
@@ -33,7 +34,7 @@ describe('WebAuthnError', () => {
const expectedMessage = 'There was a problem communicating with your device.';
expect(
- new WebAuthnError(new DOMException('', 'SecurityError'), 'authenticate').message(),
+ new WebAuthnError(new DOMException('', 'SecurityError'), WEBAUTHN_AUTHENTICATE).message(),
).toEqual(expectedMessage);
});
});
diff --git a/spec/frontend/authentication/webauthn/util_spec.js b/spec/frontend/authentication/webauthn/util_spec.js
index bc44b47d0ba..831d1636b8c 100644
--- a/spec/frontend/authentication/webauthn/util_spec.js
+++ b/spec/frontend/authentication/webauthn/util_spec.js
@@ -1,4 +1,9 @@
-import { base64ToBuffer, bufferToBase64, base64ToBase64Url } from '~/authentication/webauthn/util';
+import {
+ base64ToBuffer,
+ bufferToBase64,
+ base64ToBase64Url,
+ supported,
+} from '~/authentication/webauthn/util';
const encodedString = 'SGVsbG8gd29ybGQh';
const stringBytes = [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33];
@@ -31,4 +36,28 @@ describe('Webauthn utils', () => {
expect(base64ToBase64Url(argument)).toBe(expectedResult);
});
});
+
+ describe('supported', () => {
+ afterEach(() => {
+ global.navigator.credentials = undefined;
+ window.PublicKeyCredential = undefined;
+ });
+
+ it.each`
+ credentials | PublicKeyCredential | expected
+ ${undefined} | ${undefined} | ${false}
+ ${{}} | ${undefined} | ${false}
+ ${{ create: true }} | ${undefined} | ${false}
+ ${{ create: true, get: true }} | ${undefined} | ${false}
+ ${{ create: true, get: true }} | ${true} | ${true}
+ `(
+ 'returns $expected when credentials is $credentials and PublicKeyCredential is $PublicKeyCredential',
+ ({ credentials, PublicKeyCredential, expected }) => {
+ global.navigator.credentials = credentials;
+ window.PublicKeyCredential = PublicKeyCredential;
+
+ expect(supported()).toBe(expected);
+ },
+ );
+ });
});
diff --git a/spec/frontend/awards_handler_spec.js b/spec/frontend/awards_handler_spec.js
index 1a54b9909ba..35a1603d375 100644
--- a/spec/frontend/awards_handler_spec.js
+++ b/spec/frontend/awards_handler_spec.js
@@ -6,10 +6,8 @@ import { useFakeRequestAnimationFrame } from 'helpers/fake_request_animation_fra
import loadAwardsHandler from '~/awards_handler';
window.gl = window.gl || {};
-window.gon = window.gon || {};
let awardsHandler = null;
-const urlRoot = gon.relative_url_root;
describe('AwardsHandler', () => {
useFakeRequestAnimationFrame();
@@ -95,9 +93,6 @@ describe('AwardsHandler', () => {
});
afterEach(() => {
- // restore original url root value
- gon.relative_url_root = urlRoot;
-
clearEmojiMock();
// Undo what we did to the shared <body>
diff --git a/spec/frontend/badges/components/badge_form_spec.js b/spec/frontend/badges/components/badge_form_spec.js
index 0a736df7075..d7519f1f80d 100644
--- a/spec/frontend/badges/components/badge_form_spec.js
+++ b/spec/frontend/badges/components/badge_form_spec.js
@@ -43,7 +43,6 @@ describe('BadgeForm component', () => {
});
afterEach(() => {
- wrapper.destroy();
axiosMock.restore();
});
diff --git a/spec/frontend/badges/components/badge_list_row_spec.js b/spec/frontend/badges/components/badge_list_row_spec.js
index ee7ccac974a..cbbeb36ff33 100644
--- a/spec/frontend/badges/components/badge_list_row_spec.js
+++ b/spec/frontend/badges/components/badge_list_row_spec.js
@@ -43,7 +43,6 @@ describe('BadgeListRow component', () => {
};
afterEach(() => {
- wrapper.destroy();
resetHTMLFixture();
});
diff --git a/spec/frontend/badges/components/badge_list_spec.js b/spec/frontend/badges/components/badge_list_spec.js
index 606b1bc9cce..374b7b50af4 100644
--- a/spec/frontend/badges/components/badge_list_spec.js
+++ b/spec/frontend/badges/components/badge_list_spec.js
@@ -38,10 +38,6 @@ describe('BadgeList component', () => {
wrapper = mount(BadgeList, { store });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('for project badges', () => {
it('renders a header with the badge count', () => {
createComponent({
diff --git a/spec/frontend/badges/components/badge_settings_spec.js b/spec/frontend/badges/components/badge_settings_spec.js
index bddb6d3801c..7ad2c99869c 100644
--- a/spec/frontend/badges/components/badge_settings_spec.js
+++ b/spec/frontend/badges/components/badge_settings_spec.js
@@ -32,10 +32,6 @@ describe('BadgeSettings component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays modal if button for deleting a badge is clicked', async () => {
const button = wrapper.find('[data-testid="delete-badge"]');
diff --git a/spec/frontend/badges/components/badge_spec.js b/spec/frontend/badges/components/badge_spec.js
index b468e38f19e..c933c1b5434 100644
--- a/spec/frontend/badges/components/badge_spec.js
+++ b/spec/frontend/badges/components/badge_spec.js
@@ -24,10 +24,6 @@ describe('Badge component', () => {
wrapper = mount(Badge, { propsData });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
beforeEach(() => {
return createComponent({ ...dummyProps }, '#dummy-element');
});
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 c922d6a9809..f667ebc0fcb 100644
--- a/spec/frontend/batch_comments/components/diff_file_drafts_spec.js
+++ b/spec/frontend/batch_comments/components/diff_file_drafts_spec.js
@@ -28,10 +28,6 @@ describe('Batch comments diff file drafts component', () => {
});
}
- afterEach(() => {
- vm.destroy();
- });
-
it('renders list of draft notes', () => {
factory();
diff --git a/spec/frontend/batch_comments/components/draft_note_spec.js b/spec/frontend/batch_comments/components/draft_note_spec.js
index 924d88866ee..159e36c1364 100644
--- a/spec/frontend/batch_comments/components/draft_note_spec.js
+++ b/spec/frontend/batch_comments/components/draft_note_spec.js
@@ -49,10 +49,6 @@ describe('Batch comments draft note component', () => {
draft = createDraft();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders template', () => {
createComponent();
expect(wrapper.findComponent(GlBadge).exists()).toBe(true);
diff --git a/spec/frontend/batch_comments/components/drafts_count_spec.js b/spec/frontend/batch_comments/components/drafts_count_spec.js
index c3a7946c85c..850a7efb4ed 100644
--- a/spec/frontend/batch_comments/components/drafts_count_spec.js
+++ b/spec/frontend/batch_comments/components/drafts_count_spec.js
@@ -15,10 +15,6 @@ describe('Batch comments drafts count component', () => {
wrapper = mount(DraftsCount, { store });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders count', () => {
expect(wrapper.text()).toContain('1');
});
diff --git a/spec/frontend/batch_comments/components/preview_dropdown_spec.js b/spec/frontend/batch_comments/components/preview_dropdown_spec.js
index f86e003ab5f..3a28bf4ade8 100644
--- a/spec/frontend/batch_comments/components/preview_dropdown_spec.js
+++ b/spec/frontend/batch_comments/components/preview_dropdown_spec.js
@@ -1,7 +1,6 @@
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
-import { GlDisclosureDropdown } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
import { visitUrl } from '~/lib/utils/url_utility';
import PreviewDropdown from '~/batch_comments/components/preview_dropdown.vue';
@@ -46,9 +45,11 @@ function factory({ viewDiffsFileByFile = false, draftsCount = 1, sortedDrafts =
},
});
- wrapper = shallowMount(PreviewDropdown, {
+ wrapper = mount(PreviewDropdown, {
store,
- stubs: { GlDisclosureDropdown },
+ stubs: {
+ PreviewItem: true,
+ },
});
}
@@ -59,12 +60,12 @@ describe('Batch comments preview dropdown', () => {
viewDiffsFileByFile: true,
sortedDrafts: [{ id: 1, file_hash: 'hash' }],
});
-
- findPreviewItem().vm.$emit('click');
-
+ findPreviewItem().trigger('click');
await nextTick();
expect(setCurrentFileHash).toHaveBeenCalledWith(expect.anything(), 'hash');
+
+ await nextTick();
expect(scrollToDraft).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ id: 1, file_hash: 'hash' }),
@@ -77,7 +78,7 @@ describe('Batch comments preview dropdown', () => {
sortedDrafts: [{ id: 1 }],
});
- findPreviewItem().vm.$emit('click');
+ findPreviewItem().trigger('click');
await nextTick();
@@ -93,7 +94,7 @@ describe('Batch comments preview dropdown', () => {
sortedDrafts: [{ id: 1, position: { head_sha: '1234' } }],
});
- findPreviewItem().vm.$emit('click');
+ findPreviewItem().trigger('click');
await nextTick();
diff --git a/spec/frontend/batch_comments/components/preview_item_spec.js b/spec/frontend/batch_comments/components/preview_item_spec.js
index 6a99294f855..a19a72af813 100644
--- a/spec/frontend/batch_comments/components/preview_item_spec.js
+++ b/spec/frontend/batch_comments/components/preview_item_spec.js
@@ -26,10 +26,6 @@ describe('Batch comments draft preview item component', () => {
wrapper = mount(PreviewItem, { store, propsData: { draft, isLast } });
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders text content', () => {
createComponent(false, { note_html: '<img src="" /><p>Hello world</p>' });
diff --git a/spec/frontend/batch_comments/components/review_bar_spec.js b/spec/frontend/batch_comments/components/review_bar_spec.js
index 0a4c9ff62e4..923e86a7e64 100644
--- a/spec/frontend/batch_comments/components/review_bar_spec.js
+++ b/spec/frontend/batch_comments/components/review_bar_spec.js
@@ -20,10 +20,6 @@ describe('Batch comments review bar component', () => {
document.body.className = '';
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('adds review-bar-visible class to body when review bar is mounted', async () => {
expect(document.body.classList.contains(REVIEW_BAR_VISIBLE_CLASS_NAME)).toBe(false);
diff --git a/spec/frontend/batch_comments/components/submit_dropdown_spec.js b/spec/frontend/batch_comments/components/submit_dropdown_spec.js
index 003a6d86371..ac6198ec8b5 100644
--- a/spec/frontend/batch_comments/components/submit_dropdown_spec.js
+++ b/spec/frontend/batch_comments/components/submit_dropdown_spec.js
@@ -47,7 +47,6 @@ const findForm = () => wrapper.findByTestId('submit-gl-form');
describe('Batch comments submit dropdown', () => {
afterEach(() => {
- wrapper.destroy();
window.mrTabs = null;
});
diff --git a/spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js b/spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js
index 20eedcbb25b..57bafb51cd6 100644
--- a/spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js
+++ b/spec/frontend/batch_comments/stores/modules/batch_comments/actions_spec.js
@@ -317,4 +317,10 @@ describe('Batch comments store actions', () => {
expect(window.mrTabs.tabShown).toHaveBeenCalledWith('diffs');
});
});
+
+ describe('clearDrafts', () => {
+ it('commits CLEAR_DRAFTS', () => {
+ return testAction(actions.clearDrafts, null, null, [{ type: 'CLEAR_DRAFTS' }], []);
+ });
+ });
});
diff --git a/spec/frontend/batch_comments/stores/modules/batch_comments/mutations_spec.js b/spec/frontend/batch_comments/stores/modules/batch_comments/mutations_spec.js
index fe01de638c2..ad6a1a38164 100644
--- a/spec/frontend/batch_comments/stores/modules/batch_comments/mutations_spec.js
+++ b/spec/frontend/batch_comments/stores/modules/batch_comments/mutations_spec.js
@@ -104,4 +104,14 @@ describe('Batch comments mutations', () => {
]);
});
});
+
+ describe(types.CLEAR_DRAFTS, () => {
+ it('clears drafts array', () => {
+ state.drafts.push({ id: 1 });
+
+ mutations[types.CLEAR_DRAFTS](state);
+
+ expect(state.drafts).toEqual([]);
+ });
+ });
});
diff --git a/spec/frontend/behaviors/components/diagram_performance_warning_spec.js b/spec/frontend/behaviors/components/diagram_performance_warning_spec.js
index c58c2bc55a9..7e6b20da4d4 100644
--- a/spec/frontend/behaviors/components/diagram_performance_warning_spec.js
+++ b/spec/frontend/behaviors/components/diagram_performance_warning_spec.js
@@ -11,10 +11,6 @@ describe('DiagramPerformanceWarning component', () => {
wrapper = shallowMount(DiagramPerformanceWarning);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders warning alert with button', () => {
expect(findAlert().props()).toMatchObject({
primaryButtonText: DiagramPerformanceWarning.i18n.buttonText,
diff --git a/spec/frontend/behaviors/components/json_table_spec.js b/spec/frontend/behaviors/components/json_table_spec.js
index 42b4a051d4d..a82310873ed 100644
--- a/spec/frontend/behaviors/components/json_table_spec.js
+++ b/spec/frontend/behaviors/components/json_table_spec.js
@@ -59,10 +59,6 @@ describe('behaviors/components/json_table', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findTable = () => wrapper.findComponent(GlTable);
const findTableCaption = () => wrapper.findByTestId('slot-table-caption');
const findFilterInput = () => wrapper.findComponent(GlFormInput);
diff --git a/spec/frontend/behaviors/copy_to_clipboard_spec.js b/spec/frontend/behaviors/copy_to_clipboard_spec.js
index c5beaa0ba5d..74a396eb8cb 100644
--- a/spec/frontend/behaviors/copy_to_clipboard_spec.js
+++ b/spec/frontend/behaviors/copy_to_clipboard_spec.js
@@ -31,7 +31,7 @@ describe('initCopyToClipboard', () => {
const defaultButtonAttributes = {
'data-clipboard-text': 'foo bar',
title,
- 'data-title': title,
+ 'data-original-title': title,
};
const createButton = (attributes = {}) => {
const combinedAttributes = { ...defaultButtonAttributes, ...attributes };
diff --git a/spec/frontend/behaviors/markdown/highlight_current_user_spec.js b/spec/frontend/behaviors/markdown/highlight_current_user_spec.js
index 38d19ac3808..ad70efdf7c3 100644
--- a/spec/frontend/behaviors/markdown/highlight_current_user_spec.js
+++ b/spec/frontend/behaviors/markdown/highlight_current_user_spec.js
@@ -22,14 +22,9 @@ describe('highlightCurrentUser', () => {
describe('without current user', () => {
beforeEach(() => {
- window.gon = window.gon || {};
window.gon.current_user_id = null;
});
- afterEach(() => {
- delete window.gon.current_user_id;
- });
-
it('does not highlight the user', () => {
const initialHtml = rootElement.outerHTML;
@@ -41,14 +36,9 @@ describe('highlightCurrentUser', () => {
describe('with current user', () => {
beforeEach(() => {
- window.gon = window.gon || {};
window.gon.current_user_id = 2;
});
- afterEach(() => {
- delete window.gon.current_user_id;
- });
-
it('highlights current user', () => {
highlightCurrentUser(elements);
diff --git a/spec/frontend/behaviors/markdown/render_observability_spec.js b/spec/frontend/behaviors/markdown/render_observability_spec.js
index c87d11742dc..f464c01ac15 100644
--- a/spec/frontend/behaviors/markdown/render_observability_spec.js
+++ b/spec/frontend/behaviors/markdown/render_observability_spec.js
@@ -1,38 +1,43 @@
+import Vue from 'vue';
+import { createWrapper } from '@vue/test-utils';
import renderObservability from '~/behaviors/markdown/render_observability';
-import * as ColorUtils from '~/lib/utils/color_utils';
+import { INLINE_EMBED_DIMENSIONS, SKELETON_VARIANT_EMBED } from '~/observability/constants';
+import ObservabilityApp from '~/observability/components/observability_app.vue';
-describe('Observability iframe renderer', () => {
- const findObservabilityIframes = (theme = 'light') =>
- document.querySelectorAll(`iframe[src="https://observe.gitlab.com/?theme=${theme}&kiosk"]`);
-
- const renderEmbeddedObservability = () => {
- renderObservability([...document.querySelectorAll('.js-render-observability')]);
- jest.runAllTimers();
- };
+describe('renderObservability', () => {
+ let subject;
beforeEach(() => {
- document.body.dataset.page = '';
- document.body.innerHTML = '';
+ subject = document.createElement('div');
+ subject.classList.add('js-render-observability');
+ subject.dataset.frameUrl = 'https://observe.gitlab.com/';
+ document.body.appendChild(subject);
});
- it('renders an observability iframe', () => {
- document.body.innerHTML = `<div class="js-render-observability" data-frame-url="https://observe.gitlab.com/"></div>`;
-
- expect(findObservabilityIframes()).toHaveLength(0);
-
- renderEmbeddedObservability();
-
- expect(findObservabilityIframes()).toHaveLength(1);
+ afterEach(() => {
+ subject.remove();
});
- it('renders iframe with dark param when GL has dark theme', () => {
- document.body.innerHTML = `<div class="js-render-observability" data-frame-url="https://observe.gitlab.com/"></div>`;
- jest.spyOn(ColorUtils, 'darkModeEnabled').mockImplementation(() => true);
+ it('should return an array of Vue instances', () => {
+ const vueInstances = renderObservability([
+ ...document.querySelectorAll('.js-render-observability'),
+ ]);
+ expect(vueInstances).toEqual([expect.any(Vue)]);
+ });
- expect(findObservabilityIframes('dark')).toHaveLength(0);
+ it('should correctly pass props to the ObservabilityApp component', () => {
+ const vueInstances = renderObservability([
+ ...document.querySelectorAll('.js-render-observability'),
+ ]);
- renderEmbeddedObservability();
+ const wrapper = createWrapper(vueInstances[0]);
- expect(findObservabilityIframes('dark')).toHaveLength(1);
+ expect(wrapper.findComponent(ObservabilityApp).props()).toMatchObject({
+ observabilityIframeSrc: 'https://observe.gitlab.com/',
+ skeletonVariant: SKELETON_VARIANT_EMBED,
+ inlineEmbed: true,
+ height: INLINE_EMBED_DIMENSIONS.HEIGHT,
+ width: INLINE_EMBED_DIMENSIONS.WIDTH,
+ });
});
});
diff --git a/spec/frontend/blame/blame_redirect_spec.js b/spec/frontend/blame/blame_redirect_spec.js
index beb10139b3a..326f60a5b13 100644
--- a/spec/frontend/blame/blame_redirect_spec.js
+++ b/spec/frontend/blame/blame_redirect_spec.js
@@ -1,8 +1,8 @@
import redirectToCorrectPage from '~/blame/blame_redirect';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Blame page redirect', () => {
beforeEach(() => {
diff --git a/spec/frontend/blame/streaming/index_spec.js b/spec/frontend/blame/streaming/index_spec.js
new file mode 100644
index 00000000000..e048ce3f70e
--- /dev/null
+++ b/spec/frontend/blame/streaming/index_spec.js
@@ -0,0 +1,110 @@
+import waitForPromises from 'helpers/wait_for_promises';
+import { renderBlamePageStreams } from '~/blame/streaming';
+import { setHTMLFixture } from 'helpers/fixtures';
+import { renderHtmlStreams } from '~/streaming/render_html_streams';
+import { rateLimitStreamRequests } from '~/streaming/rate_limit_stream_requests';
+import { handleStreamedAnchorLink } from '~/streaming/handle_streamed_anchor_link';
+import { toPolyfillReadable } from '~/streaming/polyfills';
+import { createAlert } from '~/alert';
+
+jest.mock('~/streaming/render_html_streams');
+jest.mock('~/streaming/rate_limit_stream_requests');
+jest.mock('~/streaming/handle_streamed_anchor_link');
+jest.mock('~/streaming/polyfills');
+jest.mock('~/sentry');
+jest.mock('~/alert');
+
+global.fetch = jest.fn();
+
+describe('renderBlamePageStreams', () => {
+ let stopAnchor;
+ const PAGES_URL = 'https://example.com/';
+ const findStreamContainer = () => document.querySelector('#blame-stream-container');
+ const findStreamLoadingIndicator = () => document.querySelector('#blame-stream-loading');
+
+ const setupHtml = (totalExtraPages = 0) => {
+ setHTMLFixture(`
+ <div id="blob-content-holder"
+ data-total-extra-pages="${totalExtraPages}"
+ data-pages-url="${PAGES_URL}"
+ ></div>
+ <div id="blame-stream-container"></div>
+ <div id="blame-stream-loading"></div>
+ `);
+ };
+
+ handleStreamedAnchorLink.mockImplementation(() => stopAnchor);
+ rateLimitStreamRequests.mockImplementation(({ factory, total }) => {
+ return Array.from({ length: total }, (_, i) => {
+ return Promise.resolve(factory(i));
+ });
+ });
+ toPolyfillReadable.mockImplementation((obj) => obj);
+
+ beforeEach(() => {
+ stopAnchor = jest.fn();
+ fetch.mockClear();
+ });
+
+ it('does nothing for an empty page', async () => {
+ await renderBlamePageStreams();
+
+ expect(handleStreamedAnchorLink).not.toHaveBeenCalled();
+ expect(renderHtmlStreams).not.toHaveBeenCalled();
+ });
+
+ it('renders a single stream', async () => {
+ let res;
+ const stream = new Promise((resolve) => {
+ res = resolve;
+ });
+ renderHtmlStreams.mockImplementationOnce(() => stream);
+ setupHtml();
+
+ renderBlamePageStreams(stream);
+
+ expect(handleStreamedAnchorLink).toHaveBeenCalledTimes(1);
+ expect(stopAnchor).toHaveBeenCalledTimes(0);
+ expect(renderHtmlStreams).toHaveBeenCalledWith([stream], findStreamContainer());
+ expect(findStreamLoadingIndicator()).not.toBe(null);
+
+ res();
+ await waitForPromises();
+
+ expect(stopAnchor).toHaveBeenCalledTimes(1);
+ expect(findStreamLoadingIndicator()).toBe(null);
+ });
+
+ it('renders rest of the streams', async () => {
+ const stream = Promise.resolve();
+ const stream2 = Promise.resolve({ body: null });
+ fetch.mockImplementationOnce(() => stream2);
+ setupHtml(1);
+
+ await renderBlamePageStreams(stream);
+
+ expect(fetch.mock.calls[0][0].toString()).toBe(`${PAGES_URL}?page=3`);
+ expect(renderHtmlStreams).toHaveBeenCalledWith([stream, stream2], findStreamContainer());
+ });
+
+ it('shows an error message when failed', async () => {
+ const stream = Promise.resolve();
+ const error = new Error();
+ renderHtmlStreams.mockImplementationOnce(() => Promise.reject(error));
+ setupHtml();
+
+ try {
+ await renderBlamePageStreams(stream);
+ } catch (err) {
+ expect(err).toBe(error);
+ }
+
+ expect(createAlert).toHaveBeenCalledWith({
+ message: 'Blame could not be loaded as a single page.',
+ primaryButton: {
+ text: 'View blame as separate pages',
+ clickHandler: expect.any(Function),
+ },
+ });
+ });
+});
diff --git a/spec/frontend/blob/components/blob_content_error_spec.js b/spec/frontend/blob/components/blob_content_error_spec.js
index 0f5885c2acf..203fab94a5c 100644
--- a/spec/frontend/blob/components/blob_content_error_spec.js
+++ b/spec/frontend/blob/components/blob_content_error_spec.js
@@ -18,10 +18,6 @@ describe('Blob Content Error component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('collapsed and too large blobs', () => {
it.each`
error | reason | options
diff --git a/spec/frontend/blob/components/blob_content_spec.js b/spec/frontend/blob/components/blob_content_spec.js
index f7b819b6e94..91af5f7bfed 100644
--- a/spec/frontend/blob/components/blob_content_spec.js
+++ b/spec/frontend/blob/components/blob_content_spec.js
@@ -29,10 +29,6 @@ describe('Blob Content component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('rendering', () => {
it('renders loader if `loading: true`', () => {
createComponent({ loading: true });
diff --git a/spec/frontend/blob/components/blob_edit_header_spec.js b/spec/frontend/blob/components/blob_edit_header_spec.js
index c84b5896348..2b1bd1ac4ad 100644
--- a/spec/frontend/blob/components/blob_edit_header_spec.js
+++ b/spec/frontend/blob/components/blob_edit_header_spec.js
@@ -22,10 +22,6 @@ describe('Blob Header Editing', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('rendering', () => {
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
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 0f015715dc2..e12021a48d2 100644
--- a/spec/frontend/blob/components/blob_header_default_actions_spec.js
+++ b/spec/frontend/blob/components/blob_header_default_actions_spec.js
@@ -34,10 +34,6 @@ describe('Blob Header Default Actions', () => {
buttons = wrapper.findAllComponents(GlButton);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('renders', () => {
const findCopyButton = () => wrapper.findByTestId('copyContentsButton');
const findViewRawButton = () => wrapper.findByTestId('viewRawButton');
diff --git a/spec/frontend/blob/components/blob_header_filepath_spec.js b/spec/frontend/blob/components/blob_header_filepath_spec.js
index 8c32cba1ba4..be49146ff8a 100644
--- a/spec/frontend/blob/components/blob_header_filepath_spec.js
+++ b/spec/frontend/blob/components/blob_header_filepath_spec.js
@@ -21,10 +21,6 @@ describe('Blob Header Filepath', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
const findBadge = () => wrapper.findComponent(GlBadge);
describe('rendering', () => {
diff --git a/spec/frontend/blob/components/blob_header_spec.js b/spec/frontend/blob/components/blob_header_spec.js
index 46740958090..47e09bb38bc 100644
--- a/spec/frontend/blob/components/blob_header_spec.js
+++ b/spec/frontend/blob/components/blob_header_spec.js
@@ -1,9 +1,14 @@
import { shallowMount, mount } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
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';
import ViewerSwitcher from '~/blob/components/blob_header_viewer_switcher.vue';
+import {
+ RICH_BLOB_VIEWER_TITLE,
+ SIMPLE_BLOB_VIEWER,
+ SIMPLE_BLOB_VIEWER_TITLE,
+} from '~/blob/components/constants';
import TableContents from '~/blob/components/table_contents.vue';
import { Blob } from './mock_data';
@@ -11,12 +16,26 @@ import { Blob } from './mock_data';
describe('Blob Header Default Actions', () => {
let wrapper;
- function createComponent(blobProps = {}, options = {}, propsData = {}, shouldMount = false) {
- const method = shouldMount ? mount : shallowMount;
- const blobHash = 'foo-bar';
- wrapper = method.call(this, BlobHeader, {
+ const defaultProvide = {
+ blobHash: 'foo-bar',
+ };
+
+ const findDefaultActions = () => wrapper.findComponent(DefaultActions);
+ const findTableContents = () => wrapper.findComponent(TableContents);
+ const findViewSwitcher = () => wrapper.findComponent(ViewerSwitcher);
+ const findBlobFilePath = () => wrapper.findComponent(BlobFilepath);
+ const findRichTextEditorBtn = () => wrapper.findByLabelText(RICH_BLOB_VIEWER_TITLE);
+ const findSimpleTextEditorBtn = () => wrapper.findByLabelText(SIMPLE_BLOB_VIEWER_TITLE);
+
+ function createComponent({
+ blobProps = {},
+ options = {},
+ propsData = {},
+ mountFn = shallowMount,
+ } = {}) {
+ wrapper = mountFn(BlobHeader, {
provide: {
- blobHash,
+ ...defaultProvide,
},
propsData: {
blob: { ...Blob, ...blobProps },
@@ -26,143 +45,123 @@ describe('Blob Header Default Actions', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('rendering', () => {
- const findDefaultActions = () => wrapper.findComponent(DefaultActions);
-
- const slots = {
- prepend: 'Foo Prepend',
- actions: 'Actions Bar',
- };
-
it('matches the snapshot', () => {
createComponent();
expect(wrapper.element).toMatchSnapshot();
});
- it('renders all components', () => {
- createComponent();
- expect(wrapper.findComponent(TableContents).exists()).toBe(true);
- expect(wrapper.findComponent(ViewerSwitcher).exists()).toBe(true);
- expect(findDefaultActions().exists()).toBe(true);
- expect(wrapper.findComponent(BlobFilepath).exists()).toBe(true);
+ describe('default render', () => {
+ it.each`
+ findComponent | componentName
+ ${findTableContents} | ${'TableContents'}
+ ${findViewSwitcher} | ${'ViewSwitcher'}
+ ${findDefaultActions} | ${'DefaultActions'}
+ ${findBlobFilePath} | ${'BlobFilePath'}
+ `('renders $componentName component by default', ({ findComponent }) => {
+ createComponent();
+
+ expect(findComponent().exists()).toBe(true);
+ });
});
it('does not render viewer switcher if the blob has only the simple viewer', () => {
createComponent({
- richViewer: null,
+ blobProps: {
+ richViewer: null,
+ },
});
- expect(wrapper.findComponent(ViewerSwitcher).exists()).toBe(false);
+ expect(findViewSwitcher().exists()).toBe(false);
});
it('does not render viewer switcher if a corresponding prop is passed', () => {
- createComponent(
- {},
- {},
- {
+ createComponent({
+ propsData: {
hideViewerSwitcher: true,
},
- );
- expect(wrapper.findComponent(ViewerSwitcher).exists()).toBe(false);
+ });
+ expect(findViewSwitcher().exists()).toBe(false);
});
it('does not render default actions is corresponding prop is passed', () => {
- createComponent(
- {},
- {},
- {
+ createComponent({
+ propsData: {
hideDefaultActions: true,
},
- );
- expect(wrapper.findComponent(DefaultActions).exists()).toBe(false);
+ });
+ expect(findDefaultActions().exists()).toBe(false);
});
- Object.keys(slots).forEach((slot) => {
- it('renders the slots', () => {
- const slotContent = slots[slot];
- createComponent(
- {},
- {
- scopedSlots: {
- [slot]: `<span>${slotContent}</span>`,
- },
+ it.each`
+ slotContent | key
+ ${'Foo Prepend'} | ${'prepend'}
+ ${'Actions Bar'} | ${'actions'}
+ `('renders the slot $key', ({ key, slotContent }) => {
+ createComponent({
+ options: {
+ scopedSlots: {
+ [key]: `<span>${slotContent}</span>`,
},
- {},
- true,
- );
- expect(wrapper.text()).toContain(slotContent);
+ },
+ mountFn: mount,
});
+ expect(wrapper.text()).toContain(slotContent);
});
it('passes information about render error down to default actions', () => {
- createComponent(
- {},
- {},
- {
+ createComponent({
+ propsData: {
hasRenderError: true,
},
- );
+ });
expect(findDefaultActions().props('hasRenderError')).toBe(true);
});
it('passes the correct isBinary value to default actions when viewing a binary file', () => {
- createComponent({}, {}, { isBinary: true });
+ createComponent({ propsData: { isBinary: true } });
expect(findDefaultActions().props('isBinary')).toBe(true);
});
});
describe('functionality', () => {
- const newViewer = 'Foo Bar';
- const activeViewerType = 'Alpha Beta';
-
const factory = (hideViewerSwitcher = false) => {
- createComponent(
- {},
- {},
- {
- activeViewerType,
+ createComponent({
+ propsData: {
+ activeViewerType: SIMPLE_BLOB_VIEWER,
hideViewerSwitcher,
},
- );
+ mountFn: mountExtended,
+ });
};
- it('by default sets viewer data based on activeViewerType', () => {
+ it('shows the correctly selected view by default', () => {
factory();
- expect(wrapper.vm.viewer).toBe(activeViewerType);
+
+ expect(findViewSwitcher().exists()).toBe(true);
+ expect(findRichTextEditorBtn().props().selected).toBe(false);
+ expect(findSimpleTextEditorBtn().props().selected).toBe(true);
});
- it('sets viewer to null if the viewer switcher should be hidden', () => {
+ it('Does not show the viewer switcher should be hidden', () => {
factory(true);
- expect(wrapper.vm.viewer).toBe(null);
+
+ expect(findViewSwitcher().exists()).toBe(false);
});
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;
- await nextTick();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('viewer-changed', newViewer);
- });
-
- 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;
+ await findRichTextEditorBtn().trigger('click');
- await nextTick();
- expect(wrapper.vm.$emit).not.toHaveBeenCalled();
+ expect(wrapper.emitted('viewer-changed')).toBeDefined();
});
it('sets different icons depending on the blob file type', async () => {
factory();
- expect(wrapper.vm.blobSwitcherDocIcon).toBe('document');
+
+ expect(findViewSwitcher().props('docIcon')).toBe('document');
+
await wrapper.setProps({
blob: {
...Blob,
@@ -172,7 +171,8 @@ describe('Blob Header Default Actions', () => {
},
},
});
- expect(wrapper.vm.blobSwitcherDocIcon).toBe('table');
+
+ expect(findViewSwitcher().props('docIcon')).toBe('table');
});
});
});
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 1eac0733646..2ef87f6664b 100644
--- a/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js
+++ b/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js
@@ -18,14 +18,14 @@ describe('Blob Header Viewer Switcher', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
+ const findSimpleViewerButton = () => wrapper.findComponent('[data-viewer="simple"]');
+ const findRichViewerButton = () => wrapper.findComponent('[data-viewer="rich"]');
describe('intiialization', () => {
it('is initialized with simple viewer as active', () => {
createComponent();
- expect(wrapper.vm.value).toBe(SIMPLE_BLOB_VIEWER);
+ expect(findSimpleViewerButton().props('selected')).toBe(true);
+ expect(findRichViewerButton().props('selected')).toBe(false);
});
});
@@ -52,45 +52,34 @@ describe('Blob Header Viewer Switcher', () => {
});
describe('viewer changes', () => {
- let buttons;
- let simpleBtn;
- let richBtn;
+ it('does not switch the viewer if the selected one is already active', async () => {
+ createComponent();
+ expect(findSimpleViewerButton().props('selected')).toBe(true);
- function factory(propsData = {}) {
- createComponent(propsData);
- buttons = wrapper.findAllComponents(GlButton);
- simpleBtn = buttons.at(0);
- richBtn = buttons.at(1);
-
- jest.spyOn(wrapper.vm, '$emit');
- }
-
- it('does not switch the viewer if the selected one is already active', () => {
- factory();
- expect(wrapper.vm.value).toBe(SIMPLE_BLOB_VIEWER);
- simpleBtn.vm.$emit('click');
- expect(wrapper.vm.value).toBe(SIMPLE_BLOB_VIEWER);
- expect(wrapper.vm.$emit).not.toHaveBeenCalled();
+ findSimpleViewerButton().vm.$emit('click');
+ await nextTick();
+
+ expect(findSimpleViewerButton().props('selected')).toBe(true);
+ expect(wrapper.emitted('input')).toBe(undefined);
});
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');
+ createComponent();
+ expect(findSimpleViewerButton().props('selected')).toBe(true);
+ findRichViewerButton().vm.$emit('click');
await nextTick();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', RICH_BLOB_VIEWER);
+
+ expect(wrapper.emitted('input')).toEqual([[RICH_BLOB_VIEWER]]);
});
it('emits an event when a Simple Viewer button is clicked', async () => {
- factory({
- value: RICH_BLOB_VIEWER,
- });
- simpleBtn.vm.$emit('click');
+ createComponent({ value: RICH_BLOB_VIEWER });
+ findSimpleViewerButton().vm.$emit('click');
await nextTick();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', SIMPLE_BLOB_VIEWER);
+
+ expect(wrapper.emitted('input')).toEqual([[SIMPLE_BLOB_VIEWER]]);
});
});
});
diff --git a/spec/frontend/blob/components/table_contents_spec.js b/spec/frontend/blob/components/table_contents_spec.js
index 6af9cdcae7d..acfcef9704c 100644
--- a/spec/frontend/blob/components/table_contents_spec.js
+++ b/spec/frontend/blob/components/table_contents_spec.js
@@ -31,7 +31,6 @@ describe('Markdown table of contents component', () => {
});
afterEach(() => {
- wrapper.destroy();
resetHTMLFixture();
});
diff --git a/spec/frontend/blob/csv/csv_viewer_spec.js b/spec/frontend/blob/csv/csv_viewer_spec.js
index 9364f76da5e..8f105f04aa7 100644
--- a/spec/frontend/blob/csv/csv_viewer_spec.js
+++ b/spec/frontend/blob/csv/csv_viewer_spec.js
@@ -29,10 +29,6 @@ describe('app/assets/javascripts/blob/csv/csv_viewer.vue', () => {
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findAlert = () => wrapper.findComponent(PapaParseAlert);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render loading spinner', () => {
createComponent();
diff --git a/spec/frontend/blob/notebook/notebook_viever_spec.js b/spec/frontend/blob/notebook/notebook_viever_spec.js
index 2e7eadc912d..97b32a42afe 100644
--- a/spec/frontend/blob/notebook/notebook_viever_spec.js
+++ b/spec/frontend/blob/notebook/notebook_viever_spec.js
@@ -42,8 +42,6 @@ describe('iPython notebook renderer', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
mock.restore();
});
diff --git a/spec/frontend/blob/pdf/pdf_viewer_spec.js b/spec/frontend/blob/pdf/pdf_viewer_spec.js
index 23227df6357..19d404f504b 100644
--- a/spec/frontend/blob/pdf/pdf_viewer_spec.js
+++ b/spec/frontend/blob/pdf/pdf_viewer_spec.js
@@ -26,11 +26,6 @@ describe('PDF renderer', () => {
mountComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('shows loading icon', () => {
expect(findLoading().exists()).toBe(true);
});
diff --git a/spec/frontend/blob/pipeline_tour_success_modal_spec.js b/spec/frontend/blob/pipeline_tour_success_modal_spec.js
index 81b38cfc278..84efa6041e4 100644
--- a/spec/frontend/blob/pipeline_tour_success_modal_spec.js
+++ b/spec/frontend/blob/pipeline_tour_success_modal_spec.js
@@ -38,7 +38,6 @@ describe('PipelineTourSuccessModal', () => {
});
afterEach(() => {
- wrapper.destroy();
unmockTracking();
Cookies.remove(modalProps.commitCookie);
});
diff --git a/spec/frontend/blob/suggest_gitlab_ci_yml/components/popover_spec.js b/spec/frontend/blob/suggest_gitlab_ci_yml/components/popover_spec.js
index 6b329dc078a..b30b0287a34 100644
--- a/spec/frontend/blob/suggest_gitlab_ci_yml/components/popover_spec.js
+++ b/spec/frontend/blob/suggest_gitlab_ci_yml/components/popover_spec.js
@@ -36,11 +36,6 @@ describe('Suggest gitlab-ci.yml Popover', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when no dismiss cookie is set', () => {
beforeEach(() => {
createWrapper(defaultTrackLabel);
diff --git a/spec/frontend/blob_edit/blob_bundle_spec.js b/spec/frontend/blob_edit/blob_bundle_spec.js
index ed42322b0e6..89d507b4ec5 100644
--- a/spec/frontend/blob_edit/blob_bundle_spec.js
+++ b/spec/frontend/blob_edit/blob_bundle_spec.js
@@ -5,10 +5,10 @@ import waitForPromises from 'helpers/wait_for_promises';
import blobBundle from '~/blob_edit/blob_bundle';
import SourceEditor from '~/blob_edit/edit_blob';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
jest.mock('~/blob_edit/edit_blob');
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('BlobBundle', () => {
it('does not load SourceEditor by default', () => {
diff --git a/spec/frontend/blob_edit/edit_blob_spec.js b/spec/frontend/blob_edit/edit_blob_spec.js
index dda46e97b85..9ab20fc2cd7 100644
--- a/spec/frontend/blob_edit/edit_blob_spec.js
+++ b/spec/frontend/blob_edit/edit_blob_spec.js
@@ -20,9 +20,9 @@ jest.mock('~/editor/extensions/source_editor_toolbar_ext');
const PREVIEW_MARKDOWN_PATH = '/foo/bar/preview_markdown';
const defaultExtensions = [
+ { definition: ToolbarExtension },
{ definition: SourceEditorExtension },
{ definition: FileTemplateExtension },
- { definition: ToolbarExtension },
];
const markdownExtensions = [
{ definition: EditorMarkdownExtension },
diff --git a/spec/frontend/boards/board_card_inner_spec.js b/spec/frontend/boards/board_card_inner_spec.js
index 1e823e3321a..a612e863d46 100644
--- a/spec/frontend/boards/board_card_inner_spec.js
+++ b/spec/frontend/boards/board_card_inner_spec.js
@@ -84,7 +84,7 @@ describe('Board card component', () => {
BoardCardMoveToPosition: true,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
provide: {
rootPath: '/',
@@ -110,8 +110,6 @@ describe('Board card component', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
store = null;
jest.clearAllMocks();
});
@@ -314,10 +312,6 @@ describe('Board card component', () => {
});
});
- afterEach(() => {
- global.gon.default_avatar_url = null;
- });
-
it('displays defaults avatar if users avatar is null', () => {
expect(wrapper.find('.board-card-assignee img').exists()).toBe(true);
expect(wrapper.find('.board-card-assignee img').attributes('src')).toBe(
diff --git a/spec/frontend/boards/board_list_helper.js b/spec/frontend/boards/board_list_helper.js
index d882ff071b7..43cf6ead1c1 100644
--- a/spec/frontend/boards/board_list_helper.js
+++ b/spec/frontend/boards/board_list_helper.js
@@ -92,6 +92,7 @@ export default function createComponent({
boardItems: [issue],
canAdminList: true,
boardId: 'gid://gitlab/Board/1',
+ filterParams: {},
...componentProps,
},
provide: {
diff --git a/spec/frontend/boards/board_list_spec.js b/spec/frontend/boards/board_list_spec.js
index fc8dbf8dc3a..9ec43c6e892 100644
--- a/spec/frontend/boards/board_list_spec.js
+++ b/spec/frontend/boards/board_list_spec.js
@@ -36,10 +36,6 @@ describe('Board list component', () => {
useFakeRequestAnimationFrame();
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('When Expanded', () => {
beforeEach(() => {
wrapper = createComponent({ issuesCount: 1 });
diff --git a/spec/frontend/boards/components/board_add_new_column_form_spec.js b/spec/frontend/boards/components/board_add_new_column_form_spec.js
index 0b3c6cb24c4..4fc9a6859a6 100644
--- a/spec/frontend/boards/components/board_add_new_column_form_spec.js
+++ b/spec/frontend/boards/components/board_add_new_column_form_spec.js
@@ -1,15 +1,13 @@
-import { GlDropdown, GlFormGroup, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
import defaultState from '~/boards/stores/state';
import { mockLabelList } from '../mock_data';
Vue.use(Vuex);
-describe('Board card layout', () => {
+describe('BoardAddNewColumnForm', () => {
let wrapper;
const createStore = ({ actions = {}, getters = {}, state = {} } = {}) => {
@@ -23,56 +21,30 @@ describe('Board card layout', () => {
});
};
- const mountComponent = ({
- loading = false,
- noneSelected = '',
- searchLabel = '',
- searchPlaceholder = '',
- selectedId,
- actions,
- slots,
- } = {}) => {
- wrapper = extendedWrapper(
- shallowMount(BoardAddNewColumnForm, {
- propsData: {
- loading,
- noneSelected,
- searchLabel,
- searchPlaceholder,
- selectedId,
- },
- slots,
- store: createStore({
- actions: {
- setAddColumnFormVisibility: jest.fn(),
- ...actions,
- },
- }),
- stubs: {
- GlDropdown,
+ const mountComponent = ({ searchLabel = '', selectedIdValid = true, actions, slots } = {}) => {
+ wrapper = shallowMountExtended(BoardAddNewColumnForm, {
+ propsData: {
+ searchLabel,
+ selectedIdValid,
+ },
+ slots,
+ store: createStore({
+ actions: {
+ setAddColumnFormVisibility: jest.fn(),
+ ...actions,
},
}),
- );
+ });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const formTitle = () => wrapper.findByTestId('board-add-column-form-title').text();
- const findSearchInput = () => wrapper.findComponent(GlSearchBoxByType);
- const findSearchLabelFormGroup = () => wrapper.findComponent(GlFormGroup);
const cancelButton = () => wrapper.findByTestId('cancelAddNewColumn');
const submitButton = () => wrapper.findByTestId('addNewColumnButton');
- const findDropdown = () => wrapper.findComponent(GlDropdown);
- it('shows form title & search input', () => {
+ it('shows form title', () => {
mountComponent();
- findDropdown().vm.$emit('show');
-
expect(formTitle()).toEqual(BoardAddNewColumnForm.i18n.newList);
- expect(findSearchInput().exists()).toBe(true);
});
it('clicking cancel hides the form', () => {
@@ -88,61 +60,6 @@ describe('Board card layout', () => {
expect(setAddColumnFormVisibility).toHaveBeenCalledWith(expect.anything(), false);
});
- describe('items', () => {
- const mountWithItems = (loading) =>
- mountComponent({
- loading,
- slots: {
- items: '<div class="item-slot">Some kind of list</div>',
- },
- });
-
- it('hides items slot and shows skeleton while loading', () => {
- mountWithItems(true);
-
- expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true);
- expect(wrapper.find('.item-slot').exists()).toBe(false);
- });
-
- it('shows items slot and hides skeleton while not loading', () => {
- mountWithItems(false);
-
- expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(false);
- expect(wrapper.find('.item-slot').exists()).toBe(true);
- });
- });
-
- describe('search box', () => {
- it('sets label and placeholder text from props', () => {
- const props = {
- searchLabel: 'Some items',
- searchPlaceholder: 'Search for an item',
- };
-
- mountComponent(props);
-
- expect(findSearchLabelFormGroup().attributes('label')).toEqual(props.searchLabel);
- expect(findSearchInput().attributes('placeholder')).toEqual(props.searchPlaceholder);
- });
-
- it('does not show the dropdown as invalid by default', () => {
- mountComponent();
-
- expect(findSearchLabelFormGroup().attributes('state')).toBe('true');
- expect(findDropdown().props('toggleClass')).not.toContain('gl-inset-border-1-red-400!');
- });
-
- it('emits filter event on input', () => {
- mountComponent();
-
- const searchText = 'some text';
-
- findSearchInput().vm.$emit('input', searchText);
-
- expect(wrapper.emitted('filter-items')).toEqual([[searchText]]);
- });
- });
-
describe('Add list button', () => {
it('is enabled by default', () => {
mountComponent();
@@ -159,16 +76,5 @@ describe('Board card layout', () => {
expect(wrapper.emitted('add-list')).toEqual([[]]);
});
-
- it('does not emit the add-list event on click and shows the dropdown as invalid when no ID is selected', async () => {
- mountComponent();
-
- await submitButton().vm.$emit('click');
-
- expect(findSearchLabelFormGroup().attributes('state')).toBeUndefined();
- expect(findDropdown().props('toggleClass')).toContain('gl-inset-border-1-red-400!');
-
- expect(wrapper.emitted('add-list')).toBeUndefined();
- });
});
});
diff --git a/spec/frontend/boards/components/board_add_new_column_spec.js b/spec/frontend/boards/components/board_add_new_column_spec.js
index a3b2988ce75..a09c3aaa55e 100644
--- a/spec/frontend/boards/components/board_add_new_column_spec.js
+++ b/spec/frontend/boards/components/board_add_new_column_spec.js
@@ -1,8 +1,7 @@
-import { GlFormRadioGroup } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { GlCollapsibleListbox } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import BoardAddNewColumn from '~/boards/components/board_add_new_column.vue';
import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue';
import defaultState from '~/boards/stores/state';
@@ -13,8 +12,9 @@ Vue.use(Vuex);
describe('Board card layout', () => {
let wrapper;
+ const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
const selectLabel = (id) => {
- wrapper.findComponent(GlFormRadioGroup).vm.$emit('change', id);
+ findDropdown().vm.$emit('select', id);
};
const createStore = ({ actions = {}, getters = {}, state = {} } = {}) => {
@@ -34,33 +34,34 @@ describe('Board card layout', () => {
getListByLabelId = jest.fn(),
actions = {},
} = {}) => {
- wrapper = extendedWrapper(
- shallowMount(BoardAddNewColumn, {
- data() {
- return {
- selectedId,
- };
+ wrapper = shallowMountExtended(BoardAddNewColumn, {
+ data() {
+ return {
+ selectedId,
+ };
+ },
+ store: createStore({
+ actions: {
+ fetchLabels: jest.fn(),
+ setAddColumnFormVisibility: jest.fn(),
+ ...actions,
},
- store: createStore({
- actions: {
- fetchLabels: jest.fn(),
- setAddColumnFormVisibility: jest.fn(),
- ...actions,
- },
- getters: {
- getListByLabelId: () => getListByLabelId,
- },
- state: {
- labels,
- labelsLoading: false,
- },
- }),
- provide: {
- scopedLabelsAvailable: true,
- isEpicBoard: false,
+ getters: {
+ getListByLabelId: () => getListByLabelId,
+ },
+ state: {
+ labels,
+ labelsLoading: false,
},
}),
- );
+ provide: {
+ scopedLabelsAvailable: true,
+ isEpicBoard: false,
+ },
+ stubs: {
+ GlCollapsibleListbox,
+ },
+ });
// trigger change event
if (selectedId) {
@@ -68,10 +69,6 @@ describe('Board card layout', () => {
}
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Add list button', () => {
it('calls addList', async () => {
const getListByLabelId = jest.fn().mockReturnValue(null);
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 354eb7bff16..d8b93e1f3b6 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
@@ -17,7 +17,7 @@ describe('BoardAddNewColumnTrigger', () => {
const mountComponent = () => {
wrapper = mountExtended(BoardAddNewColumnTrigger, {
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
store: createStore(),
});
@@ -27,10 +27,6 @@ describe('BoardAddNewColumnTrigger', () => {
mountComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when button is active', () => {
it('does not show the tooltip', () => {
const tooltip = findTooltipText();
diff --git a/spec/frontend/boards/components/board_app_spec.js b/spec/frontend/boards/components/board_app_spec.js
index 12318fb5d16..148e696b57b 100644
--- a/spec/frontend/boards/components/board_app_spec.js
+++ b/spec/frontend/boards/components/board_app_spec.js
@@ -28,13 +28,12 @@ describe('BoardApp', () => {
store,
provide: {
initialBoardId: 'gid://gitlab/Board/1',
+ initialFilterParams: {},
},
});
};
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
store = null;
});
diff --git a/spec/frontend/boards/components/board_card_spec.js b/spec/frontend/boards/components/board_card_spec.js
index 84e6318d98e..46116bed4cf 100644
--- a/spec/frontend/boards/components/board_card_spec.js
+++ b/spec/frontend/boards/components/board_card_spec.js
@@ -82,8 +82,6 @@ describe('Board card', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
store = null;
});
diff --git a/spec/frontend/boards/components/board_column_spec.js b/spec/frontend/boards/components/board_column_spec.js
index c0bb51620f2..011665eee68 100644
--- a/spec/frontend/boards/components/board_column_spec.js
+++ b/spec/frontend/boards/components/board_column_spec.js
@@ -10,11 +10,6 @@ describe('Board Column Component', () => {
let wrapper;
let store;
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const initStore = () => {
store = createStore();
};
@@ -36,6 +31,7 @@ describe('Board Column Component', () => {
propsData: {
list: listMock,
boardId: 'gid://gitlab/Board/1',
+ filters: {},
},
provide: {
isApolloBoard: false,
diff --git a/spec/frontend/boards/components/board_configuration_options_spec.js b/spec/frontend/boards/components/board_configuration_options_spec.js
index 6f0971a9458..d2948daf121 100644
--- a/spec/frontend/boards/components/board_configuration_options_spec.js
+++ b/spec/frontend/boards/components/board_configuration_options_spec.js
@@ -16,10 +16,6 @@ describe('BoardConfigurationOptions', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const backlogListCheckbox = () => wrapper.find('[data-testid="backlog-list-checkbox"]');
const closedListCheckbox = () => wrapper.find('[data-testid="closed-list-checkbox"]');
diff --git a/spec/frontend/boards/components/board_content_sidebar_spec.js b/spec/frontend/boards/components/board_content_sidebar_spec.js
index 955267a415c..90376a4a553 100644
--- a/spec/frontend/boards/components/board_content_sidebar_spec.js
+++ b/spec/frontend/boards/components/board_content_sidebar_spec.js
@@ -89,10 +89,6 @@ describe('BoardContentSidebar', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('confirms we render GlDrawer', () => {
expect(wrapper.findComponent(GlDrawer).exists()).toBe(true);
});
diff --git a/spec/frontend/boards/components/board_content_spec.js b/spec/frontend/boards/components/board_content_spec.js
index 97596c86198..33351bf8efd 100644
--- a/spec/frontend/boards/components/board_content_spec.js
+++ b/spec/frontend/boards/components/board_content_spec.js
@@ -4,6 +4,8 @@ import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import Draggable from 'vuedraggable';
import Vuex from 'vuex';
+
+import eventHub from '~/boards/eventhub';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
@@ -24,7 +26,6 @@ const actions = {
describe('BoardContent', () => {
let wrapper;
let fakeApollo;
- window.gon = {};
const defaultState = {
isShowingEpicsSwimlanes: false,
@@ -61,6 +62,8 @@ describe('BoardContent', () => {
apolloProvider: fakeApollo,
propsData: {
boardId: 'gid://gitlab/Board/1',
+ filterParams: {},
+ isSwimlanesOn: false,
...props,
},
provide: {
@@ -102,7 +105,6 @@ describe('BoardContent', () => {
});
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
@@ -203,5 +205,14 @@ describe('BoardContent', () => {
it('renders BoardContentSidebar', () => {
expect(wrapper.findComponent(BoardContentSidebar).exists()).toBe(true);
});
+
+ it('refetches lists when updateBoard event is received', async () => {
+ jest.spyOn(eventHub, '$on').mockImplementation(() => {});
+
+ createComponent({ isApolloBoard: true });
+ await waitForPromises();
+
+ expect(eventHub.$on).toHaveBeenCalledWith('updateBoard', wrapper.vm.refetchLists);
+ });
});
});
diff --git a/spec/frontend/boards/components/board_filtered_search_spec.js b/spec/frontend/boards/components/board_filtered_search_spec.js
index 4c0cc36889c..d8bc7f95f18 100644
--- a/spec/frontend/boards/components/board_filtered_search_spec.js
+++ b/spec/frontend/boards/components/board_filtered_search_spec.js
@@ -55,10 +55,10 @@ describe('BoardFilteredSearch', () => {
},
];
- const createComponent = ({ initialFilterParams = {}, props = {} } = {}) => {
+ const createComponent = ({ initialFilterParams = {}, props = {}, provide = {} } = {}) => {
store = createStore();
wrapper = shallowMount(BoardFilteredSearch, {
- provide: { initialFilterParams, fullPath: '' },
+ provide: { initialFilterParams, fullPath: '', isApolloBoard: false, ...provide },
store,
propsData: {
...props,
@@ -69,10 +69,6 @@ describe('BoardFilteredSearch', () => {
const findFilteredSearch = () => wrapper.findComponent(FilteredSearchBarRoot);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
beforeEach(() => {
createComponent();
@@ -191,4 +187,24 @@ describe('BoardFilteredSearch', () => {
]);
});
});
+
+ describe('when Apollo boards FF is on', () => {
+ beforeEach(() => {
+ createComponent({ provide: { isApolloBoard: true } });
+ });
+
+ it('emits setFilters and updates URL when onFilter is emitted', () => {
+ jest.spyOn(urlUtility, 'updateHistory');
+
+ findFilteredSearch().vm.$emit('onFilter', [{ value: { data: '' } }]);
+
+ expect(urlUtility.updateHistory).toHaveBeenCalledWith({
+ title: '',
+ replace: true,
+ url: 'http://test.host/',
+ });
+
+ expect(wrapper.emitted('setFilters')).toHaveLength(1);
+ });
+ });
});
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index f8154145d43..62db59f8f57 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -10,12 +10,14 @@ import { formType } from '~/boards/constants';
import createBoardMutation from '~/boards/graphql/board_create.mutation.graphql';
import destroyBoardMutation from '~/boards/graphql/board_destroy.mutation.graphql';
import updateBoardMutation from '~/boards/graphql/board_update.mutation.graphql';
+import eventHub from '~/boards/eventhub';
import { visitUrl } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
visitUrl: jest.fn().mockName('visitUrlMock'),
}));
+jest.mock('~/boards/eventhub');
Vue.use(Vuex);
@@ -59,18 +61,14 @@ describe('BoardForm', () => {
},
});
- const createComponent = (props, data) => {
+ const createComponent = (props, provide) => {
wrapper = shallowMountExtended(BoardForm, {
propsData: { ...defaultProps, ...props },
- data() {
- return {
- ...data,
- };
- },
provide: {
boardBaseUrl: 'root',
isGroupBoard: true,
isProjectBoard: false,
+ ...provide,
},
mocks: {
$apollo: {
@@ -83,8 +81,6 @@ describe('BoardForm', () => {
};
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
mutate = null;
});
@@ -140,7 +136,7 @@ describe('BoardForm', () => {
it('passes correct primary action text and variant', () => {
expect(findModalActionPrimary().text).toBe('Create board');
- expect(findModalActionPrimary().attributes[0].variant).toBe('confirm');
+ expect(findModalActionPrimary().attributes.variant).toBe('confirm');
});
it('does not render delete confirmation message', () => {
@@ -209,6 +205,30 @@ describe('BoardForm', () => {
expect(setBoardMock).not.toHaveBeenCalled();
expect(setErrorMock).toHaveBeenCalled();
});
+
+ describe('when Apollo boards FF is on', () => {
+ it('calls a correct GraphQL mutation and emits addBoard event when creating a board', async () => {
+ createComponent(
+ { canAdminBoard: true, currentPage: formType.new },
+ { isApolloBoard: true },
+ );
+ fillForm();
+
+ await waitForPromises();
+
+ expect(mutate).toHaveBeenCalledWith({
+ mutation: createBoardMutation,
+ variables: {
+ input: expect.objectContaining({
+ name: 'test',
+ }),
+ },
+ });
+
+ await waitForPromises();
+ expect(wrapper.emitted('addBoard')).toHaveLength(1);
+ });
+ });
});
});
@@ -228,7 +248,7 @@ describe('BoardForm', () => {
it('passes correct primary action text and variant', () => {
expect(findModalActionPrimary().text).toBe('Save changes');
- expect(findModalActionPrimary().attributes[0].variant).toBe('confirm');
+ expect(findModalActionPrimary().attributes.variant).toBe('confirm');
});
it('does not render delete confirmation message', () => {
@@ -308,13 +328,48 @@ describe('BoardForm', () => {
expect(setBoardMock).not.toHaveBeenCalled();
expect(setErrorMock).toHaveBeenCalled();
});
+
+ describe('when Apollo boards FF is on', () => {
+ it('calls a correct GraphQL mutation and emits updateBoard event when updating a board', async () => {
+ mutate = jest.fn().mockResolvedValue({
+ data: {
+ updateBoard: { board: { id: 'gid://gitlab/Board/321', webPath: 'test-path' } },
+ },
+ });
+ setWindowLocation('https://test/boards/1');
+
+ createComponent(
+ { canAdminBoard: true, currentPage: formType.edit },
+ { isApolloBoard: true },
+ );
+ findInput().trigger('keyup.enter', { metaKey: true });
+
+ await waitForPromises();
+
+ expect(mutate).toHaveBeenCalledWith({
+ mutation: updateBoardMutation,
+ variables: {
+ input: expect.objectContaining({
+ id: currentBoard.id,
+ }),
+ },
+ });
+
+ await waitForPromises();
+ expect(eventHub.$emit).toHaveBeenCalledTimes(1);
+ expect(eventHub.$emit).toHaveBeenCalledWith('updateBoard', {
+ id: 'gid://gitlab/Board/321',
+ webPath: 'test-path',
+ });
+ });
+ });
});
describe('when deleting a board', () => {
it('passes correct primary action text and variant', () => {
createComponent({ canAdminBoard: true, currentPage: formType.delete });
expect(findModalActionPrimary().text).toBe('Delete');
- expect(findModalActionPrimary().attributes[0].variant).toBe('danger');
+ expect(findModalActionPrimary().attributes.variant).toBe('danger');
});
it('renders 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 9e65e900440..466321cf1cc 100644
--- a/spec/frontend/boards/components/board_list_header_spec.js
+++ b/spec/frontend/boards/components/board_list_header_spec.js
@@ -1,10 +1,9 @@
-import { shallowMount } from '@vue/test-utils';
+import { GlDisclosureDropdown, GlDisclosureDropdownItem } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import Vuex from 'vuex';
import createMockApollo from 'helpers/mock_apollo_helper';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { boardListQueryResponse, mockLabelList } from 'jest/boards/mock_data';
import BoardListHeader from '~/boards/components/board_list_header.vue';
import { ListType } from '~/boards/constants';
@@ -22,8 +21,6 @@ describe('Board List Header Component', () => {
const toggleListCollapsedSpy = jest.fn();
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
fakeApollo = null;
localStorage.clear();
@@ -64,31 +61,34 @@ describe('Board List Header Component', () => {
fakeApollo = createMockApollo([[listQuery, listQueryHandler]]);
- wrapper = extendedWrapper(
- shallowMount(BoardListHeader, {
- apolloProvider: fakeApollo,
- store,
- propsData: {
- list: listMock,
- },
- provide: {
- boardId,
- weightFeatureAvailable: false,
- currentUserId,
- isEpicBoard: false,
- disabled: false,
- ...injectedProps,
- },
- }),
- );
+ wrapper = shallowMountExtended(BoardListHeader, {
+ apolloProvider: fakeApollo,
+ store,
+ propsData: {
+ list: listMock,
+ filterParams: {},
+ },
+ provide: {
+ boardId,
+ weightFeatureAvailable: false,
+ currentUserId,
+ isEpicBoard: false,
+ disabled: false,
+ ...injectedProps,
+ },
+ stubs: {
+ GlDisclosureDropdown,
+ GlDisclosureDropdownItem,
+ },
+ });
};
+ const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
const isCollapsed = () => wrapper.vm.list.collapsed;
-
- const findAddIssueButton = () => wrapper.findComponent({ ref: 'newIssueBtn' });
const findTitle = () => wrapper.find('.board-title');
const findCaret = () => wrapper.findByTestId('board-title-caret');
- const findSettingsButton = () => wrapper.findComponent({ ref: 'settingsBtn' });
+ const findNewIssueButton = () => wrapper.findByTestId('newIssueBtn');
+ const findSettingsButton = () => wrapper.findByTestId('settingsBtn');
describe('Add issue button', () => {
const hasNoAddButton = [ListType.closed];
@@ -100,59 +100,49 @@ describe('Board List Header Component', () => {
ListType.assignee,
];
- it.each(hasNoAddButton)('does not render when List Type is `%s`', (listType) => {
+ it.each(hasNoAddButton)('does not render dropdown when List Type is `%s`', (listType) => {
createComponent({ listType });
- expect(findAddIssueButton().exists()).toBe(false);
+ expect(findDropdown().exists()).toBe(false);
});
it.each(hasAddButton)('does render when List Type is `%s`', (listType) => {
createComponent({ listType });
- expect(findAddIssueButton().exists()).toBe(true);
+ expect(findDropdown().exists()).toBe(true);
+ expect(findNewIssueButton().exists()).toBe(true);
});
- it('has a test for each list type', () => {
- createComponent();
-
- Object.values(ListType).forEach((value) => {
- expect([...hasAddButton, ...hasNoAddButton]).toContain(value);
- });
- });
-
- it('does not render when logged out', () => {
+ it('does not render dropdown when logged out', () => {
createComponent({
currentUserId: null,
});
- expect(findAddIssueButton().exists()).toBe(false);
+ expect(findDropdown().exists()).toBe(false);
});
});
describe('Settings Button', () => {
- describe('with disabled=true', () => {
- const hasSettings = [
- ListType.assignee,
- ListType.milestone,
- ListType.iteration,
- ListType.label,
- ];
- const hasNoSettings = [ListType.backlog, ListType.closed];
-
- it.each(hasSettings)('does render for List Type `%s` when disabled=true', (listType) => {
- createComponent({ listType, injectedProps: { disabled: true } });
-
- expect(findSettingsButton().exists()).toBe(true);
- });
+ const hasSettings = [ListType.assignee, ListType.milestone, ListType.iteration, ListType.label];
- it.each(hasNoSettings)(
- 'does not render for List Type `%s` when disabled=true',
- (listType) => {
- createComponent({ listType });
+ it.each(hasSettings)('does render for List Type `%s`', (listType) => {
+ createComponent({ listType });
- expect(findSettingsButton().exists()).toBe(false);
- },
- );
+ expect(findDropdown().exists()).toBe(true);
+ expect(findSettingsButton().exists()).toBe(true);
+ });
+
+ it('does not render dropdown when ListType `closed`', () => {
+ createComponent({ listType: ListType.closed });
+
+ expect(findDropdown().exists()).toBe(false);
+ });
+
+ it('renders dropdown but not the Settings button when ListType `backlog`', () => {
+ createComponent({ listType: ListType.backlog });
+
+ expect(findDropdown().exists()).toBe(true);
+ expect(findSettingsButton().exists()).toBe(false);
});
});
diff --git a/spec/frontend/boards/components/board_new_issue_spec.js b/spec/frontend/boards/components/board_new_issue_spec.js
index c3e69ba0e40..651d1daee52 100644
--- a/spec/frontend/boards/components/board_new_issue_spec.js
+++ b/spec/frontend/boards/components/board_new_issue_spec.js
@@ -51,10 +51,6 @@ describe('Issue boards new issue form', () => {
await nextTick();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders board-new-item component', () => {
const boardNewItem = findBoardNewItem();
expect(boardNewItem.exists()).toBe(true);
diff --git a/spec/frontend/boards/components/board_new_item_spec.js b/spec/frontend/boards/components/board_new_item_spec.js
index f4e9901aad2..f11eb2baca7 100644
--- a/spec/frontend/boards/components/board_new_item_spec.js
+++ b/spec/frontend/boards/components/board_new_item_spec.js
@@ -35,10 +35,6 @@ describe('BoardNewItem', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
describe('when the user provides a valid input', () => {
it('finds an enabled create button', async () => {
diff --git a/spec/frontend/boards/components/board_settings_sidebar_spec.js b/spec/frontend/boards/components/board_settings_sidebar_spec.js
index 7d602042685..d0928485caf 100644
--- a/spec/frontend/boards/components/board_settings_sidebar_spec.js
+++ b/spec/frontend/boards/components/board_settings_sidebar_spec.js
@@ -48,7 +48,7 @@ describe('BoardSettingsSidebar', () => {
isIssueBoard: true,
},
directives: {
- GlModal: createMockDirective(),
+ GlModal: createMockDirective('gl-modal'),
},
stubs: {
GlDrawer: stubComponent(GlDrawer, {
@@ -65,8 +65,6 @@ describe('BoardSettingsSidebar', () => {
afterEach(() => {
jest.restoreAllMocks();
- wrapper.destroy();
- wrapper = null;
});
it('finds a MountingPortal component', () => {
diff --git a/spec/frontend/boards/components/board_top_bar_spec.js b/spec/frontend/boards/components/board_top_bar_spec.js
index 8258d9fe7f4..d97a1dbff47 100644
--- a/spec/frontend/boards/components/board_top_bar_spec.js
+++ b/spec/frontend/boards/components/board_top_bar_spec.js
@@ -11,7 +11,7 @@ import ConfigToggle from '~/boards/components/config_toggle.vue';
import IssueBoardFilteredSearch from '~/boards/components/issue_board_filtered_search.vue';
import NewBoardButton from '~/boards/components/new_board_button.vue';
import ToggleFocus from '~/boards/components/toggle_focus.vue';
-import { BoardType } from '~/boards/constants';
+import { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
import groupBoardQuery from '~/boards/graphql/group_board.query.graphql';
import projectBoardQuery from '~/boards/graphql/project_board.query.graphql';
@@ -43,8 +43,9 @@ describe('BoardTopBar', () => {
wrapper = shallowMount(BoardTopBar, {
store,
apolloProvider: mockApollo,
- props: {
+ propsData: {
boardId: 'gid://gitlab/Board/1',
+ isSwimlanesOn: false,
},
provide: {
swimlanesFeatureAvailable: false,
@@ -64,7 +65,6 @@ describe('BoardTopBar', () => {
};
afterEach(() => {
- wrapper.destroy();
mockApollo = null;
});
@@ -96,6 +96,11 @@ describe('BoardTopBar', () => {
it('does not render BoardAddNewColumnTrigger component', () => {
expect(wrapper.findComponent(BoardAddNewColumnTrigger).exists()).toBe(false);
});
+
+ it('emits setFilters when setFilters is emitted by filtered search', () => {
+ wrapper.findComponent(IssueBoardFilteredSearch).vm.$emit('setFilters');
+ expect(wrapper.emitted('setFilters')).toHaveLength(1);
+ });
});
describe('when user can admin list', () => {
@@ -111,14 +116,14 @@ describe('BoardTopBar', () => {
describe('Apollo boards', () => {
it.each`
boardType | queryHandler | notCalledHandler
- ${BoardType.group} | ${groupBoardQueryHandlerSuccess} | ${projectBoardQueryHandlerSuccess}
- ${BoardType.project} | ${projectBoardQueryHandlerSuccess} | ${groupBoardQueryHandlerSuccess}
+ ${WORKSPACE_GROUP} | ${groupBoardQueryHandlerSuccess} | ${projectBoardQueryHandlerSuccess}
+ ${WORKSPACE_PROJECT} | ${projectBoardQueryHandlerSuccess} | ${groupBoardQueryHandlerSuccess}
`('fetches $boardType boards', async ({ boardType, queryHandler, notCalledHandler }) => {
createComponent({
provide: {
boardType,
- isProjectBoard: boardType === BoardType.project,
- isGroupBoard: boardType === BoardType.group,
+ isProjectBoard: boardType === WORKSPACE_PROJECT,
+ isGroupBoard: boardType === WORKSPACE_GROUP,
isApolloBoard: true,
},
});
diff --git a/spec/frontend/boards/components/boards_selector_spec.js b/spec/frontend/boards/components/boards_selector_spec.js
index 28f51e0ecbf..aa146eb4609 100644
--- a/spec/frontend/boards/components/boards_selector_spec.js
+++ b/spec/frontend/boards/components/boards_selector_spec.js
@@ -5,11 +5,11 @@ import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
import { TEST_HOST } from 'spec/test_constants';
import BoardsSelector from '~/boards/components/boards_selector.vue';
-import { BoardType } from '~/boards/constants';
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 { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import {
@@ -116,7 +116,6 @@ describe('BoardsSelector', () => {
};
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
@@ -228,13 +227,13 @@ describe('BoardsSelector', () => {
describe('fetching all boards', () => {
it.each`
boardType | queryHandler | notCalledHandler
- ${BoardType.group} | ${groupBoardsQueryHandlerSuccess} | ${projectBoardsQueryHandlerSuccess}
- ${BoardType.project} | ${projectBoardsQueryHandlerSuccess} | ${groupBoardsQueryHandlerSuccess}
+ ${WORKSPACE_GROUP} | ${groupBoardsQueryHandlerSuccess} | ${projectBoardsQueryHandlerSuccess}
+ ${WORKSPACE_PROJECT} | ${projectBoardsQueryHandlerSuccess} | ${groupBoardsQueryHandlerSuccess}
`('fetches $boardType boards', async ({ boardType, queryHandler, notCalledHandler }) => {
createStore();
createComponent({
- isGroupBoard: boardType === BoardType.group,
- isProjectBoard: boardType === BoardType.project,
+ isGroupBoard: boardType === WORKSPACE_GROUP,
+ isProjectBoard: boardType === WORKSPACE_PROJECT,
});
await nextTick();
diff --git a/spec/frontend/boards/components/config_toggle_spec.js b/spec/frontend/boards/components/config_toggle_spec.js
index 47d4692453d..5330721451e 100644
--- a/spec/frontend/boards/components/config_toggle_spec.js
+++ b/spec/frontend/boards/components/config_toggle_spec.js
@@ -23,10 +23,6 @@ describe('ConfigToggle', () => {
const findButton = () => wrapper.findComponent(GlButton);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a button with label `View scope` when `canAdminList` is `false`', () => {
wrapper = createComponent({ canAdminList: false });
expect(findButton().text()).toBe('View scope');
diff --git a/spec/frontend/boards/components/issue_board_filtered_search_spec.js b/spec/frontend/boards/components/issue_board_filtered_search_spec.js
index 57a30ddc512..5b5b68d5dbe 100644
--- a/spec/frontend/boards/components/issue_board_filtered_search_spec.js
+++ b/spec/frontend/boards/components/issue_board_filtered_search_spec.js
@@ -2,10 +2,10 @@ import { orderBy } from 'lodash';
import { shallowMount } from '@vue/test-utils';
import BoardFilteredSearch from 'ee_else_ce/boards/components/board_filtered_search.vue';
import IssueBoardFilteredSpec from '~/boards/components/issue_board_filtered_search.vue';
-import issueBoardFilters from '~/boards/issue_board_filters';
+import issueBoardFilters from 'ee_else_ce/boards/issue_board_filters';
import { mockTokens } from '../mock_data';
-jest.mock('~/boards/issue_board_filters');
+jest.mock('ee_else_ce/boards/issue_board_filters');
describe('IssueBoardFilter', () => {
let wrapper;
@@ -14,6 +14,9 @@ describe('IssueBoardFilter', () => {
const createComponent = ({ isSignedIn = false } = {}) => {
wrapper = shallowMount(IssueBoardFilteredSpec, {
+ propsData: {
+ boardId: 'gid://gitlab/Board/1',
+ },
provide: {
isSignedIn,
releasesFetchPath: '/releases',
@@ -35,10 +38,6 @@ describe('IssueBoardFilter', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
beforeEach(() => {
createComponent();
@@ -48,6 +47,11 @@ describe('IssueBoardFilter', () => {
expect(findBoardsFilteredSearch().exists()).toBe(true);
});
+ it('emits setFilters when setFilters is emitted', () => {
+ findBoardsFilteredSearch().vm.$emit('setFilters');
+ expect(wrapper.emitted('setFilters')).toHaveLength(1);
+ });
+
it.each`
isSignedIn
${true}
diff --git a/spec/frontend/boards/components/issue_due_date_spec.js b/spec/frontend/boards/components/issue_due_date_spec.js
index 45fa10bf03a..dee8febfe4d 100644
--- a/spec/frontend/boards/components/issue_due_date_spec.js
+++ b/spec/frontend/boards/components/issue_due_date_spec.js
@@ -20,10 +20,6 @@ describe('Issue Due Date component', () => {
date = new Date();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render "Today" if the due date is today', () => {
wrapper = createComponent();
diff --git a/spec/frontend/boards/components/issue_time_estimate_spec.js b/spec/frontend/boards/components/issue_time_estimate_spec.js
index 948a7a20f7f..42507ef560b 100644
--- a/spec/frontend/boards/components/issue_time_estimate_spec.js
+++ b/spec/frontend/boards/components/issue_time_estimate_spec.js
@@ -7,10 +7,6 @@ describe('Issue Time Estimate component', () => {
const findIssueTimeEstimate = () => wrapper.find('[data-testid="issue-time-estimate"]');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when limitToHours is false', () => {
beforeEach(() => {
wrapper = shallowMount(IssueTimeEstimate, {
diff --git a/spec/frontend/boards/components/item_count_spec.js b/spec/frontend/boards/components/item_count_spec.js
index 0c0c7f66933..f2cc8eb1167 100644
--- a/spec/frontend/boards/components/item_count_spec.js
+++ b/spec/frontend/boards/components/item_count_spec.js
@@ -41,10 +41,6 @@ describe('IssueCount', () => {
createComponent({ maxIssueCount, itemsSize });
});
- afterEach(() => {
- vm.destroy();
- });
-
it('contains issueSize in the template', () => {
expect(vm.find('[data-testid="board-items-count"]').text()).toEqual(String(itemsSize));
});
@@ -66,10 +62,6 @@ describe('IssueCount', () => {
createComponent({ maxIssueCount, itemsSize });
});
- afterEach(() => {
- vm.destroy();
- });
-
it('contains issueSize in the template', () => {
expect(vm.find('[data-testid="board-items-count"]').text()).toEqual(String(itemsSize));
});
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 5e2222ac3d7..6dbeac3864f 100644
--- a/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_editable_item_spec.js
@@ -21,11 +21,6 @@ describe('boards sidebar remove issue', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('template', () => {
it('renders title', () => {
const title = 'Sidebar item title';
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js
index e2e4baefad0..b01ee01120e 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_time_tracker_spec.js
@@ -37,11 +37,6 @@ describe('BoardSidebarTimeTracker', () => {
store.state.activeId = '1';
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it.each`
timeTrackingLimitToHours | canUpdate
${true} | ${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 bc66a0515aa..a20884baf3b 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js
@@ -27,9 +27,7 @@ describe('~/boards/components/sidebar/board_sidebar_title.vue', () => {
afterEach(() => {
localStorage.clear();
- wrapper.destroy();
store = null;
- wrapper = null;
});
const createWrapper = (item = TEST_ISSUE_A) => {
diff --git a/spec/frontend/boards/components/toggle_focus_spec.js b/spec/frontend/boards/components/toggle_focus_spec.js
index 3cbaac91f8d..cad287954d7 100644
--- a/spec/frontend/boards/components/toggle_focus_spec.js
+++ b/spec/frontend/boards/components/toggle_focus_spec.js
@@ -10,7 +10,7 @@ describe('ToggleFocus', () => {
const createComponent = () => {
wrapper = shallowMountExtended(ToggleFocus, {
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
attachTo: document.body,
});
@@ -18,10 +18,6 @@ describe('ToggleFocus', () => {
const findButton = () => wrapper.findComponent(GlButton);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a button with `maximize` icon', () => {
createComponent();
diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js
index 1d011eacf1c..e5167120542 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -477,6 +477,9 @@ export const mockList = {
loading: false,
issuesCount: 1,
maxIssueCount: 0,
+ metadata: {
+ epicsCount: 1,
+ },
__typename: 'BoardList',
};
@@ -915,6 +918,7 @@ export const epicBoardListQueryResponse = (totalWeight = 5) => ({
__typename: 'EpicList',
id: 'gid://gitlab/Boards::EpicList/3',
metadata: {
+ epicsCount: 1,
totalWeight,
},
},
diff --git a/spec/frontend/boards/project_select_spec.js b/spec/frontend/boards/project_select_spec.js
index 4324e7068e0..74ce4b6b786 100644
--- a/spec/frontend/boards/project_select_spec.js
+++ b/spec/frontend/boards/project_select_spec.js
@@ -71,11 +71,6 @@ describe('ProjectSelect component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('displays a header title', () => {
createWrapper();
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
index ab959abaa99..f430062bb73 100644
--- a/spec/frontend/boards/stores/actions_spec.js
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -2,13 +2,7 @@ import * as Sentry from '@sentry/browser';
import { cloneDeep } from 'lodash';
import Vue from 'vue';
import Vuex from 'vuex';
-import {
- inactiveId,
- ISSUABLE,
- ListType,
- BoardType,
- DraggableItemTypes,
-} from 'ee_else_ce/boards/constants';
+import { inactiveId, ISSUABLE, ListType, DraggableItemTypes } from 'ee_else_ce/boards/constants';
import issueMoveListMutation from 'ee_else_ce/boards/graphql/issue_move_list.mutation.graphql';
import testAction from 'helpers/vuex_action_helper';
import {
@@ -26,7 +20,7 @@ import actions from '~/boards/stores/actions';
import * as types from '~/boards/stores/mutation_types';
import mutations from '~/boards/stores/mutations';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_ISSUE, WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
import projectBoardMilestones from '~/boards/graphql/project_board_milestones.query.graphql';
import groupBoardMilestones from '~/boards/graphql/group_board_milestones.query.graphql';
@@ -49,7 +43,7 @@ import {
mockMilestones,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
// We need this helper to make sure projectPath is including
// subgroups when the movIssue action is called.
@@ -300,8 +294,8 @@ describe('fetchLists', () => {
it.each`
issuableType | boardType | fullBoardId | isGroup | isProject
- ${TYPE_ISSUE} | ${BoardType.group} | ${'gid://gitlab/Board/1'} | ${true} | ${false}
- ${TYPE_ISSUE} | ${BoardType.project} | ${'gid://gitlab/Board/1'} | ${false} | ${true}
+ ${TYPE_ISSUE} | ${WORKSPACE_GROUP} | ${'gid://gitlab/Board/1'} | ${true} | ${false}
+ ${TYPE_ISSUE} | ${WORKSPACE_PROJECT} | ${'gid://gitlab/Board/1'} | ${false} | ${true}
`(
'calls $issuableType query with correct variables',
async ({ issuableType, boardType, fullBoardId, isGroup, isProject }) => {
@@ -336,7 +330,7 @@ describe('fetchLists', () => {
describe('fetchMilestones', () => {
const queryResponse = {
data: {
- project: {
+ workspace: {
milestones: {
nodes: mockMilestones,
},
@@ -346,7 +340,7 @@ describe('fetchMilestones', () => {
const queryErrors = {
data: {
- project: {
+ workspace: {
errors: ['You cannot view these milestones'],
milestones: {},
},
diff --git a/spec/frontend/boards/stores/getters_spec.js b/spec/frontend/boards/stores/getters_spec.js
index c86a256bd96..944a7493504 100644
--- a/spec/frontend/boards/stores/getters_spec.js
+++ b/spec/frontend/boards/stores/getters_spec.js
@@ -31,10 +31,6 @@ describe('Boards - Getters', () => {
});
describe('isSwimlanesOn', () => {
- afterEach(() => {
- window.gon = { features: {} };
- });
-
it('returns false', () => {
expect(getters.isSwimlanesOn()).toBe(false);
});
@@ -171,10 +167,6 @@ describe('Boards - Getters', () => {
});
describe('isEpicBoard', () => {
- afterEach(() => {
- window.gon = { features: {} };
- });
-
it('returns false', () => {
expect(getters.isEpicBoard()).toBe(false);
});
diff --git a/spec/frontend/branches/components/delete_branch_button_spec.js b/spec/frontend/branches/components/delete_branch_button_spec.js
index b029f34c3d7..5b2ec443c59 100644
--- a/spec/frontend/branches/components/delete_branch_button_spec.js
+++ b/spec/frontend/branches/components/delete_branch_button_spec.js
@@ -25,10 +25,6 @@ describe('Delete branch button', () => {
eventHubSpy = jest.spyOn(eventHub, '$emit');
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the button with default tooltip, style, and icon', () => {
createComponent();
diff --git a/spec/frontend/branches/components/delete_branch_modal_spec.js b/spec/frontend/branches/components/delete_branch_modal_spec.js
index c977868ca93..dd5b7fca564 100644
--- a/spec/frontend/branches/components/delete_branch_modal_spec.js
+++ b/spec/frontend/branches/components/delete_branch_modal_spec.js
@@ -52,10 +52,6 @@ describe('Delete branch modal', () => {
const expectedUnmergedWarning =
"This branch hasn't been merged into default. To avoid data loss, consider merging this branch before deleting it.";
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Deleting a regular branch', () => {
const expectedTitle = 'Delete branch. Are you ABSOLUTELY SURE?';
const expectedWarning = "You're about to permanently delete the branch test_modal.";
diff --git a/spec/frontend/branches/components/delete_merged_branches_spec.js b/spec/frontend/branches/components/delete_merged_branches_spec.js
index 4f1e772f4a4..75a669c78f2 100644
--- a/spec/frontend/branches/components/delete_merged_branches_spec.js
+++ b/spec/frontend/branches/components/delete_merged_branches_spec.js
@@ -27,7 +27,7 @@ const createComponent = (mountFn = shallowMountExtended, stubs = {}) => {
...propsDataMock,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
stubs,
});
@@ -78,10 +78,6 @@ describe('Delete merged branches component', () => {
createComponent(shallowMountExtended, stubsData);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders correct modal title and text', () => {
const modalText = findModal().text();
expect(findModal().props('title')).toBe(i18n.modalTitle);
diff --git a/spec/frontend/branches/components/divergence_graph_spec.js b/spec/frontend/branches/components/divergence_graph_spec.js
index 9429a6e982c..66193c2ebf0 100644
--- a/spec/frontend/branches/components/divergence_graph_spec.js
+++ b/spec/frontend/branches/components/divergence_graph_spec.js
@@ -9,10 +9,6 @@ function factory(propsData = {}) {
}
describe('Branch divergence graph component', () => {
- afterEach(() => {
- vm.destroy();
- });
-
it('renders ahead and behind count', () => {
factory({
defaultBranch: 'main',
diff --git a/spec/frontend/branches/components/graph_bar_spec.js b/spec/frontend/branches/components/graph_bar_spec.js
index 61c051b49c6..585b376081b 100644
--- a/spec/frontend/branches/components/graph_bar_spec.js
+++ b/spec/frontend/branches/components/graph_bar_spec.js
@@ -8,10 +8,6 @@ function factory(propsData = {}) {
}
describe('Branch divergence graph bar component', () => {
- afterEach(() => {
- vm.destroy();
- });
-
it.each`
position | positionClass
${'left'} | ${'position-right-0'}
diff --git a/spec/frontend/captcha/captcha_modal_spec.js b/spec/frontend/captcha/captcha_modal_spec.js
index 20e69b5a834..6d6d8043797 100644
--- a/spec/frontend/captcha/captcha_modal_spec.js
+++ b/spec/frontend/captcha/captcha_modal_spec.js
@@ -1,6 +1,5 @@
import { GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
import { stubComponent } from 'helpers/stub_component';
import CaptchaModal from '~/captcha/captcha_modal.vue';
import { initRecaptchaScript } from '~/captcha/init_recaptcha_script';
@@ -9,10 +8,11 @@ jest.mock('~/captcha/init_recaptcha_script');
describe('Captcha Modal', () => {
let wrapper;
- let modal;
let grecaptcha;
const captchaSiteKey = 'abc123';
+ const showSpy = jest.fn();
+ const hideSpy = jest.fn();
function createComponent({ props = {} } = {}) {
wrapper = shallowMount(CaptchaModal, {
@@ -21,11 +21,18 @@ describe('Captcha Modal', () => {
...props,
},
stubs: {
- GlModal: stubComponent(GlModal),
+ GlModal: stubComponent(GlModal, {
+ methods: {
+ show: showSpy,
+ hide: hideSpy,
+ },
+ }),
},
});
}
+ const findGlModal = () => wrapper.findComponent(GlModal);
+
beforeEach(() => {
grecaptcha = {
render: jest.fn(),
@@ -34,38 +41,17 @@ describe('Captcha Modal', () => {
initRecaptchaScript.mockResolvedValue(grecaptcha);
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- const findGlModal = () => {
- const glModal = wrapper.findComponent(GlModal);
-
- jest.spyOn(glModal.vm, 'show').mockImplementation(() => glModal.vm.$emit('shown'));
- jest
- .spyOn(glModal.vm, 'hide')
- .mockImplementation(() => glModal.vm.$emit('hide', { trigger: '' }));
-
- return glModal;
- };
-
- const showModal = () => {
- wrapper.setProps({ needsCaptchaResponse: true });
- };
-
- beforeEach(() => {
- createComponent();
- modal = findGlModal();
- });
-
describe('rendering', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
it('renders', () => {
- expect(modal.exists()).toBe(true);
+ expect(findGlModal().exists()).toBe(true);
});
it('assigns the modal a unique ID', () => {
- const firstInstanceModalId = modal.props('modalId');
+ const firstInstanceModalId = findGlModal().props('modalId');
createComponent();
const secondInstanceModalId = findGlModal().props('modalId');
expect(firstInstanceModalId).not.toEqual(secondInstanceModalId);
@@ -76,13 +62,12 @@ describe('Captcha Modal', () => {
describe('when modal is shown', () => {
describe('when initRecaptchaScript promise resolves successfully', () => {
beforeEach(async () => {
- showModal();
-
- await nextTick();
+ createComponent({ props: { needsCaptchaResponse: true } });
+ findGlModal().vm.$emit('shown');
});
it('shows modal', async () => {
- expect(findGlModal().vm.show).toHaveBeenCalled();
+ expect(showSpy).toHaveBeenCalled();
});
it('renders window.grecaptcha', () => {
@@ -108,7 +93,7 @@ describe('Captcha Modal', () => {
it('hides modal with null trigger', async () => {
// Assert that hide is called with zero args, so that we don't trigger the logic
// for hiding the modal via cancel, esc, headerclose, etc, without a captcha response
- expect(modal.vm.hide).toHaveBeenCalledWith();
+ expect(hideSpy).toHaveBeenCalledWith();
});
});
@@ -127,7 +112,7 @@ describe('Captcha Modal', () => {
const bvModalEvent = {
trigger,
};
- modal.vm.$emit('hide', bvModalEvent);
+ findGlModal().vm.$emit('hide', bvModalEvent);
});
it(`emits receivedCaptchaResponse with ${JSON.stringify(expected)}`, () => {
@@ -141,21 +126,24 @@ describe('Captcha Modal', () => {
const fakeError = {};
beforeEach(() => {
- initRecaptchaScript.mockImplementation(() => Promise.reject(fakeError));
+ createComponent({
+ props: { needsCaptchaResponse: true },
+ });
+ initRecaptchaScript.mockImplementation(() => Promise.reject(fakeError));
jest.spyOn(console, 'error').mockImplementation();
- showModal();
+ findGlModal().vm.$emit('shown');
});
it('emits receivedCaptchaResponse exactly once with null', () => {
expect(wrapper.emitted('receivedCaptchaResponse')).toEqual([[null]]);
});
- it('hides modal with null trigger', async () => {
+ it('hides modal with null trigger', () => {
// Assert that hide is called with zero args, so that we don't trigger the logic
// for hiding the modal via cancel, esc, headerclose, etc, without a captcha response
- expect(modal.vm.hide).toHaveBeenCalledWith();
+ expect(hideSpy).toHaveBeenCalledWith();
});
it('calls console.error with a message and the exception', () => {
diff --git a/spec/frontend/ci/ci_lint/components/ci_lint_spec.js b/spec/frontend/ci/ci_lint/components/ci_lint_spec.js
index d4f588a0e09..4b7ca36f331 100644
--- a/spec/frontend/ci/ci_lint/components/ci_lint_spec.js
+++ b/spec/frontend/ci/ci_lint/components/ci_lint_spec.js
@@ -48,7 +48,6 @@ describe('CI Lint', () => {
afterEach(() => {
mockMutate.mockClear();
- wrapper.destroy();
});
it('displays the editor', () => {
diff --git a/spec/frontend/ci/ci_variable_list/components/ci_admin_variables_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_admin_variables_spec.js
index 5e0c35c9f90..8e012883f09 100644
--- a/spec/frontend/ci/ci_variable_list/components/ci_admin_variables_spec.js
+++ b/spec/frontend/ci/ci_variable_list/components/ci_admin_variables_spec.js
@@ -16,10 +16,6 @@ describe('Ci Project Variable wrapper', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Passes down the correct props to ci_variable_shared', () => {
expect(findCiShared().props()).toEqual({
areScopedVariablesAvailable: false,
diff --git a/spec/frontend/ci/ci_variable_list/components/ci_environments_dropdown_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_environments_dropdown_spec.js
index 2fd395a1230..7181398c2a6 100644
--- a/spec/frontend/ci/ci_variable_list/components/ci_environments_dropdown_spec.js
+++ b/spec/frontend/ci/ci_variable_list/components/ci_environments_dropdown_spec.js
@@ -27,10 +27,6 @@ describe('Ci environments dropdown', () => {
findListbox().vm.$emit('search', searchTerm);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('No environments found', () => {
beforeEach(() => {
createComponent({ searchTerm: 'stable' });
diff --git a/spec/frontend/ci/ci_variable_list/components/ci_group_variables_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_group_variables_spec.js
index c0fb133b9b1..77d90a7667d 100644
--- a/spec/frontend/ci/ci_variable_list/components/ci_group_variables_spec.js
+++ b/spec/frontend/ci/ci_variable_list/components/ci_group_variables_spec.js
@@ -24,10 +24,6 @@ describe('Ci Group Variable wrapper', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Props', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/ci/ci_variable_list/components/ci_project_variables_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_project_variables_spec.js
index bd1e6b17d6b..ce5237a84f7 100644
--- a/spec/frontend/ci/ci_variable_list/components/ci_project_variables_spec.js
+++ b/spec/frontend/ci/ci_variable_list/components/ci_project_variables_spec.js
@@ -25,10 +25,6 @@ describe('Ci Project Variable wrapper', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Passes down the correct props to ci_variable_shared', () => {
expect(findCiShared().props()).toEqual({
id: convertToGraphQLId(TYPENAME_PROJECT, mockProvide.projectId),
diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js
index 508af964ca3..8f3fccc2804 100644
--- a/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js
+++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_modal_spec.js
@@ -85,10 +85,6 @@ describe('Ci variable modal', () => {
const findVariableTypeDropdown = () => wrapper.find('#ci-variable-type');
const findEnvironmentScopeText = () => wrapper.findByText('Environment scope');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Adding a variable', () => {
describe('when no key/value pair are present', () => {
beforeEach(() => {
diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js
index 32af2ec4de9..0141232a299 100644
--- a/spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js
+++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_settings_spec.js
@@ -22,6 +22,7 @@ describe('Ci variable table', () => {
hideEnvironmentScope: false,
isLoading: false,
maxVariableLimit: 5,
+ pageInfo: { after: '' },
variables: mockVariablesWithScopes(projectString),
};
@@ -37,10 +38,6 @@ describe('Ci variable table', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('props passing', () => {
it('passes props down correctly to the ci table', () => {
createComponent();
@@ -49,6 +46,7 @@ describe('Ci variable table', () => {
entity: 'project',
isLoading: defaultProps.isLoading,
maxVariableLimit: defaultProps.maxVariableLimit,
+ pageInfo: defaultProps.pageInfo,
variables: defaultProps.variables,
});
});
@@ -144,4 +142,22 @@ describe('Ci variable table', () => {
expect(wrapper.emitted(eventName)).toEqual([[newVariable]]);
});
});
+
+ describe('pages events', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it.each`
+ eventName | args
+ ${'handle-prev-page'} | ${undefined}
+ ${'handle-next-page'} | ${undefined}
+ ${'sort-changed'} | ${{ sortDesc: true }}
+ `('bubbles up the $eventName event', async ({ args, eventName }) => {
+ findCiVariableTable().vm.$emit(eventName, args);
+ await nextTick();
+
+ expect(wrapper.emitted(eventName)).toEqual([[args]]);
+ });
+ });
});
diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js
index c977ae773db..87192006efc 100644
--- a/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js
+++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_shared_spec.js
@@ -4,7 +4,7 @@ import { GlLoadingIcon, GlTable } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { resolvers } from '~/ci/ci_variable_list/graphql/settings';
import { TYPENAME_GROUP } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
@@ -41,7 +41,7 @@ import {
mockAdminVariables,
} from '../mocks';
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(VueApollo);
@@ -53,6 +53,7 @@ const mockProvide = {
const defaultProps = {
areScopedVariablesAvailable: true,
+ pageInfo: {},
hideEnvironmentScope: false,
refetchAfterMutation: false,
};
@@ -105,345 +106,378 @@ describe('Ci Variable Shared Component', () => {
mockVariables = jest.fn();
});
- describe('while queries are being fetch', () => {
- beforeEach(() => {
- createComponentWithApollo({ isLoading: true });
- });
-
- it('shows a loading icon', () => {
- expect(findLoadingIcon().exists()).toBe(true);
- expect(findCiTable().exists()).toBe(false);
- });
- });
-
- describe('when queries are resolved', () => {
- describe('successfully', () => {
- beforeEach(async () => {
- mockEnvironments.mockResolvedValue(mockProjectEnvironments);
- mockVariables.mockResolvedValue(mockProjectVariables);
-
- await createComponentWithApollo({ provide: createProjectProvide() });
+ describe.each`
+ isVariablePagesEnabled | text
+ ${true} | ${'enabled'}
+ ${false} | ${'disabled'}
+ `('When Pages FF is $text', ({ isVariablePagesEnabled }) => {
+ const featureFlagProvide = isVariablePagesEnabled
+ ? { glFeatures: { ciVariablesPages: true } }
+ : {};
+
+ describe('while queries are being fetch', () => {
+ beforeEach(() => {
+ createComponentWithApollo({ isLoading: true });
});
- it('passes down the expected max variable limit as props', () => {
- expect(findCiSettings().props('maxVariableLimit')).toBe(
- mockProjectVariables.data.project.ciVariables.limit,
- );
+ it('shows a loading icon', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+ expect(findCiTable().exists()).toBe(false);
});
+ });
- it('passes down the expected environments as props', () => {
- expect(findCiSettings().props('environments')).toEqual([prodName, devName]);
- });
+ describe('when queries are resolved', () => {
+ describe('successfully', () => {
+ beforeEach(async () => {
+ mockEnvironments.mockResolvedValue(mockProjectEnvironments);
+ mockVariables.mockResolvedValue(mockProjectVariables);
- it('passes down the expected variables as props', () => {
- expect(findCiSettings().props('variables')).toEqual(
- mockProjectVariables.data.project.ciVariables.nodes,
- );
- });
+ await createComponentWithApollo({
+ provide: { ...createProjectProvide(), ...featureFlagProvide },
+ });
+ });
- it('createAlert was not called', () => {
- expect(createAlert).not.toHaveBeenCalled();
- });
- });
+ it('passes down the expected max variable limit as props', () => {
+ expect(findCiSettings().props('maxVariableLimit')).toBe(
+ mockProjectVariables.data.project.ciVariables.limit,
+ );
+ });
- describe('with an error for variables', () => {
- beforeEach(async () => {
- mockEnvironments.mockResolvedValue(mockProjectEnvironments);
- mockVariables.mockRejectedValue();
+ it('passes down the expected environments as props', () => {
+ expect(findCiSettings().props('environments')).toEqual([prodName, devName]);
+ });
- await createComponentWithApollo();
- });
+ it('passes down the expected variables as props', () => {
+ expect(findCiSettings().props('variables')).toEqual(
+ mockProjectVariables.data.project.ciVariables.nodes,
+ );
+ });
- it('calls createAlert with the expected error message', () => {
- expect(createAlert).toHaveBeenCalledWith({ message: variableFetchErrorText });
+ it('createAlert was not called', () => {
+ expect(createAlert).not.toHaveBeenCalled();
+ });
});
- });
- describe('with an error for environments', () => {
- beforeEach(async () => {
- mockEnvironments.mockRejectedValue();
- mockVariables.mockResolvedValue(mockProjectVariables);
+ describe('with an error for variables', () => {
+ beforeEach(async () => {
+ mockEnvironments.mockResolvedValue(mockProjectEnvironments);
+ mockVariables.mockRejectedValue();
- await createComponentWithApollo();
- });
+ await createComponentWithApollo({ provide: featureFlagProvide });
+ });
- it('calls createAlert with the expected error message', () => {
- expect(createAlert).toHaveBeenCalledWith({ message: environmentFetchErrorText });
+ it('calls createAlert with the expected error message', () => {
+ expect(createAlert).toHaveBeenCalledWith({ message: variableFetchErrorText });
+ });
});
- });
- });
- describe('environment query', () => {
- describe('when there is an environment key in queryData', () => {
- beforeEach(async () => {
- mockEnvironments.mockResolvedValue(mockProjectEnvironments);
- mockVariables.mockResolvedValue(mockProjectVariables);
+ describe('with an error for environments', () => {
+ beforeEach(async () => {
+ mockEnvironments.mockRejectedValue();
+ mockVariables.mockResolvedValue(mockProjectVariables);
- await createComponentWithApollo({ props: { ...createProjectProps() } });
- });
+ await createComponentWithApollo({ provide: featureFlagProvide });
+ });
- it('is executed', () => {
- expect(mockVariables).toHaveBeenCalled();
+ it('calls createAlert with the expected error message', () => {
+ expect(createAlert).toHaveBeenCalledWith({ message: environmentFetchErrorText });
+ });
});
});
- describe('when there isnt an environment key in queryData', () => {
- beforeEach(async () => {
- mockVariables.mockResolvedValue(mockGroupVariables);
+ describe('environment query', () => {
+ describe('when there is an environment key in queryData', () => {
+ beforeEach(async () => {
+ mockEnvironments.mockResolvedValue(mockProjectEnvironments);
+ mockVariables.mockResolvedValue(mockProjectVariables);
- await createComponentWithApollo({ props: { ...createGroupProps() } });
- });
+ await createComponentWithApollo({
+ props: { ...createProjectProps() },
+ provide: featureFlagProvide,
+ });
+ });
- it('is skipped', () => {
- expect(mockVariables).not.toHaveBeenCalled();
+ it('is executed', () => {
+ expect(mockVariables).toHaveBeenCalled();
+ });
});
- });
- });
- describe('mutations', () => {
- const groupProps = createGroupProps();
+ describe('when there isnt an environment key in queryData', () => {
+ beforeEach(async () => {
+ mockVariables.mockResolvedValue(mockGroupVariables);
- beforeEach(async () => {
- mockVariables.mockResolvedValue(mockGroupVariables);
+ await createComponentWithApollo({
+ props: { ...createGroupProps() },
+ provide: featureFlagProvide,
+ });
+ });
- await createComponentWithApollo({
- customHandlers: [[getGroupVariables, mockVariables]],
- props: groupProps,
+ it('is skipped', () => {
+ expect(mockVariables).not.toHaveBeenCalled();
+ });
});
});
- it.each`
- actionName | mutation | event
- ${'add'} | ${groupProps.mutationData[ADD_MUTATION_ACTION]} | ${'add-variable'}
- ${'update'} | ${groupProps.mutationData[UPDATE_MUTATION_ACTION]} | ${'update-variable'}
- ${'delete'} | ${groupProps.mutationData[DELETE_MUTATION_ACTION]} | ${'delete-variable'}
- `(
- 'calls the right mutation from propsData when user performs $actionName variable',
- async ({ event, mutation }) => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
-
- await findCiSettings().vm.$emit(event, newVariable);
-
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation,
- variables: {
- endpoint: mockProvide.endpoint,
- fullPath: groupProps.fullPath,
- id: convertToGraphQLId(TYPENAME_GROUP, groupProps.id),
- variable: newVariable,
- },
- });
- },
- );
-
- it.each`
- actionName | event
- ${'add'} | ${'add-variable'}
- ${'update'} | ${'update-variable'}
- ${'delete'} | ${'delete-variable'}
- `(
- 'throws with the specific graphql error if present when user performs $actionName variable',
- async ({ event }) => {
- const graphQLErrorMessage = 'There is a problem with this graphQL action';
- jest
- .spyOn(wrapper.vm.$apollo, 'mutate')
- .mockResolvedValue({ data: { ciVariableMutation: { errors: [graphQLErrorMessage] } } });
- await findCiSettings().vm.$emit(event, newVariable);
- await nextTick();
-
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled();
- expect(createAlert).toHaveBeenCalledWith({ message: graphQLErrorMessage });
- },
- );
-
- it.each`
- actionName | event
- ${'add'} | ${'add-variable'}
- ${'update'} | ${'update-variable'}
- ${'delete'} | ${'delete-variable'}
- `(
- 'throws generic error on failure with no graphql errors and user performs $actionName variable',
- async ({ event }) => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockImplementationOnce(() => {
- throw new Error();
- });
- await findCiSettings().vm.$emit(event, newVariable);
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled();
- expect(createAlert).toHaveBeenCalledWith({ message: genericMutationErrorText });
- },
- );
+ describe('mutations', () => {
+ const groupProps = createGroupProps();
- describe('without fullpath and ID props', () => {
beforeEach(async () => {
- mockVariables.mockResolvedValue(mockAdminVariables);
+ mockVariables.mockResolvedValue(mockGroupVariables);
await createComponentWithApollo({
- customHandlers: [[getAdminVariables, mockVariables]],
- props: createInstanceProps(),
+ customHandlers: [[getGroupVariables, mockVariables]],
+ props: groupProps,
+ provide: featureFlagProvide,
});
});
-
- it('does not pass fullPath and ID to the mutation', async () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
-
- await findCiSettings().vm.$emit('add-variable', newVariable);
-
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: wrapper.props().mutationData[ADD_MUTATION_ACTION],
- variables: {
- endpoint: mockProvide.endpoint,
- variable: newVariable,
- },
- });
- });
- });
- });
-
- describe('Props', () => {
- const mockGroupCiVariables = mockGroupVariables.data.group.ciVariables;
- const mockProjectCiVariables = mockProjectVariables.data.project.ciVariables;
-
- describe('in a specific context as', () => {
it.each`
- name | mockVariablesValue | mockEnvironmentsValue | withEnvironments | expectedEnvironments | propsFn | provideFn | mutation | maxVariableLimit
- ${'project'} | ${mockProjectVariables} | ${mockProjectEnvironments} | ${true} | ${['prod', 'dev']} | ${createProjectProps} | ${createProjectProvide} | ${null} | ${mockProjectCiVariables.limit}
- ${'group'} | ${mockGroupVariables} | ${[]} | ${false} | ${[]} | ${createGroupProps} | ${createGroupProvide} | ${getGroupVariables} | ${mockGroupCiVariables.limit}
- ${'instance'} | ${mockAdminVariables} | ${[]} | ${false} | ${[]} | ${createInstanceProps} | ${() => {}} | ${getAdminVariables} | ${0}
+ actionName | mutation | event
+ ${'add'} | ${groupProps.mutationData[ADD_MUTATION_ACTION]} | ${'add-variable'}
+ ${'update'} | ${groupProps.mutationData[UPDATE_MUTATION_ACTION]} | ${'update-variable'}
+ ${'delete'} | ${groupProps.mutationData[DELETE_MUTATION_ACTION]} | ${'delete-variable'}
`(
- 'passes down all the required props when its a $name component',
- async ({
- mutation,
- maxVariableLimit,
- mockVariablesValue,
- mockEnvironmentsValue,
- withEnvironments,
- expectedEnvironments,
- propsFn,
- provideFn,
- }) => {
- const props = propsFn();
- const provide = provideFn();
-
- mockVariables.mockResolvedValue(mockVariablesValue);
-
- if (withEnvironments) {
- mockEnvironments.mockResolvedValue(mockEnvironmentsValue);
- }
-
- let customHandlers = null;
-
- if (mutation) {
- customHandlers = [[mutation, mockVariables]];
- }
+ 'calls the right mutation from propsData when user performs $actionName variable',
+ async ({ event, mutation }) => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
+
+ await findCiSettings().vm.$emit(event, newVariable);
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation,
+ variables: {
+ endpoint: mockProvide.endpoint,
+ fullPath: groupProps.fullPath,
+ id: convertToGraphQLId(TYPENAME_GROUP, groupProps.id),
+ variable: newVariable,
+ },
+ });
+ },
+ );
- await createComponentWithApollo({ customHandlers, props, provide });
+ it.each`
+ actionName | event
+ ${'add'} | ${'add-variable'}
+ ${'update'} | ${'update-variable'}
+ ${'delete'} | ${'delete-variable'}
+ `(
+ 'throws with the specific graphql error if present when user performs $actionName variable',
+ async ({ event }) => {
+ const graphQLErrorMessage = 'There is a problem with this graphQL action';
+ jest
+ .spyOn(wrapper.vm.$apollo, 'mutate')
+ .mockResolvedValue({ data: { ciVariableMutation: { errors: [graphQLErrorMessage] } } });
+ await findCiSettings().vm.$emit(event, newVariable);
+ await nextTick();
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled();
+ expect(createAlert).toHaveBeenCalledWith({ message: graphQLErrorMessage });
+ },
+ );
- expect(findCiSettings().props()).toEqual({
- areScopedVariablesAvailable: wrapper.props().areScopedVariablesAvailable,
- hideEnvironmentScope: defaultProps.hideEnvironmentScope,
- isLoading: false,
- maxVariableLimit,
- variables: wrapper.props().queryData.ciVariables.lookup(mockVariablesValue.data)?.nodes,
- entity: props.entity,
- environments: expectedEnvironments,
+ it.each`
+ actionName | event
+ ${'add'} | ${'add-variable'}
+ ${'update'} | ${'update-variable'}
+ ${'delete'} | ${'delete-variable'}
+ `(
+ 'throws generic error on failure with no graphql errors and user performs $actionName variable',
+ async ({ event }) => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockImplementationOnce(() => {
+ throw new Error();
});
+ await findCiSettings().vm.$emit(event, newVariable);
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled();
+ expect(createAlert).toHaveBeenCalledWith({ message: genericMutationErrorText });
},
);
- });
- describe('refetchAfterMutation', () => {
- it.each`
- bool | text
- ${true} | ${'refetches the variables'}
- ${false} | ${'does not refetch the variables'}
- `('when $bool it $text', async ({ bool }) => {
- await createComponentWithApollo({
- props: { ...createInstanceProps(), refetchAfterMutation: bool },
- });
+ describe('without fullpath and ID props', () => {
+ beforeEach(async () => {
+ mockVariables.mockResolvedValue(mockAdminVariables);
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({ data: {} });
- jest.spyOn(wrapper.vm.$apollo.queries.ciVariables, 'refetch').mockImplementation(jest.fn());
+ await createComponentWithApollo({
+ customHandlers: [[getAdminVariables, mockVariables]],
+ props: createInstanceProps(),
+ provide: featureFlagProvide,
+ });
+ });
- await findCiSettings().vm.$emit('add-variable', newVariable);
+ it('does not pass fullPath and ID to the mutation', async () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
- await nextTick();
+ await findCiSettings().vm.$emit('add-variable', newVariable);
- if (bool) {
- expect(wrapper.vm.$apollo.queries.ciVariables.refetch).toHaveBeenCalled();
- } else {
- expect(wrapper.vm.$apollo.queries.ciVariables.refetch).not.toHaveBeenCalled();
- }
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: wrapper.props().mutationData[ADD_MUTATION_ACTION],
+ variables: {
+ endpoint: mockProvide.endpoint,
+ variable: newVariable,
+ },
+ });
+ });
});
});
- describe('Validators', () => {
- describe('queryData', () => {
- let error;
+ describe('Props', () => {
+ const mockGroupCiVariables = mockGroupVariables.data.group.ciVariables;
+ const mockProjectCiVariables = mockProjectVariables.data.project.ciVariables;
+
+ describe('in a specific context as', () => {
+ it.each`
+ name | mockVariablesValue | mockEnvironmentsValue | withEnvironments | expectedEnvironments | propsFn | provideFn | mutation | maxVariableLimit
+ ${'project'} | ${mockProjectVariables} | ${mockProjectEnvironments} | ${true} | ${['prod', 'dev']} | ${createProjectProps} | ${createProjectProvide} | ${null} | ${mockProjectCiVariables.limit}
+ ${'group'} | ${mockGroupVariables} | ${[]} | ${false} | ${[]} | ${createGroupProps} | ${createGroupProvide} | ${getGroupVariables} | ${mockGroupCiVariables.limit}
+ ${'instance'} | ${mockAdminVariables} | ${[]} | ${false} | ${[]} | ${createInstanceProps} | ${() => {}} | ${getAdminVariables} | ${0}
+ `(
+ 'passes down all the required props when its a $name component',
+ async ({
+ mutation,
+ maxVariableLimit,
+ mockVariablesValue,
+ mockEnvironmentsValue,
+ withEnvironments,
+ expectedEnvironments,
+ propsFn,
+ provideFn,
+ }) => {
+ const props = propsFn();
+ const provide = provideFn();
- beforeEach(async () => {
- mockVariables.mockResolvedValue(mockGroupVariables);
- });
+ mockVariables.mockResolvedValue(mockVariablesValue);
+
+ if (withEnvironments) {
+ mockEnvironments.mockResolvedValue(mockEnvironmentsValue);
+ }
+
+ let customHandlers = null;
+
+ if (mutation) {
+ customHandlers = [[mutation, mockVariables]];
+ }
- it('will mount component with right data', async () => {
- try {
await createComponentWithApollo({
- customHandlers: [[getGroupVariables, mockVariables]],
- props: { ...createGroupProps() },
+ customHandlers,
+ props,
+ provide: { ...provide, ...featureFlagProvide },
});
- } catch (e) {
- error = e;
- } finally {
- expect(wrapper.exists()).toBe(true);
- expect(error).toBeUndefined();
- }
- });
- it('will not mount component with wrong data', async () => {
- try {
- await createComponentWithApollo({
- customHandlers: [[getGroupVariables, mockVariables]],
- props: { ...createGroupProps(), queryData: { wrongKey: {} } },
+ expect(findCiSettings().props()).toEqual({
+ areScopedVariablesAvailable: wrapper.props().areScopedVariablesAvailable,
+ hideEnvironmentScope: defaultProps.hideEnvironmentScope,
+ pageInfo: defaultProps.pageInfo,
+ isLoading: false,
+ maxVariableLimit,
+ variables: wrapper.props().queryData.ciVariables.lookup(mockVariablesValue.data)
+ ?.nodes,
+ entity: props.entity,
+ environments: expectedEnvironments,
});
- } catch (e) {
- error = e;
- } finally {
- expect(wrapper.exists()).toBe(false);
- expect(error.toString()).toContain('custom validator check failed for prop');
+ },
+ );
+ });
+
+ describe('refetchAfterMutation', () => {
+ it.each`
+ bool | text
+ ${true} | ${'refetches the variables'}
+ ${false} | ${'does not refetch the variables'}
+ `('when $bool it $text', async ({ bool }) => {
+ await createComponentWithApollo({
+ props: { ...createInstanceProps(), refetchAfterMutation: bool },
+ provide: featureFlagProvide,
+ });
+
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({ data: {} });
+ jest
+ .spyOn(wrapper.vm.$apollo.queries.ciVariables, 'refetch')
+ .mockImplementation(jest.fn());
+
+ await findCiSettings().vm.$emit('add-variable', newVariable);
+
+ await nextTick();
+
+ if (bool) {
+ expect(wrapper.vm.$apollo.queries.ciVariables.refetch).toHaveBeenCalled();
+ } else {
+ expect(wrapper.vm.$apollo.queries.ciVariables.refetch).not.toHaveBeenCalled();
}
});
});
- describe('mutationData', () => {
- let error;
+ describe('Validators', () => {
+ describe('queryData', () => {
+ let error;
- beforeEach(async () => {
- mockVariables.mockResolvedValue(mockGroupVariables);
- });
+ beforeEach(async () => {
+ mockVariables.mockResolvedValue(mockGroupVariables);
+ });
- it('will mount component with right data', async () => {
- try {
- await createComponentWithApollo({
- props: { ...createGroupProps() },
- });
- } catch (e) {
- error = e;
- } finally {
- expect(wrapper.exists()).toBe(true);
- expect(error).toBeUndefined();
- }
+ it('will mount component with right data', async () => {
+ try {
+ await createComponentWithApollo({
+ customHandlers: [[getGroupVariables, mockVariables]],
+ props: { ...createGroupProps() },
+ provide: featureFlagProvide,
+ });
+ } catch (e) {
+ error = e;
+ } finally {
+ expect(wrapper.exists()).toBe(true);
+ expect(error).toBeUndefined();
+ }
+ });
+
+ it('will not mount component with wrong data', async () => {
+ try {
+ await createComponentWithApollo({
+ customHandlers: [[getGroupVariables, mockVariables]],
+ props: { ...createGroupProps(), queryData: { wrongKey: {} } },
+ provide: featureFlagProvide,
+ });
+ } catch (e) {
+ error = e;
+ } finally {
+ expect(wrapper.exists()).toBe(false);
+ expect(error.toString()).toContain('custom validator check failed for prop');
+ }
+ });
});
- it('will not mount component with wrong data', async () => {
- try {
- await createComponentWithApollo({
- props: { ...createGroupProps(), mutationData: { wrongKey: {} } },
- });
- } catch (e) {
- error = e;
- } finally {
- expect(wrapper.exists()).toBe(false);
- expect(error.toString()).toContain('custom validator check failed for prop');
- }
+ describe('mutationData', () => {
+ let error;
+
+ beforeEach(async () => {
+ mockVariables.mockResolvedValue(mockGroupVariables);
+ });
+
+ it('will mount component with right data', async () => {
+ try {
+ await createComponentWithApollo({
+ props: { ...createGroupProps() },
+ provide: featureFlagProvide,
+ });
+ } catch (e) {
+ error = e;
+ } finally {
+ expect(wrapper.exists()).toBe(true);
+ expect(error).toBeUndefined();
+ }
+ });
+
+ it('will not mount component with wrong data', async () => {
+ try {
+ await createComponentWithApollo({
+ props: { ...createGroupProps(), mutationData: { wrongKey: {} } },
+ provide: featureFlagProvide,
+ });
+ } catch (e) {
+ error = e;
+ } finally {
+ expect(wrapper.exists()).toBe(false);
+ expect(error.toString()).toContain('custom validator check failed for prop');
+ }
+ });
});
});
});
diff --git a/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js b/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js
index 9e2508c56ee..2ef789e89c3 100644
--- a/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js
+++ b/spec/frontend/ci/ci_variable_list/components/ci_variable_table_spec.js
@@ -12,18 +12,25 @@ describe('Ci variable table', () => {
entity: 'project',
isLoading: false,
maxVariableLimit: mockVariables(projectString).length + 1,
+ pageInfo: {},
variables: mockVariables(projectString),
};
const mockMaxVariableLimit = defaultProps.variables.length;
- const createComponent = ({ props = {} } = {}) => {
+ const createComponent = ({ props = {}, provide = {} } = {}) => {
wrapper = mountExtended(CiVariableTable, {
attachTo: document.body,
propsData: {
...defaultProps,
...props,
},
+ provide: {
+ glFeatures: {
+ ciVariablesPages: false,
+ },
+ ...provide,
+ },
});
};
@@ -41,132 +48,136 @@ describe('Ci variable table', () => {
return sprintf(EXCEEDS_VARIABLE_LIMIT_TEXT, { entity, currentVariableCount, maxVariableLimit });
};
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('When table is empty', () => {
- beforeEach(() => {
- createComponent({ props: { variables: [] } });
- });
+ describe.each`
+ isVariablePagesEnabled | text
+ ${true} | ${'enabled'}
+ ${false} | ${'disabled'}
+ `('When Pages FF is $text', ({ isVariablePagesEnabled }) => {
+ const provide = isVariablePagesEnabled ? { glFeatures: { ciVariablesPages: true } } : {};
- it('displays empty message', () => {
- expect(findEmptyVariablesPlaceholder().exists()).toBe(true);
- });
-
- it('hides the reveal button', () => {
- expect(findRevealButton().exists()).toBe(false);
- });
- });
+ describe('When table is empty', () => {
+ beforeEach(() => {
+ createComponent({ props: { variables: [] }, provide });
+ });
- describe('When table has variables', () => {
- beforeEach(() => {
- createComponent();
- });
+ it('displays empty message', () => {
+ expect(findEmptyVariablesPlaceholder().exists()).toBe(true);
+ });
- it('does not display the empty message', () => {
- expect(findEmptyVariablesPlaceholder().exists()).toBe(false);
+ it('hides the reveal button', () => {
+ expect(findRevealButton().exists()).toBe(false);
+ });
});
- it('displays the reveal button', () => {
- expect(findRevealButton().exists()).toBe(true);
- });
+ describe('When table has variables', () => {
+ beforeEach(() => {
+ createComponent({ provide });
+ });
- it('displays the correct amount of variables', async () => {
- expect(wrapper.findAll('.js-ci-variable-row')).toHaveLength(defaultProps.variables.length);
- });
+ it('does not display the empty message', () => {
+ expect(findEmptyVariablesPlaceholder().exists()).toBe(false);
+ });
- it('displays the correct variable options', async () => {
- expect(findOptionsValues(0)).toBe('Protected, Expanded');
- expect(findOptionsValues(1)).toBe('Masked');
- });
+ it('displays the reveal button', () => {
+ expect(findRevealButton().exists()).toBe(true);
+ });
- it('enables the Add Variable button', () => {
- expect(findAddButton().props('disabled')).toBe(false);
- });
- });
+ it('displays the correct amount of variables', async () => {
+ expect(wrapper.findAll('.js-ci-variable-row')).toHaveLength(defaultProps.variables.length);
+ });
- describe('When variables have exceeded the max limit', () => {
- beforeEach(() => {
- createComponent({ props: { maxVariableLimit: mockVariables(projectString).length } });
- });
+ it('displays the correct variable options', async () => {
+ expect(findOptionsValues(0)).toBe('Protected, Expanded');
+ expect(findOptionsValues(1)).toBe('Masked');
+ });
- it('disables the Add Variable button', () => {
- expect(findAddButton().props('disabled')).toBe(true);
+ it('enables the Add Variable button', () => {
+ expect(findAddButton().props('disabled')).toBe(false);
+ });
});
- });
- describe('max limit reached alert', () => {
- describe('when there is no variable limit', () => {
+ describe('When variables have exceeded the max limit', () => {
beforeEach(() => {
createComponent({
- props: { maxVariableLimit: 0 },
+ props: { maxVariableLimit: mockVariables(projectString).length },
+ provide,
});
});
- it('hides alert', () => {
- expect(findLimitReachedAlerts().length).toBe(0);
+ it('disables the Add Variable button', () => {
+ expect(findAddButton().props('disabled')).toBe(true);
});
});
- describe('when variable limit exists', () => {
- it('hides alert when limit has not been reached', () => {
- createComponent();
+ describe('max limit reached alert', () => {
+ describe('when there is no variable limit', () => {
+ beforeEach(() => {
+ createComponent({
+ props: { maxVariableLimit: 0 },
+ provide,
+ });
+ });
- expect(findLimitReachedAlerts().length).toBe(0);
+ it('hides alert', () => {
+ expect(findLimitReachedAlerts().length).toBe(0);
+ });
});
- it('shows alert when limit has been reached', () => {
- const exceedsVariableLimitText = generateExceedsVariableLimitText(
- defaultProps.entity,
- defaultProps.variables.length,
- mockMaxVariableLimit,
- );
+ describe('when variable limit exists', () => {
+ it('hides alert when limit has not been reached', () => {
+ createComponent({ provide });
- createComponent({
- props: { maxVariableLimit: mockMaxVariableLimit },
+ expect(findLimitReachedAlerts().length).toBe(0);
});
- expect(findLimitReachedAlerts().length).toBe(2);
+ it('shows alert when limit has been reached', () => {
+ const exceedsVariableLimitText = generateExceedsVariableLimitText(
+ defaultProps.entity,
+ defaultProps.variables.length,
+ mockMaxVariableLimit,
+ );
+
+ createComponent({
+ props: { maxVariableLimit: mockMaxVariableLimit },
+ });
- expect(findLimitReachedAlerts().at(0).props('dismissible')).toBe(false);
- expect(findLimitReachedAlerts().at(0).text()).toContain(exceedsVariableLimitText);
+ expect(findLimitReachedAlerts().length).toBe(2);
- expect(findLimitReachedAlerts().at(1).props('dismissible')).toBe(false);
- expect(findLimitReachedAlerts().at(1).text()).toContain(exceedsVariableLimitText);
+ expect(findLimitReachedAlerts().at(0).props('dismissible')).toBe(false);
+ expect(findLimitReachedAlerts().at(0).text()).toContain(exceedsVariableLimitText);
+
+ expect(findLimitReachedAlerts().at(1).props('dismissible')).toBe(false);
+ expect(findLimitReachedAlerts().at(1).text()).toContain(exceedsVariableLimitText);
+ });
});
});
- });
- describe('Table click actions', () => {
- beforeEach(() => {
- createComponent();
- });
+ describe('Table click actions', () => {
+ beforeEach(() => {
+ createComponent({ provide });
+ });
- it('reveals secret values when button is clicked', async () => {
- expect(findHiddenValues()).toHaveLength(defaultProps.variables.length);
- expect(findRevealedValues()).toHaveLength(0);
+ it('reveals secret values when button is clicked', async () => {
+ expect(findHiddenValues()).toHaveLength(defaultProps.variables.length);
+ expect(findRevealedValues()).toHaveLength(0);
- await findRevealButton().trigger('click');
+ await findRevealButton().trigger('click');
- expect(findHiddenValues()).toHaveLength(0);
- expect(findRevealedValues()).toHaveLength(defaultProps.variables.length);
- });
+ expect(findHiddenValues()).toHaveLength(0);
+ expect(findRevealedValues()).toHaveLength(defaultProps.variables.length);
+ });
- it('dispatches `setSelectedVariable` with correct variable to edit', async () => {
- await findEditButton().trigger('click');
+ it('dispatches `setSelectedVariable` with correct variable to edit', async () => {
+ await findEditButton().trigger('click');
- expect(wrapper.emitted('set-selected-variable')).toEqual([[defaultProps.variables[0]]]);
- });
+ expect(wrapper.emitted('set-selected-variable')).toEqual([[defaultProps.variables[0]]]);
+ });
- it('dispatches `setSelectedVariable` with no variable when adding a new one', async () => {
- await findAddButton().trigger('click');
+ it('dispatches `setSelectedVariable` with no variable when adding a new one', async () => {
+ await findAddButton().trigger('click');
- expect(wrapper.emitted('set-selected-variable')).toEqual([[null]]);
+ expect(wrapper.emitted('set-selected-variable')).toEqual([[null]]);
+ });
});
});
});
diff --git a/spec/frontend/ci/pipeline_editor/components/code_snippet_alert/code_snippet_alert_spec.js b/spec/frontend/ci/pipeline_editor/components/code_snippet_alert/code_snippet_alert_spec.js
index b00e1adab63..48a85eba433 100644
--- a/spec/frontend/ci/pipeline_editor/components/code_snippet_alert/code_snippet_alert_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/code_snippet_alert/code_snippet_alert_spec.js
@@ -41,10 +41,6 @@ describe('EE - CodeSnippetAlert', () => {
createWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it("provides a link to the feature's documentation", () => {
const docsLink = findDocsLink();
diff --git a/spec/frontend/ci/pipeline_editor/components/commit/commit_form_spec.js b/spec/frontend/ci/pipeline_editor/components/commit/commit_form_spec.js
index 8e1d8081dd8..b2dfa900b1d 100644
--- a/spec/frontend/ci/pipeline_editor/components/commit/commit_form_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/commit/commit_form_spec.js
@@ -33,10 +33,6 @@ describe('Pipeline Editor | Commit Form', () => {
const findSubmitBtn = () => wrapper.find('[type="submit"]');
const findCancelBtn = () => wrapper.find('[type="reset"]');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when the form is displayed', () => {
beforeEach(async () => {
createComponent();
diff --git a/spec/frontend/ci/pipeline_editor/components/commit/commit_section_spec.js b/spec/frontend/ci/pipeline_editor/components/commit/commit_section_spec.js
index f6e93c55bbb..f8be035d33c 100644
--- a/spec/frontend/ci/pipeline_editor/components/commit/commit_section_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/commit/commit_section_spec.js
@@ -113,10 +113,6 @@ describe('Pipeline Editor | Commit section', () => {
await waitForPromises();
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when the user commits a new file', () => {
beforeEach(async () => {
mockMutateCommitData.mockResolvedValue(mockCommitCreateResponse);
diff --git a/spec/frontend/ci/pipeline_editor/components/drawer/cards/first_pipeline_card_spec.js b/spec/frontend/ci/pipeline_editor/components/drawer/cards/first_pipeline_card_spec.js
index 137137ec657..0ecb77674d5 100644
--- a/spec/frontend/ci/pipeline_editor/components/drawer/cards/first_pipeline_card_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/drawer/cards/first_pipeline_card_spec.js
@@ -21,10 +21,6 @@ describe('First pipeline card', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the title', () => {
expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.title);
});
diff --git a/spec/frontend/ci/pipeline_editor/components/drawer/cards/getting_started_card_spec.js b/spec/frontend/ci/pipeline_editor/components/drawer/cards/getting_started_card_spec.js
index cdce757ce7c..417597eaf1f 100644
--- a/spec/frontend/ci/pipeline_editor/components/drawer/cards/getting_started_card_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/drawer/cards/getting_started_card_spec.js
@@ -12,10 +12,6 @@ describe('Getting started card', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the title', () => {
expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.title);
});
diff --git a/spec/frontend/ci/pipeline_editor/components/drawer/cards/pipeline_config_reference_card_spec.js b/spec/frontend/ci/pipeline_editor/components/drawer/cards/pipeline_config_reference_card_spec.js
index 6909916c3e6..5399924b462 100644
--- a/spec/frontend/ci/pipeline_editor/components/drawer/cards/pipeline_config_reference_card_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/drawer/cards/pipeline_config_reference_card_spec.js
@@ -33,10 +33,6 @@ describe('Pipeline config reference card', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the title', () => {
expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.title);
});
diff --git a/spec/frontend/ci/pipeline_editor/components/drawer/cards/visualize_and_lint_card_spec.js b/spec/frontend/ci/pipeline_editor/components/drawer/cards/visualize_and_lint_card_spec.js
index 0c6879020de..547ba3cbd8b 100644
--- a/spec/frontend/ci/pipeline_editor/components/drawer/cards/visualize_and_lint_card_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/drawer/cards/visualize_and_lint_card_spec.js
@@ -12,10 +12,6 @@ describe('Visual and Lint card', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the title', () => {
expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.title);
});
diff --git a/spec/frontend/ci/pipeline_editor/components/drawer/pipeline_editor_drawer_spec.js b/spec/frontend/ci/pipeline_editor/components/drawer/pipeline_editor_drawer_spec.js
index 42e372cc1db..b07d63dd5d9 100644
--- a/spec/frontend/ci/pipeline_editor/components/drawer/pipeline_editor_drawer_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/drawer/pipeline_editor_drawer_spec.js
@@ -11,10 +11,6 @@ describe('Pipeline editor drawer', () => {
wrapper = shallowMount(PipelineEditorDrawer);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('emits close event when closing the drawer', () => {
createComponent();
diff --git a/spec/frontend/ci/pipeline_editor/components/drawer/ui/demo_job_pill_spec.js b/spec/frontend/ci/pipeline_editor/components/drawer/ui/demo_job_pill_spec.js
index f510c61ee74..b0c889cfc9f 100644
--- a/spec/frontend/ci/pipeline_editor/components/drawer/ui/demo_job_pill_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/drawer/ui/demo_job_pill_spec.js
@@ -17,10 +17,6 @@ describe('Demo job pill', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the jobName', () => {
expect(wrapper.text()).toContain(jobName);
});
diff --git a/spec/frontend/ci/pipeline_editor/components/editor/ci_config_merged_preview_spec.js b/spec/frontend/ci/pipeline_editor/components/editor/ci_config_merged_preview_spec.js
index 2a2bc2547cc..2182b6e9cc6 100644
--- a/spec/frontend/ci/pipeline_editor/components/editor/ci_config_merged_preview_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/editor/ci_config_merged_preview_spec.js
@@ -34,10 +34,6 @@ describe('Text editor component', () => {
const findIcon = () => wrapper.findComponent(GlIcon);
const findEditor = () => wrapper.findComponent(MockSourceEditor);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when status is valid', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/ci/pipeline_editor/components/editor/ci_editor_header_spec.js b/spec/frontend/ci/pipeline_editor/components/editor/ci_editor_header_spec.js
index dc72694d26f..560e8840d57 100644
--- a/spec/frontend/ci/pipeline_editor/components/editor/ci_editor_header_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/editor/ci_editor_header_spec.js
@@ -26,7 +26,6 @@ describe('CI Editor Header', () => {
const findHelpBtn = () => wrapper.findByTestId('drawer-toggle');
afterEach(() => {
- wrapper.destroy();
unmockTracking();
});
diff --git a/spec/frontend/ci/pipeline_editor/components/editor/text_editor_spec.js b/spec/frontend/ci/pipeline_editor/components/editor/text_editor_spec.js
index ec987be8cb8..0be26570fbf 100644
--- a/spec/frontend/ci/pipeline_editor/components/editor/text_editor_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/editor/text_editor_spec.js
@@ -1,7 +1,11 @@
import { shallowMount } from '@vue/test-utils';
+import { editor as monacoEditor } from 'monaco-editor';
+import SourceEditor from '~/vue_shared/components/source_editor.vue';
import { EDITOR_READY_EVENT } from '~/editor/constants';
+import { CiSchemaExtension as MockedCiSchemaExtension } from '~/editor/extensions/source_editor_ci_schema_ext';
import { SOURCE_EDITOR_DEBOUNCE } from '~/ci/pipeline_editor/constants';
+import eventHub, { SCROLL_EDITOR_TO_BOTTOM } from '~/ci/pipeline_editor/event_hub';
import TextEditor from '~/ci/pipeline_editor/components/editor/text_editor.vue';
import {
mockCiConfigPath,
@@ -12,19 +16,26 @@ import {
mockDefaultBranch,
} from '../../mock_data';
+jest.mock('monaco-editor');
+jest.mock('~/editor/extensions/source_editor_ci_schema_ext', () => {
+ const { createMockSourceEditorExtension } = jest.requireActual(
+ 'helpers/create_mock_source_editor_extension',
+ );
+ const { CiSchemaExtension } = jest.requireActual(
+ '~/editor/extensions/source_editor_ci_schema_ext',
+ );
+
+ return {
+ CiSchemaExtension: createMockSourceEditorExtension(CiSchemaExtension),
+ };
+});
+
describe('Pipeline Editor | Text editor component', () => {
let wrapper;
let editorReadyListener;
- let mockUse;
- let mockRegisterCiSchema;
- let mockEditorInstance;
- let editorInstanceDetail;
-
- const MockSourceEditor = {
- template: '<div/>',
- props: ['value', 'fileName', 'editorOptions', 'debounceValue'],
- };
+
+ const getMonacoEditor = () => monacoEditor.create.mock.results[0].value;
const createComponent = (mountFn = shallowMount) => {
wrapper = mountFn(TextEditor, {
@@ -44,33 +55,17 @@ describe('Pipeline Editor | Text editor component', () => {
[EDITOR_READY_EVENT]: editorReadyListener,
},
stubs: {
- SourceEditor: MockSourceEditor,
+ SourceEditor,
},
});
};
- const findEditor = () => wrapper.findComponent(MockSourceEditor);
+ const findEditor = () => wrapper.findComponent(SourceEditor);
beforeEach(() => {
- editorReadyListener = jest.fn();
- mockUse = jest.fn();
- mockRegisterCiSchema = jest.fn();
- mockEditorInstance = {
- use: mockUse,
- registerCiSchema: mockRegisterCiSchema,
- };
- editorInstanceDetail = {
- detail: {
- instance: mockEditorInstance,
- },
- };
- });
+ jest.spyOn(monacoEditor, 'create');
- afterEach(() => {
- wrapper.destroy();
-
- mockUse.mockClear();
- mockRegisterCiSchema.mockClear();
+ editorReadyListener = jest.fn();
});
describe('template', () => {
@@ -99,21 +94,34 @@ describe('Pipeline Editor | Text editor component', () => {
});
it('bubbles up events', () => {
- findEditor().vm.$emit(EDITOR_READY_EVENT, editorInstanceDetail);
-
expect(editorReadyListener).toHaveBeenCalled();
});
+
+ it('scrolls editor to bottom on scroll editor to bottom event', () => {
+ const setScrollTop = jest.spyOn(getMonacoEditor(), 'setScrollTop');
+
+ eventHub.$emit(SCROLL_EDITOR_TO_BOTTOM);
+
+ expect(setScrollTop).toHaveBeenCalledWith(getMonacoEditor().getScrollHeight());
+ });
+
+ it('when destroyed, destroys scroll listener', () => {
+ const setScrollTop = jest.spyOn(getMonacoEditor(), 'setScrollTop');
+
+ wrapper.destroy();
+ eventHub.$emit(SCROLL_EDITOR_TO_BOTTOM);
+
+ expect(setScrollTop).not.toHaveBeenCalled();
+ });
});
describe('CI schema', () => {
beforeEach(() => {
createComponent();
- findEditor().vm.$emit(EDITOR_READY_EVENT, editorInstanceDetail);
});
it('configures editor with syntax highlight', () => {
- expect(mockUse).toHaveBeenCalledTimes(1);
- expect(mockRegisterCiSchema).toHaveBeenCalledTimes(1);
+ expect(MockedCiSchemaExtension.mockedMethods.registerCiSchema).toHaveBeenCalledTimes(1);
});
});
});
diff --git a/spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js b/spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js
index a26232df58f..bf14f4c4cd6 100644
--- a/spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/file-nav/branch_switcher_spec.js
@@ -133,10 +133,6 @@ describe('Pipeline editor branch switcher', () => {
mockAvailableBranchQuery = jest.fn();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const testErrorHandling = () => {
expect(wrapper.emitted('showError')).toBeDefined();
expect(wrapper.emitted('showError')[0]).toEqual([
diff --git a/spec/frontend/ci/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js b/spec/frontend/ci/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js
index 907db16913c..19c113689c2 100644
--- a/spec/frontend/ci/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/file-nav/pipeline_editor_file_nav_spec.js
@@ -48,10 +48,6 @@ describe('Pipeline editor file nav', () => {
const findFileTreeBtn = () => wrapper.findByTestId('file-tree-toggle');
const findPopoverContainer = () => wrapper.findComponent(FileTreePopover);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/ci/pipeline_editor/components/file-tree/container_spec.js b/spec/frontend/ci/pipeline_editor/components/file-tree/container_spec.js
index 11ba517e0eb..306dd78d395 100644
--- a/spec/frontend/ci/pipeline_editor/components/file-tree/container_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/file-tree/container_spec.js
@@ -22,7 +22,7 @@ describe('Pipeline editor file nav', () => {
includes,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
stubs,
}),
@@ -35,7 +35,6 @@ describe('Pipeline editor file nav', () => {
afterEach(() => {
localStorage.clear();
- wrapper.destroy();
});
describe('template', () => {
diff --git a/spec/frontend/ci/pipeline_editor/components/file-tree/file_item_spec.js b/spec/frontend/ci/pipeline_editor/components/file-tree/file_item_spec.js
index bceb741f91c..80737e9a8ab 100644
--- a/spec/frontend/ci/pipeline_editor/components/file-tree/file_item_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/file-tree/file_item_spec.js
@@ -18,10 +18,6 @@ describe('Pipeline editor file nav', () => {
const fileIcon = () => wrapper.findComponent(FileIcon);
const link = () => wrapper.findComponent(GlLink);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/ci/pipeline_editor/components/header/pipeline_editor_header_spec.js b/spec/frontend/ci/pipeline_editor/components/header/pipeline_editor_header_spec.js
index 555b9f29fbf..a651664851e 100644
--- a/spec/frontend/ci/pipeline_editor/components/header/pipeline_editor_header_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/header/pipeline_editor_header_spec.js
@@ -26,11 +26,6 @@ describe('Pipeline editor header', () => {
const findPipelineStatus = () => wrapper.findComponent(PipelineStatus);
const findValidationSegment = () => wrapper.findComponent(ValidationSegment);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('template', () => {
it('hides the pipeline status for new projects without a CI file', () => {
createComponent({ props: { isNewCiConfigFile: true } });
diff --git a/spec/frontend/ci/pipeline_editor/components/header/pipeline_status_spec.js b/spec/frontend/ci/pipeline_editor/components/header/pipeline_status_spec.js
index a62c51ffb59..3faa2890254 100644
--- a/spec/frontend/ci/pipeline_editor/components/header/pipeline_status_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/header/pipeline_status_spec.js
@@ -48,7 +48,6 @@ describe('Pipeline Status', () => {
afterEach(() => {
mockPipelineQuery.mockReset();
- wrapper.destroy();
});
describe('loading icon', () => {
diff --git a/spec/frontend/ci/pipeline_editor/components/header/validation_segment_spec.js b/spec/frontend/ci/pipeline_editor/components/header/validation_segment_spec.js
index 0853a6f4ca4..a107a626c6d 100644
--- a/spec/frontend/ci/pipeline_editor/components/header/validation_segment_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/header/validation_segment_spec.js
@@ -1,11 +1,10 @@
import VueApollo from 'vue-apollo';
-import { GlIcon } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
import Vue from 'vue';
import { escape } from 'lodash';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import createMockApollo from 'helpers/mock_apollo_helper';
import { sprintf } from '~/locale';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import createMockApollo from 'helpers/mock_apollo_helper';
import ValidationSegment, {
i18n,
} from '~/ci/pipeline_editor/components/header/validation_segment.vue';
@@ -20,8 +19,8 @@ import {
} from '~/ci/pipeline_editor/constants';
import {
mergeUnwrappedCiConfig,
+ mockCiTroubleshootingPath,
mockCiYml,
- mockLintUnavailableHelpPagePath,
mockYmlHelpPagePath,
} from '../../mock_data';
@@ -43,29 +42,27 @@ describe('Validation segment component', () => {
},
});
- wrapper = extendedWrapper(
- shallowMount(ValidationSegment, {
- apolloProvider: mockApollo,
- provide: {
- ymlHelpPagePath: mockYmlHelpPagePath,
- lintUnavailableHelpPagePath: mockLintUnavailableHelpPagePath,
- },
- propsData: {
- ciConfig: mergeUnwrappedCiConfig(),
- ciFileContent: mockCiYml,
- ...props,
- },
- }),
- );
+ wrapper = shallowMountExtended(ValidationSegment, {
+ apolloProvider: mockApollo,
+ provide: {
+ ymlHelpPagePath: mockYmlHelpPagePath,
+ ciTroubleshootingPath: mockCiTroubleshootingPath,
+ },
+ propsData: {
+ ciConfig: mergeUnwrappedCiConfig(),
+ ciFileContent: mockCiYml,
+ ...props,
+ },
+ stubs: {
+ GlSprintf,
+ },
+ });
};
const findIcon = () => wrapper.findComponent(GlIcon);
- const findLearnMoreLink = () => wrapper.findByTestId('learnMoreLink');
- const findValidationMsg = () => wrapper.findByTestId('validationMsg');
-
- afterEach(() => {
- wrapper.destroy();
- });
+ const findHelpLink = () => wrapper.findComponent(GlLink);
+ const findValidationMsg = () => wrapper.findComponent(GlSprintf);
+ const findValidationSegment = () => wrapper.findByTestId('validation-segment');
it('shows the loading state', () => {
createComponent({ appStatus: EDITOR_APP_STATUS_LOADING });
@@ -82,8 +79,12 @@ describe('Validation segment component', () => {
expect(findIcon().props('name')).toBe('check');
});
+ it('does not render a link', () => {
+ expect(findHelpLink().exists()).toBe(false);
+ });
+
it('shows a message for empty state', () => {
- expect(findValidationMsg().text()).toBe(i18n.empty);
+ expect(findValidationSegment().text()).toBe(i18n.empty);
});
});
@@ -97,12 +98,15 @@ describe('Validation segment component', () => {
});
it('shows a message for valid state', () => {
- expect(findValidationMsg().text()).toContain(i18n.valid);
+ expect(findValidationSegment().text()).toBe(
+ sprintf(i18n.valid, { linkStart: '', linkEnd: '' }),
+ );
});
it('shows the learn more link', () => {
- expect(findLearnMoreLink().attributes('href')).toBe(mockYmlHelpPagePath);
- expect(findLearnMoreLink().text()).toBe(i18n.learnMore);
+ expect(findValidationMsg().exists()).toBe(true);
+ expect(findValidationMsg().text()).toBe('Learn more');
+ expect(findHelpLink().attributes('href')).toBe(mockYmlHelpPagePath);
});
});
@@ -117,13 +121,16 @@ describe('Validation segment component', () => {
expect(findIcon().props('name')).toBe('warning-solid');
});
- it('has message for invalid state', () => {
- expect(findValidationMsg().text()).toBe(i18n.invalid);
+ it('shows a message for invalid state', () => {
+ expect(findValidationSegment().text()).toBe(
+ sprintf(i18n.invalid, { linkStart: '', linkEnd: '' }),
+ );
});
it('shows the learn more link', () => {
- expect(findLearnMoreLink().attributes('href')).toBe(mockYmlHelpPagePath);
- expect(findLearnMoreLink().text()).toBe('Learn more');
+ expect(findValidationMsg().exists()).toBe(true);
+ expect(findValidationMsg().text()).toBe('Learn more');
+ expect(findHelpLink().attributes('href')).toBe(mockYmlHelpPagePath);
});
describe('with multiple errors', () => {
@@ -140,11 +147,16 @@ describe('Validation segment component', () => {
},
});
});
+
+ it('shows the learn more link', () => {
+ expect(findValidationMsg().exists()).toBe(true);
+ expect(findValidationMsg().text()).toBe('Learn more');
+ expect(findHelpLink().attributes('href')).toBe(mockYmlHelpPagePath);
+ });
+
it('shows an invalid state with an error', () => {
- // Test the error is shown _and_ the string matches
- expect(findValidationMsg().text()).toContain(firstError);
- expect(findValidationMsg().text()).toBe(
- sprintf(i18n.invalidWithReason, { reason: firstError }),
+ expect(findValidationSegment().text()).toBe(
+ sprintf(i18n.invalidWithReason, { reason: firstError, linkStart: '', linkEnd: '' }),
);
});
});
@@ -163,10 +175,8 @@ describe('Validation segment component', () => {
});
});
it('shows an invalid state with an error while preventing XSS', () => {
- const { innerHTML } = findValidationMsg().element;
-
- expect(innerHTML).not.toContain(evilError);
- expect(innerHTML).toContain(escape(evilError));
+ expect(findValidationSegment().html()).not.toContain(evilError);
+ expect(findValidationSegment().html()).toContain(escape(evilError));
});
});
});
@@ -182,16 +192,18 @@ describe('Validation segment component', () => {
});
it('show a message that the service is unavailable', () => {
- expect(findValidationMsg().text()).toBe(i18n.unavailableValidation);
+ expect(findValidationSegment().text()).toBe(
+ sprintf(i18n.unavailableValidation, { linkStart: '', linkEnd: '' }),
+ );
});
it('shows the time-out icon', () => {
expect(findIcon().props('name')).toBe('time-out');
});
- it('shows the learn more link', () => {
- expect(findLearnMoreLink().attributes('href')).toBe(mockLintUnavailableHelpPagePath);
- expect(findLearnMoreLink().text()).toBe(i18n.learnMore);
+ it('shows the link to ci troubleshooting', () => {
+ expect(findValidationMsg().exists()).toBe(true);
+ expect(findHelpLink().attributes('href')).toBe(mockCiTroubleshootingPath);
});
});
});
diff --git a/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/image_item_spec.js b/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/image_item_spec.js
new file mode 100644
index 00000000000..c7c40c3a4b9
--- /dev/null
+++ b/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/image_item_spec.js
@@ -0,0 +1,39 @@
+import ImageItem from '~/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/image_item.vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { JOB_TEMPLATE } from '~/ci/pipeline_editor/components/job_assistant_drawer/constants';
+
+describe('Image item', () => {
+ let wrapper;
+
+ const findImageNameInput = () => wrapper.findByTestId('image-name-input');
+ const findImageEntrypointInput = () => wrapper.findByTestId('image-entrypoint-input');
+
+ const dummyImageName = 'dummyImageName';
+ const dummyImageEntrypoint = 'dummyImageEntrypoint';
+
+ const createComponent = ({ job = JSON.parse(JSON.stringify(JOB_TEMPLATE)) } = {}) => {
+ wrapper = shallowMountExtended(ImageItem, {
+ propsData: {
+ job,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should emit update job event when filling inputs', () => {
+ expect(wrapper.emitted('update-job')).toBeUndefined();
+
+ findImageNameInput().vm.$emit('input', dummyImageName);
+
+ expect(wrapper.emitted('update-job')).toHaveLength(1);
+ expect(wrapper.emitted('update-job')[0]).toEqual(['image.name', dummyImageName]);
+
+ findImageEntrypointInput().vm.$emit('input', dummyImageEntrypoint);
+
+ expect(wrapper.emitted('update-job')).toHaveLength(2);
+ expect(wrapper.emitted('update-job')[1]).toEqual(['image.entrypoint', [dummyImageEntrypoint]]);
+ });
+});
diff --git a/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/job_setup_item_spec.js b/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/job_setup_item_spec.js
new file mode 100644
index 00000000000..eaad0dae90d
--- /dev/null
+++ b/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/job_setup_item_spec.js
@@ -0,0 +1,61 @@
+import createStore from '~/ci/pipeline_editor/store';
+import JobSetupItem from '~/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/job_setup_item.vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { JOB_TEMPLATE } from '~/ci/pipeline_editor/components/job_assistant_drawer/constants';
+
+describe('Job setup item', () => {
+ let wrapper;
+
+ const findJobNameInput = () => wrapper.findByTestId('job-name-input');
+ const findJobScriptInput = () => wrapper.findByTestId('job-script-input');
+ const findJobTagsInput = () => wrapper.findByTestId('job-tags-input');
+ const findJobStageInput = () => wrapper.findByTestId('job-stage-input');
+
+ const dummyJobName = 'dummyJobName';
+ const dummyJobScript = 'dummyJobScript';
+ const dummyJobStage = 'dummyJobStage';
+ const dummyJobTags = ['tag1'];
+
+ const createComponent = () => {
+ wrapper = shallowMountExtended(JobSetupItem, {
+ store: createStore(),
+ propsData: {
+ tagOptions: [
+ { id: 'tag1', name: 'tag1' },
+ { id: 'tag2', name: 'tag2' },
+ ],
+ isNameValid: true,
+ isScriptValid: true,
+ job: JSON.parse(JSON.stringify(JOB_TEMPLATE)),
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should emit update job event when filling inputs', () => {
+ expect(wrapper.emitted('update-job')).toBeUndefined();
+
+ findJobNameInput().vm.$emit('input', dummyJobName);
+
+ expect(wrapper.emitted('update-job')).toHaveLength(1);
+ expect(wrapper.emitted('update-job')[0]).toEqual(['name', dummyJobName]);
+
+ findJobScriptInput().vm.$emit('input', dummyJobScript);
+
+ expect(wrapper.emitted('update-job')).toHaveLength(2);
+ expect(wrapper.emitted('update-job')[1]).toEqual(['script', dummyJobScript]);
+
+ findJobStageInput().vm.$emit('input', dummyJobStage);
+
+ expect(wrapper.emitted('update-job')).toHaveLength(3);
+ expect(wrapper.emitted('update-job')[2]).toEqual(['stage', dummyJobStage]);
+
+ findJobTagsInput().vm.$emit('input', dummyJobTags);
+
+ expect(wrapper.emitted('update-job')).toHaveLength(4);
+ expect(wrapper.emitted('update-job')[3]).toEqual(['tags', dummyJobTags]);
+ });
+});
diff --git a/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer_spec.js b/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer_spec.js
index 79200d92598..b293805d653 100644
--- a/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer_spec.js
@@ -1,24 +1,47 @@
import { GlDrawer } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
-import Vue from 'vue';
+import Vue, { nextTick } from 'vue';
+import { stringify } from 'yaml';
import JobAssistantDrawer from '~/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue';
+import JobSetupItem from '~/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/job_setup_item.vue';
+import ImageItem from '~/ci/pipeline_editor/components/job_assistant_drawer/accordion_items/image_item.vue';
+import getAllRunners from '~/ci/runner/graphql/list/all_runners.query.graphql';
+import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
+import createStore from '~/ci/pipeline_editor/store';
+import { mockAllRunnersQueryResponse } from 'jest/ci/pipeline_editor/mock_data';
import { mountExtended } from 'helpers/vue_test_utils_helper';
+import eventHub, { SCROLL_EDITOR_TO_BOTTOM } from '~/ci/pipeline_editor/event_hub';
Vue.use(VueApollo);
describe('Job assistant drawer', () => {
let wrapper;
+ let mockApollo;
+
+ const dummyJobName = 'a';
+ const dummyJobScript = 'b';
+ const dummyImageName = 'c';
+ const dummyImageEntrypoint = 'd';
const findDrawer = () => wrapper.findComponent(GlDrawer);
+ const findJobSetupItem = () => wrapper.findComponent(JobSetupItem);
+ const findImageItem = () => wrapper.findComponent(ImageItem);
+ const findConfirmButton = () => wrapper.findByTestId('confirm-button');
const findCancelButton = () => wrapper.findByTestId('cancel-button');
const createComponent = () => {
+ mockApollo = createMockApollo([
+ [getAllRunners, jest.fn().mockResolvedValue(mockAllRunnersQueryResponse)],
+ ]);
+
wrapper = mountExtended(JobAssistantDrawer, {
+ store: createStore(),
propsData: {
isVisible: true,
},
+ apolloProvider: mockApollo,
});
};
@@ -27,6 +50,14 @@ describe('Job assistant drawer', () => {
await waitForPromises();
});
+ it('should contain job setup accordion', () => {
+ expect(findJobSetupItem().exists()).toBe(true);
+ });
+
+ it('should contain image accordion', () => {
+ expect(findImageItem().exists()).toBe(true);
+ });
+
it('should emit close job assistant drawer event when closing the drawer', () => {
expect(wrapper.emitted('close-job-assistant-drawer')).toBeUndefined();
@@ -42,4 +73,83 @@ describe('Job assistant drawer', () => {
expect(wrapper.emitted('close-job-assistant-drawer')).toHaveLength(1);
});
+
+ it('trigger validate if job name is empty', async () => {
+ const updateCiConfigSpy = jest.spyOn(wrapper.vm, 'updateCiConfig');
+ findJobSetupItem().vm.$emit('update-job', 'script', 'b');
+ findConfirmButton().trigger('click');
+
+ await nextTick();
+
+ expect(findJobSetupItem().props('isNameValid')).toBe(false);
+ expect(findJobSetupItem().props('isScriptValid')).toBe(true);
+ expect(updateCiConfigSpy).toHaveBeenCalledTimes(0);
+ });
+
+ describe('when enter valid input', () => {
+ beforeEach(() => {
+ findJobSetupItem().vm.$emit('update-job', 'name', dummyJobName);
+ findJobSetupItem().vm.$emit('update-job', 'script', dummyJobScript);
+ findImageItem().vm.$emit('update-job', 'image.name', dummyImageName);
+ findImageItem().vm.$emit('update-job', 'image.entrypoint', [dummyImageEntrypoint]);
+ });
+
+ it('passes correct prop to accordions', () => {
+ const accordions = [findJobSetupItem(), findImageItem()];
+ accordions.forEach((accordion) => {
+ expect(accordion.props('job')).toMatchObject({
+ name: dummyJobName,
+ script: dummyJobScript,
+ image: {
+ name: dummyImageName,
+ entrypoint: [dummyImageEntrypoint],
+ },
+ });
+ });
+ });
+
+ it('job name and script state should be valid', () => {
+ expect(findJobSetupItem().props('isNameValid')).toBe(true);
+ expect(findJobSetupItem().props('isScriptValid')).toBe(true);
+ });
+
+ it('should clear job data when click confirm button', async () => {
+ findConfirmButton().trigger('click');
+
+ await nextTick();
+
+ expect(findJobSetupItem().props('job')).toMatchObject({ name: '', script: '' });
+ });
+
+ it('should clear job data when click cancel button', async () => {
+ findCancelButton().trigger('click');
+
+ await nextTick();
+
+ expect(findJobSetupItem().props('job')).toMatchObject({ name: '', script: '' });
+ });
+
+ it('should update correct ci content when click add button', () => {
+ const updateCiConfigSpy = jest.spyOn(wrapper.vm, 'updateCiConfig');
+
+ findConfirmButton().trigger('click');
+
+ expect(updateCiConfigSpy).toHaveBeenCalledWith(
+ `\n${stringify({
+ [dummyJobName]: {
+ script: dummyJobScript,
+ image: { name: dummyImageName, entrypoint: [dummyImageEntrypoint] },
+ },
+ })}`,
+ );
+ });
+
+ it('should emit scroll editor to button event when click add button', () => {
+ const eventHubSpy = jest.spyOn(eventHub, '$emit');
+
+ findConfirmButton().trigger('click');
+
+ expect(eventHubSpy).toHaveBeenCalledWith(SCROLL_EDITOR_TO_BOTTOM);
+ });
+ });
});
diff --git a/spec/frontend/ci/pipeline_editor/components/lint/ci_lint_results_spec.js b/spec/frontend/ci/pipeline_editor/components/lint/ci_lint_results_spec.js
index d43bdec3a33..cc9a77ae525 100644
--- a/spec/frontend/ci/pipeline_editor/components/lint/ci_lint_results_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/lint/ci_lint_results_spec.js
@@ -40,10 +40,6 @@ describe('CI Lint Results', () => {
const findAfterScripts = findAllByTestId('after-script');
const filterEmptyScripts = (property) => mockJobs.filter((job) => job[property].length !== 0);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Empty results', () => {
it('renders with no jobs, errors or warnings defined', () => {
createComponent({ jobs: undefined, errors: undefined, warnings: undefined }, shallowMount);
diff --git a/spec/frontend/ci/pipeline_editor/components/lint/ci_lint_warnings_spec.js b/spec/frontend/ci/pipeline_editor/components/lint/ci_lint_warnings_spec.js
index b5e3ea06c2c..d09e22898cd 100644
--- a/spec/frontend/ci/pipeline_editor/components/lint/ci_lint_warnings_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/lint/ci_lint_warnings_spec.js
@@ -21,11 +21,6 @@ describe('CI lint warnings', () => {
const findWarnings = () => wrapper.findAll('[data-testid="ci-lint-warning"]');
const findWarningMessage = () => trimText(wrapper.findComponent(GlSprintf).text());
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('displays the warning alert', () => {
createComponent();
diff --git a/spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js b/spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js
index f40db50aab7..52a543c7686 100644
--- a/spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js
@@ -119,6 +119,7 @@ describe('Pipeline editor tabs component', () => {
});
afterEach(() => {
+ // eslint-disable-next-line @gitlab/vtu-no-explicit-wrapper-destroy
wrapper.destroy();
});
diff --git a/spec/frontend/ci/pipeline_editor/components/popovers/file_tree_popover_spec.js b/spec/frontend/ci/pipeline_editor/components/popovers/file_tree_popover_spec.js
index 63ebfc0559d..a9aabb103f2 100644
--- a/spec/frontend/ci/pipeline_editor/components/popovers/file_tree_popover_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/popovers/file_tree_popover_spec.js
@@ -22,7 +22,6 @@ describe('FileTreePopover component', () => {
afterEach(() => {
localStorage.clear();
- wrapper.destroy();
});
describe('default', () => {
diff --git a/spec/frontend/ci/pipeline_editor/components/popovers/validate_pipeline_popover_spec.js b/spec/frontend/ci/pipeline_editor/components/popovers/validate_pipeline_popover_spec.js
index cf0b974081e..23f9c7a87ee 100644
--- a/spec/frontend/ci/pipeline_editor/components/popovers/validate_pipeline_popover_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/popovers/validate_pipeline_popover_spec.js
@@ -19,10 +19,6 @@ describe('ValidatePopover component', () => {
const findHelpLink = () => wrapper.findByTestId('help-link');
const findFeedbackLink = () => wrapper.findByTestId('feedback-link');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
beforeEach(async () => {
createComponent({
diff --git a/spec/frontend/ci/pipeline_editor/components/popovers/walkthrough_popover_spec.js b/spec/frontend/ci/pipeline_editor/components/popovers/walkthrough_popover_spec.js
index ca6033f2ff5..186fd803d47 100644
--- a/spec/frontend/ci/pipeline_editor/components/popovers/walkthrough_popover_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/popovers/walkthrough_popover_spec.js
@@ -12,10 +12,6 @@ describe('WalkthroughPopover component', () => {
return extendedWrapper(mountFn(WalkthroughPopover));
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('CTA button clicked', () => {
beforeEach(async () => {
wrapper = createComponent(mount);
diff --git a/spec/frontend/ci/pipeline_editor/components/ui/confirm_unsaved_changes_dialog_spec.js b/spec/frontend/ci/pipeline_editor/components/ui/confirm_unsaved_changes_dialog_spec.js
index b22c98e5544..8b8dd4d22c2 100644
--- a/spec/frontend/ci/pipeline_editor/components/ui/confirm_unsaved_changes_dialog_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/ui/confirm_unsaved_changes_dialog_spec.js
@@ -4,10 +4,9 @@ import ConfirmDialog from '~/ci/pipeline_editor/components/ui/confirm_unsaved_ch
describe('pipeline_editor/components/ui/confirm_unsaved_changes_dialog', () => {
let beforeUnloadEvent;
let setDialogContent;
- let wrapper;
const createComponent = (propsData = {}) => {
- wrapper = shallowMount(ConfirmDialog, {
+ shallowMount(ConfirmDialog, {
propsData,
});
};
@@ -21,7 +20,6 @@ describe('pipeline_editor/components/ui/confirm_unsaved_changes_dialog', () => {
afterEach(() => {
beforeUnloadEvent.preventDefault.mockRestore();
setDialogContent.mockRestore();
- wrapper.destroy();
});
it('shows confirmation dialog when there are unsaved changes', () => {
diff --git a/spec/frontend/ci/pipeline_editor/components/ui/pipeline_editor_empty_state_spec.js b/spec/frontend/ci/pipeline_editor/components/ui/pipeline_editor_empty_state_spec.js
index 3c68f74af43..e636a89c6d9 100644
--- a/spec/frontend/ci/pipeline_editor/components/ui/pipeline_editor_empty_state_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/ui/pipeline_editor_empty_state_spec.js
@@ -23,10 +23,6 @@ describe('Pipeline editor empty state', () => {
const findConfirmButton = () => wrapper.findComponent(GlButton);
const findDescription = () => wrapper.findComponent(GlSprintf);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when project uses an external CI config', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/ci/pipeline_editor/components/validate/ci_validate_spec.js b/spec/frontend/ci/pipeline_editor/components/validate/ci_validate_spec.js
index ae25142b455..8874add6bb2 100644
--- a/spec/frontend/ci/pipeline_editor/components/validate/ci_validate_spec.js
+++ b/spec/frontend/ci/pipeline_editor/components/validate/ci_validate_spec.js
@@ -99,10 +99,6 @@ describe('Pipeline Editor Validate Tab', () => {
mockBlobContentData = jest.fn();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('while initial CI content is loading', () => {
beforeEach(() => {
createComponent({ isBlobLoading: true });
diff --git a/spec/frontend/ci/pipeline_editor/mock_data.js b/spec/frontend/ci/pipeline_editor/mock_data.js
index 541123d7efc..ecfc477184b 100644
--- a/spec/frontend/ci/pipeline_editor/mock_data.js
+++ b/spec/frontend/ci/pipeline_editor/mock_data.js
@@ -12,7 +12,7 @@ export const mockCommitSha = 'aabbccdd';
export const mockCommitNextSha = 'eeffgghh';
export const mockIncludesHelpPagePath = '/-/includes/help';
export const mockLintHelpPagePath = '/-/lint-help';
-export const mockLintUnavailableHelpPagePath = '/-/pipeline-editor/troubleshoot';
+export const mockCiTroubleshootingPath = '/-/pipeline-editor/troubleshoot';
export const mockSimulatePipelineHelpPagePath = '/-/simulate-pipeline-help';
export const mockYmlHelpPagePath = '/-/yml-help';
export const mockCommitMessage = 'My commit message';
@@ -583,6 +583,91 @@ export const mockCommitCreateResponse = {
},
};
+export const mockAllRunnersQueryResponse = {
+ data: {
+ runners: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Ci::Runner/1',
+ description: 'test',
+ runnerType: 'PROJECT_TYPE',
+ shortSha: 'DdTYMQGS',
+ version: '15.6.1',
+ ipAddress: '127.0.0.1',
+ active: true,
+ locked: true,
+ jobCount: 0,
+ jobExecutionStatus: 'IDLE',
+ tagList: ['tag1', 'tag2', 'tag3'],
+ createdAt: '2022-11-29T09:37:43Z',
+ contactedAt: null,
+ status: 'NEVER_CONTACTED',
+ userPermissions: {
+ updateRunner: true,
+ deleteRunner: true,
+ __typename: 'RunnerPermissions',
+ },
+ groups: null,
+ ownerProject: {
+ id: 'gid://gitlab/Project/1',
+ name: '123',
+ nameWithNamespace: 'Administrator / 123',
+ webUrl: 'http://127.0.0.1:3000/root/test',
+ __typename: 'Project',
+ },
+ __typename: 'CiRunner',
+ upgradeStatus: 'NOT_AVAILABLE',
+ adminUrl: 'http://127.0.0.1:3000/admin/runners/1',
+ editAdminUrl: 'http://127.0.0.1:3000/admin/runners/1/edit',
+ },
+ {
+ id: 'gid://gitlab/Ci::Runner/2',
+ description: 'test',
+ runnerType: 'PROJECT_TYPE',
+ shortSha: 'DdTYMQGA',
+ version: '15.6.1',
+ ipAddress: '127.0.0.1',
+ active: true,
+ locked: true,
+ jobCount: 0,
+ jobExecutionStatus: 'IDLE',
+ tagList: ['tag3', 'tag4'],
+ createdAt: '2022-11-29T09:37:43Z',
+ contactedAt: null,
+ status: 'NEVER_CONTACTED',
+ userPermissions: {
+ updateRunner: true,
+ deleteRunner: true,
+ __typename: 'RunnerPermissions',
+ },
+ groups: null,
+ ownerProject: {
+ id: 'gid://gitlab/Project/1',
+ name: '123',
+ nameWithNamespace: 'Administrator / 123',
+ webUrl: 'http://127.0.0.1:3000/root/test',
+ __typename: 'Project',
+ },
+ __typename: 'CiRunner',
+ upgradeStatus: 'NOT_AVAILABLE',
+ adminUrl: 'http://127.0.0.1:3000/admin/runners/2',
+ editAdminUrl: 'http://127.0.0.1:3000/admin/runners/2/edit',
+ },
+ ],
+ pageInfo: {
+ hasNextPage: false,
+ hasPreviousPage: false,
+ startCursor:
+ 'eyJjcmVhdGVkX2F0IjoiMjAyMi0xMS0yOSAwOTozNzo0My40OTEwNTEwMDAgKzAwMDAiLCJpZCI6IjIifQ',
+ endCursor:
+ 'eyJjcmVhdGVkX2F0IjoiMjAyMi0xMS0yOSAwOTozNzo0My40OTEwNTEwMDAgKzAwMDAiLCJpZCI6IjIifQ',
+ __typename: 'PageInfo',
+ },
+ __typename: 'CiRunnerConnection',
+ },
+ },
+};
+
export const mockCommitCreateResponseNewEtag = {
data: {
commitCreate: {
diff --git a/spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js
index a103acb33bc..7a13bfbd1ab 100644
--- a/spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js
+++ b/spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js
@@ -1,4 +1,4 @@
-import { GlAlert, GlButton, GlLoadingIcon } from '@gitlab/ui';
+import { GlAlert, GlButton, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -8,6 +8,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status';
import { objectToQuery, redirectTo } from '~/lib/utils/url_utility';
import { resolvers } from '~/ci/pipeline_editor/graphql/resolvers';
+import createStore from '~/ci/pipeline_editor/store';
import PipelineEditorTabs from '~/ci/pipeline_editor/components/pipeline_editor_tabs.vue';
import PipelineEditorEmptyState from '~/ci/pipeline_editor/components/ui/pipeline_editor_empty_state.vue';
import PipelineEditorMessages from '~/ci/pipeline_editor/components/ui/pipeline_editor_messages.vue';
@@ -80,7 +81,9 @@ describe('Pipeline editor app component', () => {
provide = {},
stubs = {},
} = {}) => {
+ const store = createStore();
wrapper = shallowMount(PipelineEditorApp, {
+ store,
provide: { ...defaultProvide, ...provide },
stubs,
mocks: {
@@ -162,10 +165,6 @@ describe('Pipeline editor app component', () => {
mockPipelineQuery = jest.fn();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('loading state', () => {
it('displays a loading icon if the blob query is loading', () => {
createComponent({ blobLoading: true });
@@ -256,6 +255,10 @@ describe('Pipeline editor app component', () => {
.mockImplementation(jest.fn());
});
+ it('available stages is updated', () => {
+ expect(wrapper.vm.$store.state.availableStages).toStrictEqual(['test', 'build']);
+ });
+
it('shows pipeline editor home component', () => {
expect(findEditorHome().exists()).toBe(true);
});
@@ -351,7 +354,9 @@ describe('Pipeline editor app component', () => {
});
it('shows that the lint service is down', () => {
- expect(findValidationSegment().text()).toContain(
+ const validationMessage = findValidationSegment().findComponent(GlSprintf);
+
+ expect(validationMessage.attributes('message')).toContain(
validationSegmenti18n.unavailableValidation,
);
});
diff --git a/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js b/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js
index 4f8f2112abe..7ec6d4c6a01 100644
--- a/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js
+++ b/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js
@@ -67,7 +67,6 @@ describe('Pipeline editor home wrapper', () => {
afterEach(() => {
localStorage.clear();
- wrapper.destroy();
});
describe('renders', () => {
diff --git a/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js b/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js
index 6f18899ebac..1349461d8bc 100644
--- a/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js
+++ b/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js
@@ -32,6 +32,7 @@ import {
mockProjectId,
mockRefs,
mockYamlVariables,
+ mockPipelineConfigButtonText,
} from '../mock_data';
Vue.use(VueApollo);
@@ -42,6 +43,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
const projectRefsEndpoint = '/root/project/refs';
const pipelinesPath = '/root/project/-/pipelines';
+const pipelinesEditorPath = '/root/project/-/ci/editor';
const projectPath = '/root/project/-/pipelines/config_variables';
const newPipelinePostResponse = { id: 1 };
const defaultBranch = 'main';
@@ -65,6 +67,7 @@ describe('Pipeline New Form', () => {
wrapper.findAllByTestId('pipeline-form-ci-variable-value-dropdown');
const findValueDropdownItems = (dropdown) => dropdown.findAllComponents(GlDropdownItem);
const findErrorAlert = () => wrapper.findByTestId('run-pipeline-error-alert');
+ const findPipelineConfigButton = () => wrapper.findByTestId('ci-cd-pipeline-configuration');
const findWarningAlert = () => wrapper.findByTestId('run-pipeline-warning-alert');
const findWarningAlertSummary = () => findWarningAlert().findComponent(GlSprintf);
const findWarnings = () => wrapper.findAllByTestId('run-pipeline-warning');
@@ -106,6 +109,8 @@ describe('Pipeline New Form', () => {
propsData: {
projectId: mockProjectId,
pipelinesPath,
+ pipelinesEditorPath,
+ canViewPipelineEditor: true,
projectPath,
defaultBranch,
refParam: defaultBranch,
@@ -128,7 +133,6 @@ describe('Pipeline New Form', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
describe('Form', () => {
@@ -500,6 +504,17 @@ describe('Pipeline New Form', () => {
expect(findSubmitButton().props('disabled')).toBe(false);
});
+ it('shows pipeline configuration button for user who can view', () => {
+ expect(findPipelineConfigButton().exists()).toBe(true);
+ expect(findPipelineConfigButton().text()).toBe(mockPipelineConfigButtonText);
+ });
+
+ it('does not show pipeline configuration button for user who can not view', () => {
+ createComponentWithApollo({ props: { canViewPipelineEditor: false } });
+
+ expect(findPipelineConfigButton().exists()).toBe(false);
+ });
+
it('does not show the credit card validation required alert', () => {
expect(findCCAlert().exists()).toBe(false);
});
diff --git a/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js b/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js
index cf8009e388f..60ace483712 100644
--- a/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js
+++ b/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js
@@ -1,4 +1,4 @@
-import { GlListbox, GlListboxItem } from '@gitlab/ui';
+import { GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -13,13 +13,13 @@ const projectRefsEndpoint = '/root/project/refs';
const refShortName = 'main';
const refFullName = 'refs/heads/main';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Pipeline New Form', () => {
let wrapper;
let mock;
- const findDropdown = () => wrapper.findComponent(GlListbox);
+ const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
const findRefsDropdownItems = () => wrapper.findAllComponents(GlListboxItem);
const findSearchBox = () => wrapper.findByTestId('listbox-search-input');
const findListboxGroups = () => wrapper.findAll('ul[role="group"]');
diff --git a/spec/frontend/ci/pipeline_new/mock_data.js b/spec/frontend/ci/pipeline_new/mock_data.js
index 5b935c0c819..175f513217b 100644
--- a/spec/frontend/ci/pipeline_new/mock_data.js
+++ b/spec/frontend/ci/pipeline_new/mock_data.js
@@ -133,3 +133,5 @@ export const mockCiConfigVariablesResponseWithoutDesc = mockCiConfigVariablesQue
mockYamlVariablesWithoutDesc,
);
export const mockNoCachedCiConfigVariablesResponse = mockCiConfigVariablesQueryResponse(null);
+
+export const mockPipelineConfigButtonText = 'Go to the pipeline editor';
diff --git a/spec/frontend/ci/pipeline_schedules/components/delete_pipeline_schedule_modal_spec.js b/spec/frontend/ci/pipeline_schedules/components/delete_pipeline_schedule_modal_spec.js
index ba948f12b33..c45267e5a47 100644
--- a/spec/frontend/ci/pipeline_schedules/components/delete_pipeline_schedule_modal_spec.js
+++ b/spec/frontend/ci/pipeline_schedules/components/delete_pipeline_schedule_modal_spec.js
@@ -20,10 +20,6 @@ describe('Delete pipeline schedule modal', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('emits the deleteSchedule event', async () => {
findModal().vm.$emit('primary');
diff --git a/spec/frontend/ci/pipeline_schedules/components/pipeline_schedules_spec.js b/spec/frontend/ci/pipeline_schedules/components/pipeline_schedules_spec.js
index 611993556e3..50008cedd9c 100644
--- a/spec/frontend/ci/pipeline_schedules/components/pipeline_schedules_spec.js
+++ b/spec/frontend/ci/pipeline_schedules/components/pipeline_schedules_spec.js
@@ -16,6 +16,7 @@ import getPipelineSchedulesQuery from '~/ci/pipeline_schedules/graphql/queries/g
import {
mockGetPipelineSchedulesGraphQLResponse,
mockPipelineScheduleNodes,
+ mockPipelineScheduleCurrentUser,
deleteMutationResponse,
playMutationResponse,
takeOwnershipMutationResponse,
@@ -79,10 +80,6 @@ describe('Pipeline schedules app', () => {
const findSchedulesCharacteristics = () =>
wrapper.findByTestId('pipeline-schedules-characteristics');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
beforeEach(() => {
createComponent();
@@ -115,6 +112,7 @@ describe('Pipeline schedules app', () => {
await waitForPromises();
expect(findTable().props('schedules')).toEqual(mockPipelineScheduleNodes);
+ expect(findTable().props('currentUser')).toEqual(mockPipelineScheduleCurrentUser);
});
it('shows query error alert', async () => {
diff --git a/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_actions_spec.js b/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_actions_spec.js
index 6fb6a8bc33b..be0052fc7cf 100644
--- a/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_actions_spec.js
+++ b/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_actions_spec.js
@@ -3,6 +3,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import PipelineScheduleActions from '~/ci/pipeline_schedules/components/table/cells/pipeline_schedule_actions.vue';
import {
mockPipelineScheduleNodes,
+ mockPipelineScheduleCurrentUser,
mockPipelineScheduleAsGuestNodes,
mockTakeOwnershipNodes,
} from '../../../mock_data';
@@ -12,6 +13,7 @@ describe('Pipeline schedule actions', () => {
const defaultProps = {
schedule: mockPipelineScheduleNodes[0],
+ currentUser: mockPipelineScheduleCurrentUser,
};
const createComponent = (props = defaultProps) => {
@@ -27,18 +29,17 @@ describe('Pipeline schedule actions', () => {
const findTakeOwnershipBtn = () => wrapper.findByTestId('take-ownership-pipeline-schedule-btn');
const findPlayScheduleBtn = () => wrapper.findByTestId('play-pipeline-schedule-btn');
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('displays action buttons', () => {
+ it('displays buttons when user is the owner of schedule and has adminPipelineSchedule permissions', () => {
createComponent();
expect(findAllButtons()).toHaveLength(3);
});
- it('does not display action buttons', () => {
- createComponent({ schedule: mockPipelineScheduleAsGuestNodes[0] });
+ it('does not display action buttons when user is not owner and does not have adminPipelineSchedule permission', () => {
+ createComponent({
+ schedule: mockPipelineScheduleAsGuestNodes[0],
+ currentUser: mockPipelineScheduleCurrentUser,
+ });
expect(findAllButtons()).toHaveLength(0);
});
@@ -54,7 +55,10 @@ describe('Pipeline schedule actions', () => {
});
it('take ownership button emits showTakeOwnershipModal event and schedule id', () => {
- createComponent({ schedule: mockTakeOwnershipNodes[0] });
+ createComponent({
+ schedule: mockTakeOwnershipNodes[0],
+ currentUser: mockPipelineScheduleCurrentUser,
+ });
findTakeOwnershipBtn().vm.$emit('click');
diff --git a/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js b/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js
index 0821c59c8a0..ae069145292 100644
--- a/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js
+++ b/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_last_pipeline_spec.js
@@ -21,10 +21,6 @@ describe('Pipeline schedule last pipeline', () => {
const findCIBadgeLink = () => wrapper.findComponent(CiBadgeLink);
const findStatusText = () => wrapper.findByTestId('pipeline-schedule-status-text');
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays pipeline status', () => {
createComponent();
diff --git a/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_next_run_spec.js b/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_next_run_spec.js
index 1c06c411097..3bdbb371ddc 100644
--- a/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_next_run_spec.js
+++ b/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_next_run_spec.js
@@ -21,10 +21,6 @@ describe('Pipeline schedule next run', () => {
const findTimeAgo = () => wrapper.findComponent(TimeAgoTooltip);
const findInactive = () => wrapper.findByTestId('pipeline-schedule-inactive');
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays time ago', () => {
createComponent();
diff --git a/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_owner_spec.js b/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_owner_spec.js
index 6c1991cb4ac..849bef80f42 100644
--- a/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_owner_spec.js
+++ b/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_owner_spec.js
@@ -25,10 +25,6 @@ describe('Pipeline schedule owner', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays avatar', () => {
expect(findAvatar().exists()).toBe(true);
expect(findAvatar().props('src')).toBe(defaultProps.schedule.owner.avatarUrl);
diff --git a/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_target_spec.js b/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_target_spec.js
index f531f04a736..5cc3829efbd 100644
--- a/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_target_spec.js
+++ b/spec/frontend/ci/pipeline_schedules/components/table/cells/pipeline_schedule_target_spec.js
@@ -25,10 +25,6 @@ describe('Pipeline schedule target', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays icon', () => {
expect(findIcon().exists()).toBe(true);
expect(findIcon().props('name')).toBe('fork');
diff --git a/spec/frontend/ci/pipeline_schedules/components/table/pipeline_schedules_table_spec.js b/spec/frontend/ci/pipeline_schedules/components/table/pipeline_schedules_table_spec.js
index 316b3bcf926..e488a36f3dc 100644
--- a/spec/frontend/ci/pipeline_schedules/components/table/pipeline_schedules_table_spec.js
+++ b/spec/frontend/ci/pipeline_schedules/components/table/pipeline_schedules_table_spec.js
@@ -1,13 +1,14 @@
import { GlTableLite } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import PipelineSchedulesTable from '~/ci/pipeline_schedules/components/table/pipeline_schedules_table.vue';
-import { mockPipelineScheduleNodes } from '../../mock_data';
+import { mockPipelineScheduleNodes, mockPipelineScheduleCurrentUser } from '../../mock_data';
describe('Pipeline schedules table', () => {
let wrapper;
const defaultProps = {
schedules: mockPipelineScheduleNodes,
+ currentUser: mockPipelineScheduleCurrentUser,
};
const createComponent = (props = defaultProps) => {
@@ -25,10 +26,6 @@ describe('Pipeline schedules table', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays table', () => {
expect(findTable().exists()).toBe(true);
});
diff --git a/spec/frontend/ci/pipeline_schedules/components/take_ownership_modal_legacy_spec.js b/spec/frontend/ci/pipeline_schedules/components/take_ownership_modal_legacy_spec.js
index 7e6d4ec4bf8..e4ff9a0545b 100644
--- a/spec/frontend/ci/pipeline_schedules/components/take_ownership_modal_legacy_spec.js
+++ b/spec/frontend/ci/pipeline_schedules/components/take_ownership_modal_legacy_spec.js
@@ -25,14 +25,12 @@ describe('Take ownership modal', () => {
const actionPrimary = findModal().props('actionPrimary');
expect(actionPrimary.attributes).toEqual(
- expect.objectContaining([
- {
- category: 'primary',
- variant: 'confirm',
- href: url,
- 'data-method': 'post',
- },
- ]),
+ expect.objectContaining({
+ category: 'primary',
+ variant: 'confirm',
+ href: url,
+ 'data-method': 'post',
+ }),
);
});
diff --git a/spec/frontend/ci/pipeline_schedules/mock_data.js b/spec/frontend/ci/pipeline_schedules/mock_data.js
index 2826c054249..1485f6beea4 100644
--- a/spec/frontend/ci/pipeline_schedules/mock_data.js
+++ b/spec/frontend/ci/pipeline_schedules/mock_data.js
@@ -5,6 +5,7 @@ import mockGetPipelineSchedulesTakeOwnershipGraphQLResponse from 'test_fixtures/
const {
data: {
+ currentUser,
project: {
pipelineSchedules: { nodes },
},
@@ -28,6 +29,7 @@ const {
} = mockGetPipelineSchedulesTakeOwnershipGraphQLResponse;
export const mockPipelineScheduleNodes = nodes;
+export const mockPipelineScheduleCurrentUser = currentUser;
export const mockPipelineScheduleAsGuestNodes = guestNodes;
diff --git a/spec/frontend/ci/reports/codequality_report/components/codequality_issue_body_spec.js b/spec/frontend/ci/reports/codequality_report/components/codequality_issue_body_spec.js
index 90ca2a07266..f7386cfec74 100644
--- a/spec/frontend/ci/reports/codequality_report/components/codequality_issue_body_spec.js
+++ b/spec/frontend/ci/reports/codequality_report/components/codequality_issue_body_spec.js
@@ -30,11 +30,6 @@ describe('code quality issue body issue body', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('severity rating', () => {
it.each`
severity | iconClass | iconName
diff --git a/spec/frontend/ci/reports/components/grouped_issues_list_spec.js b/spec/frontend/ci/reports/components/grouped_issues_list_spec.js
index 3e4adfc7794..8beec220802 100644
--- a/spec/frontend/ci/reports/components/grouped_issues_list_spec.js
+++ b/spec/frontend/ci/reports/components/grouped_issues_list_spec.js
@@ -15,10 +15,6 @@ describe('Grouped Issues List', () => {
const findHeading = (groupName) => wrapper.find(`[data-testid="${groupName}Heading"`);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a smart virtual list with the correct props', () => {
createComponent({
propsData: {
diff --git a/spec/frontend/ci/reports/components/issue_status_icon_spec.js b/spec/frontend/ci/reports/components/issue_status_icon_spec.js
index fb13d4407e2..82b655dd598 100644
--- a/spec/frontend/ci/reports/components/issue_status_icon_spec.js
+++ b/spec/frontend/ci/reports/components/issue_status_icon_spec.js
@@ -13,11 +13,6 @@ describe('IssueStatusIcon', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it.each([STATUS_SUCCESS, STATUS_NEUTRAL, STATUS_FAILED])(
'renders "%s" state correctly',
(status) => {
diff --git a/spec/frontend/ci/reports/components/report_link_spec.js b/spec/frontend/ci/reports/components/report_link_spec.js
index ba541ba0303..4a97afd77df 100644
--- a/spec/frontend/ci/reports/components/report_link_spec.js
+++ b/spec/frontend/ci/reports/components/report_link_spec.js
@@ -4,10 +4,6 @@ import ReportLink from '~/ci/reports/components/report_link.vue';
describe('app/assets/javascripts/ci/reports/components/report_link.vue', () => {
let wrapper;
- afterEach(() => {
- wrapper.destroy();
- });
-
const defaultProps = {
issue: {},
};
diff --git a/spec/frontend/ci/reports/components/report_section_spec.js b/spec/frontend/ci/reports/components/report_section_spec.js
index f032b210184..f4012fe0215 100644
--- a/spec/frontend/ci/reports/components/report_section_spec.js
+++ b/spec/frontend/ci/reports/components/report_section_spec.js
@@ -49,10 +49,6 @@ describe('ReportSection component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('computed', () => {
describe('isCollapsible', () => {
const testMatrix = [
diff --git a/spec/frontend/ci/reports/components/summary_row_spec.js b/spec/frontend/ci/reports/components/summary_row_spec.js
index fb2ae5371d5..b1ae9e26b5b 100644
--- a/spec/frontend/ci/reports/components/summary_row_spec.js
+++ b/spec/frontend/ci/reports/components/summary_row_spec.js
@@ -31,11 +31,6 @@ describe('Summary row', () => {
const findStatusIcon = () => wrapper.findByTestId('summary-row-icon');
const findHelpPopover = () => wrapper.findComponent(HelpPopover);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders provided summary', () => {
createComponent();
expect(findSummary().text()).toContain(summary);
diff --git a/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js b/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js
index edf3d1706cc..85b1d3b1b2f 100644
--- a/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js
+++ b/spec/frontend/ci/runner/admin_new_runner_app/admin_new_runner_app_spec.js
@@ -1,40 +1,53 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
import AdminNewRunnerApp from '~/ci/runner/admin_new_runner/admin_new_runner_app.vue';
+import { saveAlertToLocalStorage } from '~/ci/runner/local_storage_alert/save_alert_to_local_storage';
import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue';
import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue';
-import RunnerFormFields from '~/ci/runner/components/runner_form_fields.vue';
-import { DEFAULT_PLATFORM } from '~/ci/runner/constants';
+import { PARAM_KEY_PLATFORM, DEFAULT_PLATFORM, WINDOWS_PLATFORM } from '~/ci/runner/constants';
+import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue';
+import { redirectTo } from '~/lib/utils/url_utility';
+import { runnerCreateResult } from '../mock_data';
const mockLegacyRegistrationToken = 'LEGACY_REGISTRATION_TOKEN';
Vue.use(VueApollo);
+jest.mock('~/ci/runner/local_storage_alert/save_alert_to_local_storage');
+jest.mock('~/alert');
+jest.mock('~/lib/utils/url_utility', () => ({
+ ...jest.requireActual('~/lib/utils/url_utility'),
+ redirectTo: jest.fn(),
+}));
+
+const mockCreatedRunner = runnerCreateResult.data.runnerCreate.runner;
+
describe('AdminNewRunnerApp', () => {
let wrapper;
const findLegacyInstructionsLink = () => wrapper.findByTestId('legacy-instructions-link');
const findRunnerInstructionsModal = () => wrapper.findComponent(RunnerInstructionsModal);
const findRunnerPlatformsRadioGroup = () => wrapper.findComponent(RunnerPlatformsRadioGroup);
- const findRunnerFormFields = () => wrapper.findComponent(RunnerFormFields);
+ const findRunnerCreateForm = () => wrapper.findComponent(RunnerCreateForm);
- const createComponent = ({ props = {}, mountFn = shallowMountExtended, ...options } = {}) => {
- wrapper = mountFn(AdminNewRunnerApp, {
+ const createComponent = () => {
+ wrapper = shallowMountExtended(AdminNewRunnerApp, {
propsData: {
legacyRegistrationToken: mockLegacyRegistrationToken,
- ...props,
},
directives: {
- GlModal: createMockDirective(),
+ GlModal: createMockDirective('gl-modal'),
},
stubs: {
GlSprintf,
},
- ...options,
});
};
@@ -56,25 +69,59 @@ describe('AdminNewRunnerApp', () => {
});
});
- describe('New runner form fields', () => {
- describe('Platform', () => {
- it('shows the platforms radio group', () => {
- expect(findRunnerPlatformsRadioGroup().props('value')).toBe(DEFAULT_PLATFORM);
- });
+ describe('Platform', () => {
+ it('shows the platforms radio group', () => {
+ expect(findRunnerPlatformsRadioGroup().props('value')).toBe(DEFAULT_PLATFORM);
+ });
+ });
+
+ describe('Runner form', () => {
+ it('shows the runner create form', () => {
+ expect(findRunnerCreateForm().exists()).toBe(true);
});
- describe('Runner', () => {
- it('shows the runners fields', () => {
- expect(findRunnerFormFields().props('value')).toEqual({
- accessLevel: 'NOT_PROTECTED',
- paused: false,
- description: '',
- maintenanceNote: '',
- maximumTimeout: ' ',
- runUntagged: false,
- tagList: '',
+ describe('When a runner is saved', () => {
+ beforeEach(() => {
+ findRunnerCreateForm().vm.$emit('saved', mockCreatedRunner);
+ });
+
+ it('pushes an alert to be shown after redirection', () => {
+ expect(saveAlertToLocalStorage).toHaveBeenCalledWith({
+ message: s__('Runners|Runner created.'),
+ variant: VARIANT_SUCCESS,
});
});
+
+ it('redirects to the registration page', () => {
+ const url = `${mockCreatedRunner.registerAdminUrl}?${PARAM_KEY_PLATFORM}=${DEFAULT_PLATFORM}`;
+
+ expect(redirectTo).toHaveBeenCalledWith(url);
+ });
+ });
+
+ describe('When another platform is selected and a runner is saved', () => {
+ beforeEach(() => {
+ findRunnerPlatformsRadioGroup().vm.$emit('input', WINDOWS_PLATFORM);
+ findRunnerCreateForm().vm.$emit('saved', mockCreatedRunner);
+ });
+
+ it('redirects to the registration page with the platform', () => {
+ const url = `${mockCreatedRunner.registerAdminUrl}?${PARAM_KEY_PLATFORM}=${WINDOWS_PLATFORM}`;
+
+ expect(redirectTo).toHaveBeenCalledWith(url);
+ });
+ });
+
+ describe('When runner fails to save', () => {
+ const ERROR_MSG = 'Cannot save!';
+
+ beforeEach(() => {
+ findRunnerCreateForm().vm.$emit('error', new Error(ERROR_MSG));
+ });
+
+ it('shows an error message', () => {
+ expect(createAlert).toHaveBeenCalledWith({ message: ERROR_MSG });
+ });
});
});
});
diff --git a/spec/frontend/ci/runner/admin_register_runner/admin_register_runner_app_spec.js b/spec/frontend/ci/runner/admin_register_runner/admin_register_runner_app_spec.js
new file mode 100644
index 00000000000..d04df85d58f
--- /dev/null
+++ b/spec/frontend/ci/runner/admin_register_runner/admin_register_runner_app_spec.js
@@ -0,0 +1,122 @@
+import { nextTick } from 'vue';
+import { GlButton } from '@gitlab/ui';
+
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import setWindowLocation from 'helpers/set_window_location_helper';
+import { TEST_HOST } from 'helpers/test_constants';
+
+import { updateHistory } from '~/lib/utils/url_utility';
+import { PARAM_KEY_PLATFORM, DEFAULT_PLATFORM, WINDOWS_PLATFORM } from '~/ci/runner/constants';
+import AdminRegisterRunnerApp from '~/ci/runner/admin_register_runner/admin_register_runner_app.vue';
+import RegistrationInstructions from '~/ci/runner/components/registration/registration_instructions.vue';
+import PlatformsDrawer from '~/ci/runner/components/registration/platforms_drawer.vue';
+import { runnerForRegistration } from '../mock_data';
+
+const mockRunnerId = runnerForRegistration.data.runner.id;
+const mockRunnersPath = '/admin/runners';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ ...jest.requireActual('~/lib/utils/url_utility'),
+ updateHistory: jest.fn(),
+}));
+
+describe('AdminRegisterRunnerApp', () => {
+ let wrapper;
+
+ const findRegistrationInstructions = () => wrapper.findComponent(RegistrationInstructions);
+ const findPlatformsDrawer = () => wrapper.findComponent(PlatformsDrawer);
+ const findBtn = () => wrapper.findComponent(GlButton);
+
+ const createComponent = () => {
+ wrapper = shallowMountExtended(AdminRegisterRunnerApp, {
+ propsData: {
+ runnerId: mockRunnerId,
+ runnersPath: mockRunnersPath,
+ },
+ });
+ };
+
+ describe('When showing runner details', () => {
+ beforeEach(async () => {
+ createComponent();
+ });
+
+ describe('when runner token is available', () => {
+ it('shows registration instructions', () => {
+ expect(findRegistrationInstructions().props()).toEqual({
+ platform: DEFAULT_PLATFORM,
+ runnerId: mockRunnerId,
+ });
+ });
+
+ it('configures platform drawer', () => {
+ expect(findPlatformsDrawer().props()).toEqual({
+ open: false,
+ platform: DEFAULT_PLATFORM,
+ });
+ });
+
+ it('shows runner list button', () => {
+ expect(findBtn().attributes('href')).toBe(mockRunnersPath);
+ expect(findBtn().props('variant')).toBe('confirm');
+ });
+ });
+ });
+
+ describe('When another platform has been selected', () => {
+ beforeEach(async () => {
+ setWindowLocation(`?${PARAM_KEY_PLATFORM}=${WINDOWS_PLATFORM}`);
+
+ createComponent();
+ });
+
+ it('shows registration instructions for the platform', () => {
+ expect(findRegistrationInstructions().props('platform')).toBe(WINDOWS_PLATFORM);
+ });
+ });
+
+ describe('When opening install instructions', () => {
+ beforeEach(async () => {
+ createComponent();
+
+ findRegistrationInstructions().vm.$emit('toggleDrawer');
+ await nextTick();
+ });
+
+ it('opens platform drawer', () => {
+ expect(findPlatformsDrawer().props('open')).toBe(true);
+ });
+
+ it('closes platform drawer', async () => {
+ findRegistrationInstructions().vm.$emit('toggleDrawer');
+ await nextTick();
+
+ expect(findPlatformsDrawer().props('open')).toBe(false);
+ });
+
+ it('closes platform drawer from drawer', async () => {
+ findPlatformsDrawer().vm.$emit('close');
+ await nextTick();
+
+ expect(findPlatformsDrawer().props('open')).toBe(false);
+ });
+
+ describe('when selecting a platform', () => {
+ beforeEach(async () => {
+ findPlatformsDrawer().vm.$emit('selectPlatform', WINDOWS_PLATFORM);
+ await nextTick();
+ });
+
+ it('updates the url', () => {
+ expect(updateHistory).toHaveBeenCalledTimes(1);
+ expect(updateHistory).toHaveBeenCalledWith({
+ url: `${TEST_HOST}/?${PARAM_KEY_PLATFORM}=${WINDOWS_PLATFORM}`,
+ });
+ });
+
+ it('updates the registration instructions', () => {
+ expect(findRegistrationInstructions().props('platform')).toBe(WINDOWS_PLATFORM);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ci/runner/admin_runner_show/admin_runner_show_app_spec.js b/spec/frontend/ci/runner/admin_runner_show/admin_runner_show_app_spec.js
index ed4f43c12d8..9d9142f2c68 100644
--- a/spec/frontend/ci/runner/admin_runner_show/admin_runner_show_app_spec.js
+++ b/spec/frontend/ci/runner/admin_runner_show/admin_runner_show_app_spec.js
@@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert, VARIANT_SUCCESS } from '~/flash';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { redirectTo } from '~/lib/utils/url_utility';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
@@ -24,7 +24,7 @@ import { saveAlertToLocalStorage } from '~/ci/runner/local_storage_alert/save_al
import { runnerData } from '../mock_data';
jest.mock('~/ci/runner/local_storage_alert/save_alert_to_local_storage');
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
jest.mock('~/lib/utils/url_utility');
@@ -72,7 +72,6 @@ describe('AdminRunnerShowApp', () => {
afterEach(() => {
mockRunnerQuery.mockReset();
- wrapper.destroy();
});
describe('When showing runner details', () => {
diff --git a/spec/frontend/ci/runner/admin_runners/admin_runners_app_spec.js b/spec/frontend/ci/runner/admin_runners/admin_runners_app_spec.js
index 7fc240e520b..0cf6241c24f 100644
--- a/spec/frontend/ci/runner/admin_runners/admin_runners_app_spec.js
+++ b/spec/frontend/ci/runner/admin_runners/admin_runners_app_spec.js
@@ -9,7 +9,7 @@ import {
mountExtended,
} from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { s__ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { updateHistory } from '~/lib/utils/url_utility';
@@ -70,7 +70,7 @@ const mockRunnersCount = runnersCountData.data.runners.count;
const mockRunnersHandler = jest.fn();
const mockRunnersCountHandler = jest.fn();
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
@@ -143,7 +143,6 @@ describe('AdminRunnersApp', () => {
mockRunnersHandler.mockReset();
mockRunnersCountHandler.mockReset();
showToast.mockReset();
- wrapper.destroy();
});
it('shows the runner setup instructions', () => {
diff --git a/spec/frontend/ci/runner/components/cells/runner_actions_cell_spec.js b/spec/frontend/ci/runner/components/cells/runner_actions_cell_spec.js
index 82e262d1b73..8ac0c5a61f8 100644
--- a/spec/frontend/ci/runner/components/cells/runner_actions_cell_spec.js
+++ b/spec/frontend/ci/runner/components/cells/runner_actions_cell_spec.js
@@ -31,10 +31,6 @@ describe('RunnerActionsCell', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Edit Action', () => {
it('Displays the runner edit link with the correct href', () => {
createComponent();
diff --git a/spec/frontend/ci/runner/components/cells/runner_owner_cell_spec.js b/spec/frontend/ci/runner/components/cells/runner_owner_cell_spec.js
index 3097e43e583..03f1ace3897 100644
--- a/spec/frontend/ci/runner/components/cells/runner_owner_cell_spec.js
+++ b/spec/frontend/ci/runner/components/cells/runner_owner_cell_spec.js
@@ -16,7 +16,7 @@ describe('RunnerOwnerCell', () => {
const createComponent = ({ runner } = {}) => {
wrapper = shallowMount(RunnerOwnerCell, {
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
propsData: {
runner,
@@ -24,10 +24,6 @@ describe('RunnerOwnerCell', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('When its an instance runner', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/ci/runner/components/cells/runner_status_cell_spec.js b/spec/frontend/ci/runner/components/cells/runner_status_cell_spec.js
index 1ff60ff1a9d..ec23d8415e8 100644
--- a/spec/frontend/ci/runner/components/cells/runner_status_cell_spec.js
+++ b/spec/frontend/ci/runner/components/cells/runner_status_cell_spec.js
@@ -34,10 +34,6 @@ describe('RunnerStatusCell', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Displays online status', () => {
createComponent();
diff --git a/spec/frontend/ci/runner/components/cells/runner_summary_cell_spec.js b/spec/frontend/ci/runner/components/cells/runner_summary_cell_spec.js
index 1711df42491..585a03c0811 100644
--- a/spec/frontend/ci/runner/components/cells/runner_summary_cell_spec.js
+++ b/spec/frontend/ci/runner/components/cells/runner_summary_cell_spec.js
@@ -45,10 +45,6 @@ describe('RunnerTypeCell', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Displays the runner name as id and short token', () => {
expect(wrapper.text()).toContain(
`#${getIdFromGraphQLId(mockRunner.id)} (${mockRunner.shortSha})`,
diff --git a/spec/frontend/ci/runner/components/cells/runner_summary_field_spec.js b/spec/frontend/ci/runner/components/cells/runner_summary_field_spec.js
index f536e0dcbcf..7748890cf77 100644
--- a/spec/frontend/ci/runner/components/cells/runner_summary_field_spec.js
+++ b/spec/frontend/ci/runner/components/cells/runner_summary_field_spec.js
@@ -17,16 +17,12 @@ describe('RunnerSummaryField', () => {
...props,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
...options,
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('shows content in slot', () => {
createComponent({
slots: { default: 'content' },
diff --git a/spec/frontend/ci/runner/components/registration/__snapshots__/utils_spec.js.snap b/spec/frontend/ci/runner/components/registration/__snapshots__/utils_spec.js.snap
new file mode 100644
index 00000000000..09d032fd32d
--- /dev/null
+++ b/spec/frontend/ci/runner/components/registration/__snapshots__/utils_spec.js.snap
@@ -0,0 +1,204 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`registration utils for "linux" platform commandPrompt is correct 1`] = `"$"`;
+
+exports[`registration utils for "linux" platform installScript is correct for "386" architecture 1`] = `
+"# Download the binary for your system
+sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-386
+
+# Give it permission to execute
+sudo chmod +x /usr/local/bin/gitlab-runner
+
+# Create a GitLab Runner user
+sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
+
+# Install and run as a service
+sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
+sudo gitlab-runner start"
+`;
+
+exports[`registration utils for "linux" platform installScript is correct for "amd64" architecture 1`] = `
+"# Download the binary for your system
+sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
+
+# Give it permission to execute
+sudo chmod +x /usr/local/bin/gitlab-runner
+
+# Create a GitLab Runner user
+sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
+
+# Install and run as a service
+sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
+sudo gitlab-runner start"
+`;
+
+exports[`registration utils for "linux" platform installScript is correct for "arm" architecture 1`] = `
+"# Download the binary for your system
+sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm
+
+# Give it permission to execute
+sudo chmod +x /usr/local/bin/gitlab-runner
+
+# Create a GitLab Runner user
+sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
+
+# Install and run as a service
+sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
+sudo gitlab-runner start"
+`;
+
+exports[`registration utils for "linux" platform installScript is correct for "arm64" architecture 1`] = `
+"# Download the binary for your system
+sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm64
+
+# Give it permission to execute
+sudo chmod +x /usr/local/bin/gitlab-runner
+
+# Create a GitLab Runner user
+sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
+
+# Install and run as a service
+sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
+sudo gitlab-runner start"
+`;
+
+exports[`registration utils for "linux" platform platformArchitectures returns correct list of architectures 1`] = `
+Array [
+ "amd64",
+ "386",
+ "arm",
+ "arm64",
+]
+`;
+
+exports[`registration utils for "linux" platform registerCommand is correct 1`] = `
+Array [
+ "gitlab-runner register",
+ " --url http://test.host",
+ " --registration-token REGISTRATION_TOKEN",
+ " --description 'RUNNER'",
+]
+`;
+
+exports[`registration utils for "linux" platform registerCommand is correct 2`] = `
+Array [
+ "gitlab-runner register",
+ " --url http://test.host",
+]
+`;
+
+exports[`registration utils for "linux" platform runCommand is correct 1`] = `"gitlab-runner run"`;
+
+exports[`registration utils for "osx" platform commandPrompt is correct 1`] = `"$"`;
+
+exports[`registration utils for "osx" platform installScript is correct for "amd64" architecture 1`] = `
+"# Download the binary for your system
+sudo curl --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64
+
+# Give it permission to execute
+sudo chmod +x /usr/local/bin/gitlab-runner
+
+# The rest of the commands execute as the user who will run the runner
+# Register the runner (steps below), then run
+cd ~
+gitlab-runner install
+gitlab-runner start"
+`;
+
+exports[`registration utils for "osx" platform installScript is correct for "arm64" architecture 1`] = `
+"# Download the binary for your system
+sudo curl --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-arm64
+
+# Give it permission to execute
+sudo chmod +x /usr/local/bin/gitlab-runner
+
+# The rest of the commands execute as the user who will run the runner
+# Register the runner (steps below), then run
+cd ~
+gitlab-runner install
+gitlab-runner start"
+`;
+
+exports[`registration utils for "osx" platform platformArchitectures returns correct list of architectures 1`] = `
+Array [
+ "amd64",
+ "arm64",
+]
+`;
+
+exports[`registration utils for "osx" platform registerCommand is correct 1`] = `
+Array [
+ "gitlab-runner register",
+ " --url http://test.host",
+ " --registration-token REGISTRATION_TOKEN",
+ " --description 'RUNNER'",
+]
+`;
+
+exports[`registration utils for "osx" platform registerCommand is correct 2`] = `
+Array [
+ "gitlab-runner register",
+ " --url http://test.host",
+]
+`;
+
+exports[`registration utils for "osx" platform runCommand is correct 1`] = `"gitlab-runner run"`;
+
+exports[`registration utils for "windows" platform commandPrompt is correct 1`] = `">"`;
+
+exports[`registration utils for "windows" platform installScript is correct for "386" architecture 1`] = `
+"# Run PowerShell: https://docs.microsoft.com/en-us/powershell/scripting/windows-powershell/starting-windows-powershell?view=powershell-7#with-administrative-privileges-run-as-administrator
+# Create a folder somewhere on your system, for example: C:\\\\GitLab-Runner
+New-Item -Path 'C:\\\\GitLab-Runner' -ItemType Directory
+
+# Change to the folder
+cd 'C:\\\\GitLab-Runner'
+
+# Download binary
+Invoke-WebRequest -Uri \\"https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-386.exe\\" -OutFile \\"gitlab-runner.exe\\"
+
+# Register the runner (steps below), then run
+.\\\\gitlab-runner.exe install
+.\\\\gitlab-runner.exe start"
+`;
+
+exports[`registration utils for "windows" platform installScript is correct for "amd64" architecture 1`] = `
+"# Run PowerShell: https://docs.microsoft.com/en-us/powershell/scripting/windows-powershell/starting-windows-powershell?view=powershell-7#with-administrative-privileges-run-as-administrator
+# Create a folder somewhere on your system, for example: C:\\\\GitLab-Runner
+New-Item -Path 'C:\\\\GitLab-Runner' -ItemType Directory
+
+# Change to the folder
+cd 'C:\\\\GitLab-Runner'
+
+# Download binary
+Invoke-WebRequest -Uri \\"https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe\\" -OutFile \\"gitlab-runner.exe\\"
+
+# Register the runner (steps below), then run
+.\\\\gitlab-runner.exe install
+.\\\\gitlab-runner.exe start"
+`;
+
+exports[`registration utils for "windows" platform platformArchitectures returns correct list of architectures 1`] = `
+Array [
+ "amd64",
+ "386",
+]
+`;
+
+exports[`registration utils for "windows" platform registerCommand is correct 1`] = `
+Array [
+ ".\\\\gitlab-runner.exe register",
+ " --url http://test.host",
+ " --registration-token REGISTRATION_TOKEN",
+ " --description 'RUNNER'",
+]
+`;
+
+exports[`registration utils for "windows" platform registerCommand is correct 2`] = `
+Array [
+ ".\\\\gitlab-runner.exe register",
+ " --url http://test.host",
+]
+`;
+
+exports[`registration utils for "windows" platform runCommand is correct 1`] = `".\\\\gitlab-runner.exe run"`;
diff --git a/spec/frontend/ci/runner/components/registration/cli_command_spec.js b/spec/frontend/ci/runner/components/registration/cli_command_spec.js
new file mode 100644
index 00000000000..78c2b94c3ea
--- /dev/null
+++ b/spec/frontend/ci/runner/components/registration/cli_command_spec.js
@@ -0,0 +1,39 @@
+import CliCommand from '~/ci/runner/components/registration/cli_command.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+
+describe('CliCommand', () => {
+ let wrapper;
+
+ // use .textContent instead of .text() to capture whitespace that's visible in <pre>
+ const getPreTextContent = () => wrapper.find('pre').element.textContent;
+ const getClipboardText = () => wrapper.findComponent(ClipboardButton).props('text');
+
+ const createComponent = (props) => {
+ wrapper = shallowMountExtended(CliCommand, {
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ it('when rendering a command', () => {
+ createComponent({
+ prompt: '#',
+ command: 'echo hi',
+ });
+
+ expect(getPreTextContent()).toBe('# echo hi');
+ expect(getClipboardText()).toBe('echo hi');
+ });
+
+ it('when rendering a multi-line command', () => {
+ createComponent({
+ prompt: '#',
+ command: ['git', ' --version'],
+ });
+
+ expect(getPreTextContent()).toBe('# git --version');
+ expect(getClipboardText()).toBe('git --version');
+ });
+});
diff --git a/spec/frontend/ci/runner/components/registration/platforms_drawer_spec.js b/spec/frontend/ci/runner/components/registration/platforms_drawer_spec.js
new file mode 100644
index 00000000000..0b438455b5b
--- /dev/null
+++ b/spec/frontend/ci/runner/components/registration/platforms_drawer_spec.js
@@ -0,0 +1,108 @@
+import { nextTick } from 'vue';
+import { GlDrawer, GlLink, GlIcon, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
+
+import PlatformsDrawer from '~/ci/runner/components/registration/platforms_drawer.vue';
+import CliCommand from '~/ci/runner/components/registration/cli_command.vue';
+import {
+ LINUX_PLATFORM,
+ MACOS_PLATFORM,
+ WINDOWS_PLATFORM,
+ INSTALL_HELP_URL,
+} from '~/ci/runner/constants';
+import { installScript, platformArchitectures } from '~/ci/runner/components/registration/utils';
+
+const MOCK_WRAPPER_HEIGHT = '99px';
+const LINUX_ARCHS = platformArchitectures({ platform: LINUX_PLATFORM });
+const MACOS_ARCHS = platformArchitectures({ platform: MACOS_PLATFORM });
+
+jest.mock('~/lib/utils/dom_utils', () => ({
+ getContentWrapperHeight: () => MOCK_WRAPPER_HEIGHT,
+}));
+
+describe('RegistrationInstructions', () => {
+ let wrapper;
+
+ const findDrawer = () => wrapper.findComponent(GlDrawer);
+ const findEnvironmentOptions = () =>
+ wrapper.findByLabelText(s__('Runners|Environment')).findAll('option');
+ const findArchitectureOptions = () =>
+ wrapper.findByLabelText(s__('Runners|Architecture')).findAll('option');
+ const findCliCommand = () => wrapper.findComponent(CliCommand);
+ const findLink = () => wrapper.findComponent(GlLink);
+
+ const createComponent = ({ props = {}, mountFn = shallowMountExtended } = {}) => {
+ wrapper = mountFn(PlatformsDrawer, {
+ propsData: {
+ open: true,
+ ...props,
+ },
+ stubs: {
+ GlSprintf,
+ },
+ });
+ };
+
+ it('shows drawer', () => {
+ createComponent();
+
+ expect(findDrawer().props()).toMatchObject({
+ open: true,
+ headerHeight: MOCK_WRAPPER_HEIGHT,
+ });
+ });
+
+ it('closes drawer', () => {
+ createComponent();
+ findDrawer().vm.$emit('close');
+
+ expect(wrapper.emitted('close')).toHaveLength(1);
+ });
+
+ it('shows selection options', () => {
+ createComponent({ mountFn: mountExtended });
+
+ expect(findEnvironmentOptions().wrappers.map((w) => w.attributes('value'))).toEqual([
+ LINUX_PLATFORM,
+ MACOS_PLATFORM,
+ WINDOWS_PLATFORM,
+ ]);
+
+ expect(findArchitectureOptions().wrappers.map((w) => w.attributes('value'))).toEqual(
+ LINUX_ARCHS,
+ );
+ });
+
+ it('shows script', () => {
+ createComponent();
+
+ expect(findCliCommand().props('command')).toBe(
+ installScript({ platform: LINUX_PLATFORM, architecture: LINUX_ARCHS[0] }),
+ );
+ });
+
+ it('shows selection options for another platform', async () => {
+ createComponent({ mountFn: mountExtended });
+
+ findEnvironmentOptions().at(1).setSelected(); // macos
+ await nextTick();
+
+ expect(wrapper.emitted('selectPlatform')).toEqual([[MACOS_PLATFORM]]);
+
+ expect(findArchitectureOptions().wrappers.map((w) => w.attributes('value'))).toEqual(
+ MACOS_ARCHS,
+ );
+
+ expect(findCliCommand().props('command')).toBe(
+ installScript({ platform: MACOS_PLATFORM, architecture: MACOS_ARCHS[0] }),
+ );
+ });
+
+ it('shows external link for more information', () => {
+ createComponent();
+
+ expect(findLink().attributes('href')).toBe(INSTALL_HELP_URL);
+ expect(findLink().findComponent(GlIcon).props('name')).toBe('external-link');
+ });
+});
diff --git a/spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js b/spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js
index 0daaca9c4ff..9ed59b0a57d 100644
--- a/spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js
+++ b/spec/frontend/ci/runner/components/registration/registration_dropdown_spec.js
@@ -116,10 +116,6 @@ describe('RegistrationDropdown', () => {
await openModal();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('opens the modal with contents', () => {
const modalText = findModalContent();
diff --git a/spec/frontend/ci/runner/components/registration/registration_instructions_spec.js b/spec/frontend/ci/runner/components/registration/registration_instructions_spec.js
new file mode 100644
index 00000000000..eb4b659091d
--- /dev/null
+++ b/spec/frontend/ci/runner/components/registration/registration_instructions_spec.js
@@ -0,0 +1,293 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlSprintf, GlSkeletonLoader } from '@gitlab/ui';
+
+import { s__ } from '~/locale';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { extendedWrapper, shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { TEST_HOST } from 'helpers/test_constants';
+
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import RegistrationInstructions from '~/ci/runner/components/registration/registration_instructions.vue';
+import runnerForRegistrationQuery from '~/ci/runner/graphql/register/runner_for_registration.query.graphql';
+import CliCommand from '~/ci/runner/components/registration/cli_command.vue';
+import {
+ DEFAULT_PLATFORM,
+ EXECUTORS_HELP_URL,
+ SERVICE_COMMANDS_HELP_URL,
+ STATUS_NEVER_CONTACTED,
+ STATUS_ONLINE,
+ RUNNER_REGISTRATION_POLLING_INTERVAL_MS,
+ I18N_REGISTRATION_SUCCESS,
+} from '~/ci/runner/constants';
+import { runnerForRegistration } from '../../mock_data';
+
+Vue.use(VueApollo);
+
+const MOCK_TOKEN = 'MOCK_TOKEN';
+const mockDescription = runnerForRegistration.data.runner.description;
+
+const mockRunner = {
+ ...runnerForRegistration.data.runner,
+ ephemeralAuthenticationToken: MOCK_TOKEN,
+};
+const mockRunnerWithoutToken = {
+ ...runnerForRegistration.data.runner,
+ ephemeralAuthenticationToken: null,
+};
+
+const mockRunnerId = `${getIdFromGraphQLId(mockRunner.id)}`;
+
+describe('RegistrationInstructions', () => {
+ let wrapper;
+ let mockRunnerQuery;
+
+ const findHeading = () => wrapper.find('h1');
+ const findStepAt = (i) => extendedWrapper(wrapper.findAll('section').at(i));
+ const findByText = (text, container = wrapper) => container.findByText(text);
+
+ const waitForPolling = async () => {
+ jest.advanceTimersByTime(RUNNER_REGISTRATION_POLLING_INTERVAL_MS);
+ await waitForPromises();
+ };
+
+ const mockResolvedRunner = (runner = mockRunner) => {
+ mockRunnerQuery.mockResolvedValue({
+ data: {
+ runner,
+ },
+ });
+ };
+
+ const createComponent = (props) => {
+ wrapper = shallowMountExtended(RegistrationInstructions, {
+ apolloProvider: createMockApollo([[runnerForRegistrationQuery, mockRunnerQuery]]),
+ propsData: {
+ runnerId: mockRunnerId,
+ platform: DEFAULT_PLATFORM,
+ ...props,
+ },
+ stubs: {
+ GlSprintf,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ mockRunnerQuery = jest.fn();
+ mockResolvedRunner();
+ });
+
+ beforeEach(() => {
+ window.gon.gitlab_url = TEST_HOST;
+ });
+
+ it('loads runner with id', async () => {
+ createComponent();
+
+ expect(mockRunnerQuery).toHaveBeenCalledWith({ id: mockRunner.id });
+ });
+
+ describe('heading', () => {
+ it('when runner is loaded, shows heading', async () => {
+ createComponent();
+ await waitForPromises();
+
+ expect(findHeading().text()).toContain(mockRunner.description);
+ });
+
+ it('when runner is loaded, shows heading safely', async () => {
+ mockResolvedRunner({
+ ...mockRunner,
+ description: '<script>hacked();</script>',
+ });
+
+ createComponent();
+ await waitForPromises();
+
+ expect(findHeading().text()).toBe('Register "<script>hacked();</script>" runner');
+ expect(findHeading().element.innerHTML).toBe(
+ 'Register "&lt;script&gt;hacked();&lt;/script&gt;" runner',
+ );
+ });
+
+ it('when runner is loading, shows default heading', () => {
+ createComponent();
+
+ expect(findHeading().text()).toBe(s__('Runners|Register runner'));
+ });
+ });
+
+ it('renders legacy instructions', () => {
+ createComponent();
+
+ findByText('How do I install GitLab Runner?').vm.$emit('click');
+
+ expect(wrapper.emitted('toggleDrawer')).toHaveLength(1);
+ });
+
+ describe('step 1', () => {
+ it('renders step 1', async () => {
+ createComponent();
+ await waitForPromises();
+
+ const step1 = findStepAt(0);
+
+ expect(step1.findComponent(CliCommand).props()).toEqual({
+ command: [
+ 'gitlab-runner register',
+ ` --url ${TEST_HOST}`,
+ ` --registration-token ${MOCK_TOKEN}`,
+ ` --description '${mockDescription}'`,
+ ],
+ prompt: '$',
+ });
+ expect(step1.find('[data-testid="runner-token"]').text()).toBe(MOCK_TOKEN);
+ expect(step1.findComponent(ClipboardButton).props('text')).toBe(MOCK_TOKEN);
+ });
+
+ it('renders step 1 in loading state', () => {
+ createComponent();
+
+ const step1 = findStepAt(0);
+
+ expect(step1.findComponent(GlSkeletonLoader).exists()).toBe(true);
+ expect(step1.find('code').exists()).toBe(false);
+ expect(step1.findComponent(ClipboardButton).exists()).toBe(false);
+ });
+
+ it('render step 1 after token is not visible', async () => {
+ mockResolvedRunner(mockRunnerWithoutToken);
+
+ createComponent();
+ await waitForPromises();
+
+ const step1 = findStepAt(0);
+
+ expect(step1.findComponent(CliCommand).props('command')).toEqual([
+ 'gitlab-runner register',
+ ` --url ${TEST_HOST}`,
+ ` --description '${mockDescription}'`,
+ ]);
+ expect(step1.find('[data-testid="runner-token"]').exists()).toBe(false);
+ expect(step1.findComponent(ClipboardButton).exists()).toBe(false);
+ });
+
+ describe('polling for changes', () => {
+ beforeEach(async () => {
+ createComponent();
+ await waitForPromises();
+ });
+
+ it('fetches data', () => {
+ expect(mockRunnerQuery).toHaveBeenCalledTimes(1);
+ });
+
+ it('polls', async () => {
+ await waitForPolling();
+ expect(mockRunnerQuery).toHaveBeenCalledTimes(2);
+
+ await waitForPolling();
+ expect(mockRunnerQuery).toHaveBeenCalledTimes(3);
+ });
+
+ it('when runner is online, stops polling', async () => {
+ mockResolvedRunner({ ...mockRunner, status: STATUS_ONLINE });
+ await waitForPolling();
+
+ expect(mockRunnerQuery).toHaveBeenCalledTimes(2);
+ await waitForPolling();
+
+ expect(mockRunnerQuery).toHaveBeenCalledTimes(2);
+ });
+
+ it('when token is no longer visible in the API, it is still visible in the UI', async () => {
+ mockResolvedRunner(mockRunnerWithoutToken);
+ await waitForPolling();
+
+ const step1 = findStepAt(0);
+ expect(step1.findComponent(CliCommand).props('command')).toEqual([
+ 'gitlab-runner register',
+ ` --url ${TEST_HOST}`,
+ ` --registration-token ${MOCK_TOKEN}`,
+ ` --description '${mockDescription}'`,
+ ]);
+ expect(step1.find('[data-testid="runner-token"]').text()).toBe(MOCK_TOKEN);
+ expect(step1.findComponent(ClipboardButton).props('text')).toBe(MOCK_TOKEN);
+ });
+
+ it('when runner is not available (e.g. deleted), the UI does not update', async () => {
+ mockResolvedRunner(null);
+ await waitForPolling();
+
+ const step1 = findStepAt(0);
+ expect(step1.findComponent(CliCommand).props('command')).toEqual([
+ 'gitlab-runner register',
+ ` --url ${TEST_HOST}`,
+ ` --registration-token ${MOCK_TOKEN}`,
+ ` --description '${mockDescription}'`,
+ ]);
+ expect(step1.find('[data-testid="runner-token"]').text()).toBe(MOCK_TOKEN);
+ expect(step1.findComponent(ClipboardButton).props('text')).toBe(MOCK_TOKEN);
+ });
+ });
+ });
+
+ it('renders step 2', () => {
+ createComponent();
+ const step2 = findStepAt(1);
+
+ expect(findByText('Not sure which one to select?', step2).attributes('href')).toBe(
+ EXECUTORS_HELP_URL,
+ );
+ });
+
+ it('renders step 3', () => {
+ createComponent();
+ const step3 = findStepAt(2);
+
+ expect(step3.findComponent(CliCommand).props()).toEqual({
+ command: 'gitlab-runner run',
+ prompt: '$',
+ });
+
+ expect(findByText('system or user service', step3).attributes('href')).toBe(
+ SERVICE_COMMANDS_HELP_URL,
+ );
+ });
+
+ describe('success state', () => {
+ describe('when the runner has not been registered', () => {
+ beforeEach(async () => {
+ createComponent();
+
+ await waitForPolling();
+
+ mockResolvedRunner({ ...mockRunner, status: STATUS_NEVER_CONTACTED });
+
+ await waitForPolling();
+ });
+
+ it('does not show success message', () => {
+ expect(wrapper.text()).not.toContain(I18N_REGISTRATION_SUCCESS);
+ });
+ });
+
+ describe('when the runner has been registered', () => {
+ beforeEach(async () => {
+ createComponent();
+ await waitForPolling();
+
+ mockResolvedRunner({ ...mockRunner, status: STATUS_ONLINE });
+ await waitForPolling();
+ });
+
+ it('shows success message', () => {
+ expect(wrapper.text()).toContain('🎉');
+ expect(wrapper.text()).toContain(I18N_REGISTRATION_SUCCESS);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ci/runner/components/registration/registration_token_reset_dropdown_item_spec.js b/spec/frontend/ci/runner/components/registration/registration_token_reset_dropdown_item_spec.js
index 783a4d9252a..ff69fd6d3d6 100644
--- a/spec/frontend/ci/runner/components/registration/registration_token_reset_dropdown_item_spec.js
+++ b/spec/frontend/ci/runner/components/registration/registration_token_reset_dropdown_item_spec.js
@@ -5,14 +5,14 @@ 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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import RegistrationTokenResetDropdownItem from '~/ci/runner/components/registration/registration_token_reset_dropdown_item.vue';
import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/ci/runner/constants';
import runnersRegistrationTokenResetMutation from '~/ci/runner/graphql/list/runners_registration_token_reset.mutation.graphql';
import { captureException } from '~/ci/runner/sentry_utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
Vue.use(VueApollo);
@@ -43,7 +43,7 @@ describe('RegistrationTokenResetDropdownItem', () => {
[runnersRegistrationTokenResetMutation, runnersRegistrationTokenResetMutationHandler],
]),
directives: {
- GlModal: createMockDirective(),
+ GlModal: createMockDirective('gl-modal'),
},
});
@@ -63,10 +63,6 @@ describe('RegistrationTokenResetDropdownItem', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Displays reset button', () => {
expect(findDropdownItem().exists()).toBe(true);
});
diff --git a/spec/frontend/ci/runner/components/registration/registration_token_spec.js b/spec/frontend/ci/runner/components/registration/registration_token_spec.js
index d2a51c0d910..4f44e6e10b2 100644
--- a/spec/frontend/ci/runner/components/registration/registration_token_spec.js
+++ b/spec/frontend/ci/runner/components/registration/registration_token_spec.js
@@ -27,10 +27,6 @@ describe('RegistrationToken', () => {
showToast = wrapper.vm.$toast ? jest.spyOn(wrapper.vm.$toast, 'show') : null;
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Displays value and copy button', () => {
createComponent();
diff --git a/spec/frontend/ci/runner/components/registration/utils_spec.js b/spec/frontend/ci/runner/components/registration/utils_spec.js
new file mode 100644
index 00000000000..acf5993b15b
--- /dev/null
+++ b/spec/frontend/ci/runner/components/registration/utils_spec.js
@@ -0,0 +1,118 @@
+import { TEST_HOST } from 'helpers/test_constants';
+import {
+ DEFAULT_PLATFORM,
+ LINUX_PLATFORM,
+ MACOS_PLATFORM,
+ WINDOWS_PLATFORM,
+} from '~/ci/runner/constants';
+
+import {
+ commandPrompt,
+ registerCommand,
+ runCommand,
+ installScript,
+ platformArchitectures,
+} from '~/ci/runner/components/registration/utils';
+
+const REGISTRATION_TOKEN = 'REGISTRATION_TOKEN';
+const DESCRIPTION = 'RUNNER';
+
+describe('registration utils', () => {
+ beforeEach(() => {
+ window.gon.gitlab_url = TEST_HOST;
+ });
+
+ describe.each([LINUX_PLATFORM, MACOS_PLATFORM, WINDOWS_PLATFORM])(
+ 'for "%s" platform',
+ (platform) => {
+ it('commandPrompt is correct', () => {
+ expect(commandPrompt({ platform })).toMatchSnapshot();
+ });
+
+ it('registerCommand is correct', () => {
+ expect(
+ registerCommand({
+ platform,
+ registrationToken: REGISTRATION_TOKEN,
+ description: DESCRIPTION,
+ }),
+ ).toMatchSnapshot();
+
+ expect(registerCommand({ platform })).toMatchSnapshot();
+ });
+
+ it('runCommand is correct', () => {
+ expect(runCommand({ platform })).toMatchSnapshot();
+ });
+ },
+ );
+
+ describe.each([LINUX_PLATFORM, MACOS_PLATFORM])('for "%s" platform', (platform) => {
+ it.each`
+ description | parameter
+ ${'my runner'} | ${"'my runner'"}
+ ${"bob's runner"} | ${"'bob'\\''s runner'"}
+ `('registerCommand escapes description `$description`', ({ description, parameter }) => {
+ expect(registerCommand({ platform, description })[2]).toBe(` --description ${parameter}`);
+ });
+ });
+
+ describe.each([WINDOWS_PLATFORM])('for "%s" platform', (platform) => {
+ it.each`
+ description | parameter
+ ${'my runner'} | ${"'my runner'"}
+ ${"bob's runner"} | ${"'bob''s runner'"}
+ `('registerCommand escapes description `$description`', ({ description, parameter }) => {
+ expect(registerCommand({ platform, description })[2]).toBe(` --description ${parameter}`);
+ });
+ });
+
+ describe('for missing platform', () => {
+ it('commandPrompt uses the default', () => {
+ const expected = commandPrompt({ platform: DEFAULT_PLATFORM });
+
+ expect(commandPrompt({ platform: null })).toEqual(expected);
+ expect(commandPrompt({ platform: undefined })).toEqual(expected);
+ });
+
+ it('registerCommand uses the default', () => {
+ const expected = registerCommand({
+ platform: DEFAULT_PLATFORM,
+ registrationToken: REGISTRATION_TOKEN,
+ });
+
+ expect(registerCommand({ platform: null, registrationToken: REGISTRATION_TOKEN })).toEqual(
+ expected,
+ );
+ expect(
+ registerCommand({ platform: undefined, registrationToken: REGISTRATION_TOKEN }),
+ ).toEqual(expected);
+ });
+
+ it('runCommand uses the default', () => {
+ const expected = runCommand({ platform: DEFAULT_PLATFORM });
+
+ expect(runCommand({ platform: null })).toEqual(expected);
+ expect(runCommand({ platform: undefined })).toEqual(expected);
+ });
+ });
+
+ describe.each([LINUX_PLATFORM, MACOS_PLATFORM, WINDOWS_PLATFORM])(
+ 'for "%s" platform',
+ (platform) => {
+ describe('platformArchitectures', () => {
+ it('returns correct list of architectures', () => {
+ expect(platformArchitectures({ platform })).toMatchSnapshot();
+ });
+ });
+
+ describe('installScript', () => {
+ const architectures = platformArchitectures({ platform });
+
+ it.each(architectures)('is correct for "%s" architecture', (architecture) => {
+ expect(installScript({ platform, architecture })).toMatchSnapshot();
+ });
+ });
+ },
+ );
+});
diff --git a/spec/frontend/ci/runner/components/runner_assigned_item_spec.js b/spec/frontend/ci/runner/components/runner_assigned_item_spec.js
index 5df2e04c340..a1fd9e4c1aa 100644
--- a/spec/frontend/ci/runner/components/runner_assigned_item_spec.js
+++ b/spec/frontend/ci/runner/components/runner_assigned_item_spec.js
@@ -33,10 +33,6 @@ describe('RunnerAssignedItem', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Shows an avatar', () => {
const avatar = findAvatar();
diff --git a/spec/frontend/ci/runner/components/runner_bulk_delete_spec.js b/spec/frontend/ci/runner/components/runner_bulk_delete_spec.js
index 0dc5a90fb83..f609c6be41a 100644
--- a/spec/frontend/ci/runner/components/runner_bulk_delete_spec.js
+++ b/spec/frontend/ci/runner/components/runner_bulk_delete_spec.js
@@ -2,7 +2,7 @@ import Vue from 'vue';
import { makeVar } from '@apollo/client/core';
import { GlModal, GlSprintf } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { s__ } from '~/locale';
@@ -15,7 +15,7 @@ import { allRunnersData } from '../mock_data';
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('RunnerBulkDelete', () => {
let wrapper;
@@ -51,7 +51,7 @@ describe('RunnerBulkDelete', () => {
runners: mockRunners,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
stubs: {
GlSprintf,
diff --git a/spec/frontend/ci/runner/components/runner_create_form_spec.js b/spec/frontend/ci/runner/components/runner_create_form_spec.js
new file mode 100644
index 00000000000..1123a026a4d
--- /dev/null
+++ b/spec/frontend/ci/runner/components/runner_create_form_spec.js
@@ -0,0 +1,170 @@
+import Vue from 'vue';
+import { GlForm } from '@gitlab/ui';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import RunnerCreateForm from '~/ci/runner/components/runner_create_form.vue';
+import RunnerFormFields from '~/ci/runner/components/runner_form_fields.vue';
+import { DEFAULT_ACCESS_LEVEL } from '~/ci/runner/constants';
+import runnerCreateMutation from '~/ci/runner/graphql/new/runner_create.mutation.graphql';
+import { captureException } from '~/ci/runner/sentry_utils';
+import { runnerCreateResult } from '../mock_data';
+
+jest.mock('~/ci/runner/sentry_utils');
+
+const mockCreatedRunner = runnerCreateResult.data.runnerCreate.runner;
+
+const defaultRunnerModel = {
+ description: '',
+ accessLevel: DEFAULT_ACCESS_LEVEL,
+ paused: false,
+ maintenanceNote: '',
+ maximumTimeout: '',
+ runUntagged: false,
+ tagList: '',
+};
+
+Vue.use(VueApollo);
+
+describe('RunnerCreateForm', () => {
+ let wrapper;
+ let runnerCreateHandler;
+
+ const findForm = () => wrapper.findComponent(GlForm);
+ const findRunnerFormFields = () => wrapper.findComponent(RunnerFormFields);
+ const findSubmitBtn = () => wrapper.find('[type="submit"]');
+
+ const createComponent = () => {
+ wrapper = shallowMountExtended(RunnerCreateForm, {
+ apolloProvider: createMockApollo([[runnerCreateMutation, runnerCreateHandler]]),
+ });
+ };
+
+ beforeEach(() => {
+ runnerCreateHandler = jest.fn().mockResolvedValue(runnerCreateResult);
+
+ createComponent();
+ });
+
+ it('shows default runner values', () => {
+ expect(findRunnerFormFields().props('value')).toEqual(defaultRunnerModel);
+ });
+
+ it('shows a submit button', () => {
+ expect(findSubmitBtn().exists()).toBe(true);
+ });
+
+ describe('when user submits', () => {
+ let preventDefault;
+
+ beforeEach(() => {
+ preventDefault = jest.fn();
+
+ findRunnerFormFields().vm.$emit('input', {
+ ...defaultRunnerModel,
+ description: 'My runner',
+ maximumTimeout: 0,
+ tagList: 'tag1, tag2',
+ });
+ });
+
+ describe('immediately after submit', () => {
+ beforeEach(() => {
+ findForm().vm.$emit('submit', { preventDefault });
+ });
+
+ it('prevents default form submission', () => {
+ expect(preventDefault).toHaveBeenCalledTimes(1);
+ });
+
+ it('shows a saving state', () => {
+ expect(findSubmitBtn().props('loading')).toBe(true);
+ });
+
+ it('saves runner', async () => {
+ expect(runnerCreateHandler).toHaveBeenCalledWith({
+ input: {
+ ...defaultRunnerModel,
+ description: 'My runner',
+ maximumTimeout: 0,
+ tagList: ['tag1', 'tag2'],
+ },
+ });
+ });
+ });
+
+ describe('when saved successfully', () => {
+ beforeEach(async () => {
+ findForm().vm.$emit('submit', { preventDefault });
+ await waitForPromises();
+ });
+
+ it('emits "saved" result', async () => {
+ expect(wrapper.emitted('saved')[0]).toEqual([mockCreatedRunner]);
+ });
+
+ it('does not show a saving state', () => {
+ expect(findSubmitBtn().props('loading')).toBe(false);
+ });
+ });
+
+ describe('when a server error occurs', () => {
+ const error = new Error('Error!');
+
+ beforeEach(async () => {
+ runnerCreateHandler.mockRejectedValue(error);
+
+ findForm().vm.$emit('submit', { preventDefault });
+ await waitForPromises();
+ });
+
+ it('emits "error" result', async () => {
+ expect(wrapper.emitted('error')[0]).toEqual([error]);
+ });
+
+ it('does not show a saving state', () => {
+ expect(findSubmitBtn().props('loading')).toBe(false);
+ });
+
+ it('reports error', () => {
+ expect(captureException).toHaveBeenCalledTimes(1);
+ expect(captureException).toHaveBeenCalledWith({
+ component: 'RunnerCreateForm',
+ error,
+ });
+ });
+ });
+
+ describe('when a validation error occurs', () => {
+ const errorMsg1 = 'Issue1!';
+ const errorMsg2 = 'Issue2!';
+
+ beforeEach(async () => {
+ runnerCreateHandler.mockResolvedValue({
+ data: {
+ runnerCreate: {
+ errors: [errorMsg1, errorMsg2],
+ runner: null,
+ },
+ },
+ });
+
+ findForm().vm.$emit('submit', { preventDefault });
+ await waitForPromises();
+ });
+
+ it('emits "error" results', async () => {
+ expect(wrapper.emitted('error')[0]).toEqual([new Error(`${errorMsg1} ${errorMsg2}`)]);
+ });
+
+ it('does not show a saving state', () => {
+ expect(findSubmitBtn().props('loading')).toBe(false);
+ });
+
+ it('does not report error', () => {
+ expect(captureException).not.toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ci/runner/components/runner_delete_button_spec.js b/spec/frontend/ci/runner/components/runner_delete_button_spec.js
index 02960ad427e..f9bea318d84 100644
--- a/spec/frontend/ci/runner/components/runner_delete_button_spec.js
+++ b/spec/frontend/ci/runner/components/runner_delete_button_spec.js
@@ -8,7 +8,7 @@ import runnerDeleteMutation from '~/ci/runner/graphql/shared/runner_delete.mutat
import waitForPromises from 'helpers/wait_for_promises';
import { captureException } from '~/ci/runner/sentry_utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { I18N_DELETE_RUNNER } from '~/ci/runner/constants';
import RunnerDeleteButton from '~/ci/runner/components/runner_delete_button.vue';
@@ -21,7 +21,7 @@ const mockRunnerName = `#${mockRunnerId} (${mockRunner.shortSha})`;
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
describe('RunnerDeleteButton', () => {
@@ -53,8 +53,8 @@ describe('RunnerDeleteButton', () => {
},
apolloProvider,
directives: {
- GlTooltip: createMockDirective(),
- GlModal: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
+ GlModal: createMockDirective('gl-modal'),
},
});
};
@@ -83,10 +83,6 @@ describe('RunnerDeleteButton', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Displays a delete button without an icon', () => {
expect(findBtn().props()).toMatchObject({
loading: false,
diff --git a/spec/frontend/ci/runner/components/runner_details_spec.js b/spec/frontend/ci/runner/components/runner_details_spec.js
index 65a81973869..c2d9e86aa91 100644
--- a/spec/frontend/ci/runner/components/runner_details_spec.js
+++ b/spec/frontend/ci/runner/components/runner_details_spec.js
@@ -37,10 +37,6 @@ describe('RunnerDetails', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Details tab', () => {
describe.each`
field | runner | expectedValue
diff --git a/spec/frontend/ci/runner/components/runner_edit_button_spec.js b/spec/frontend/ci/runner/components/runner_edit_button_spec.js
index 907cdc90100..5cc1ee049f4 100644
--- a/spec/frontend/ci/runner/components/runner_edit_button_spec.js
+++ b/spec/frontend/ci/runner/components/runner_edit_button_spec.js
@@ -11,7 +11,7 @@ describe('RunnerEditButton', () => {
wrapper = mountFn(RunnerEditButton, {
attrs,
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
@@ -20,10 +20,6 @@ describe('RunnerEditButton', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Displays Edit text', () => {
expect(wrapper.attributes('aria-label')).toBe('Edit');
});
diff --git a/spec/frontend/ci/runner/components/runner_filtered_search_bar_spec.js b/spec/frontend/ci/runner/components/runner_filtered_search_bar_spec.js
index 408750e646f..ac84c7898bf 100644
--- a/spec/frontend/ci/runner/components/runner_filtered_search_bar_spec.js
+++ b/spec/frontend/ci/runner/components/runner_filtered_search_bar_spec.js
@@ -65,10 +65,6 @@ describe('RunnerList', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('binds a namespace to the filtered search', () => {
expect(findFilteredSearch().props('namespace')).toBe('runners');
});
diff --git a/spec/frontend/ci/runner/components/runner_groups_spec.js b/spec/frontend/ci/runner/components/runner_groups_spec.js
index 0991feb2e55..e4f5f55ab4b 100644
--- a/spec/frontend/ci/runner/components/runner_groups_spec.js
+++ b/spec/frontend/ci/runner/components/runner_groups_spec.js
@@ -23,10 +23,6 @@ describe('RunnerGroups', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Shows a heading', () => {
createComponent();
diff --git a/spec/frontend/ci/runner/components/runner_header_spec.js b/spec/frontend/ci/runner/components/runner_header_spec.js
index abe3b47767e..c851966431d 100644
--- a/spec/frontend/ci/runner/components/runner_header_spec.js
+++ b/spec/frontend/ci/runner/components/runner_header_spec.js
@@ -42,10 +42,6 @@ describe('RunnerHeader', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays the runner status', () => {
createComponent({
mountFn: mountExtended,
diff --git a/spec/frontend/ci/runner/components/runner_jobs_spec.js b/spec/frontend/ci/runner/components/runner_jobs_spec.js
index bdb8a4a31a3..365b0f1f5ba 100644
--- a/spec/frontend/ci/runner/components/runner_jobs_spec.js
+++ b/spec/frontend/ci/runner/components/runner_jobs_spec.js
@@ -4,7 +4,7 @@ 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 { createAlert } from '~/alert';
import RunnerJobs from '~/ci/runner/components/runner_jobs.vue';
import RunnerJobsTable from '~/ci/runner/components/runner_jobs_table.vue';
import RunnerPagination from '~/ci/runner/components/runner_pagination.vue';
@@ -15,7 +15,7 @@ import runnerJobsQuery from '~/ci/runner/graphql/show/runner_jobs.query.graphql'
import { runnerData, runnerJobsData } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
const mockRunner = runnerData.data.runner;
@@ -47,7 +47,6 @@ describe('RunnerJobs', () => {
afterEach(() => {
mockRunnerJobsQuery.mockReset();
- wrapper.destroy();
});
it('Requests runner jobs', async () => {
diff --git a/spec/frontend/ci/runner/components/runner_jobs_table_spec.js b/spec/frontend/ci/runner/components/runner_jobs_table_spec.js
index 281aa1aeb77..694c5a6ed17 100644
--- a/spec/frontend/ci/runner/components/runner_jobs_table_spec.js
+++ b/spec/frontend/ci/runner/components/runner_jobs_table_spec.js
@@ -37,10 +37,6 @@ describe('RunnerJobsTable', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Sets job id as a row key', () => {
createComponent();
diff --git a/spec/frontend/ci/runner/components/runner_list_empty_state_spec.js b/spec/frontend/ci/runner/components/runner_list_empty_state_spec.js
index 6aea3ddf58c..3e813723b5b 100644
--- a/spec/frontend/ci/runner/components/runner_list_empty_state_spec.js
+++ b/spec/frontend/ci/runner/components/runner_list_empty_state_spec.js
@@ -31,7 +31,7 @@ describe('RunnerListEmptyState', () => {
...props,
},
directives: {
- GlModal: createMockDirective(),
+ GlModal: createMockDirective('gl-modal'),
},
stubs: {
GlEmptyState,
@@ -62,11 +62,11 @@ describe('RunnerListEmptyState', () => {
expect(findEmptyState().text()).toMatchInterpolatedText(`${title} ${desc}`);
});
- describe('when create_runner_workflow is enabled', () => {
+ describe('when create_runner_workflow_for_admin is enabled', () => {
beforeEach(() => {
createComponent({
provide: {
- glFeatures: { createRunnerWorkflow: true },
+ glFeatures: { createRunnerWorkflowForAdmin: true },
},
});
});
@@ -76,14 +76,14 @@ describe('RunnerListEmptyState', () => {
});
});
- describe('when create_runner_workflow is enabled and newRunnerPath not defined', () => {
+ describe('when create_runner_workflow_for_admin is enabled and newRunnerPath not defined', () => {
beforeEach(() => {
createComponent({
props: {
newRunnerPath: null,
},
provide: {
- glFeatures: { createRunnerWorkflow: true },
+ glFeatures: { createRunnerWorkflowForAdmin: true },
},
});
});
@@ -95,11 +95,11 @@ describe('RunnerListEmptyState', () => {
});
});
- describe('when create_runner_workflow is disabled', () => {
+ describe('when create_runner_workflow_for_admin is disabled', () => {
beforeEach(() => {
createComponent({
provide: {
- glFeatures: { createRunnerWorkflow: false },
+ glFeatures: { createRunnerWorkflowForAdmin: false },
},
});
});
diff --git a/spec/frontend/ci/runner/components/runner_list_spec.js b/spec/frontend/ci/runner/components/runner_list_spec.js
index 2e5d1dbd063..6f4913dca3e 100644
--- a/spec/frontend/ci/runner/components/runner_list_spec.js
+++ b/spec/frontend/ci/runner/components/runner_list_spec.js
@@ -57,10 +57,6 @@ describe('RunnerList', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Displays headers', () => {
createComponent(
{
diff --git a/spec/frontend/ci/runner/components/runner_membership_toggle_spec.js b/spec/frontend/ci/runner/components/runner_membership_toggle_spec.js
index f089becd400..7ff3ec92042 100644
--- a/spec/frontend/ci/runner/components/runner_membership_toggle_spec.js
+++ b/spec/frontend/ci/runner/components/runner_membership_toggle_spec.js
@@ -18,10 +18,6 @@ describe('RunnerMembershipToggle', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Displays text', () => {
createComponent({ mountFn: mount });
diff --git a/spec/frontend/ci/runner/components/runner_pagination_spec.js b/spec/frontend/ci/runner/components/runner_pagination_spec.js
index f835ee4514d..6d84eb810f8 100644
--- a/spec/frontend/ci/runner/components/runner_pagination_spec.js
+++ b/spec/frontend/ci/runner/components/runner_pagination_spec.js
@@ -16,10 +16,6 @@ describe('RunnerPagination', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('When in between pages', () => {
const mockPageInfo = {
startCursor: mockStartCursor,
diff --git a/spec/frontend/ci/runner/components/runner_pause_button_spec.js b/spec/frontend/ci/runner/components/runner_pause_button_spec.js
index 12680e01b98..62e6cc902b7 100644
--- a/spec/frontend/ci/runner/components/runner_pause_button_spec.js
+++ b/spec/frontend/ci/runner/components/runner_pause_button_spec.js
@@ -7,7 +7,7 @@ import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_help
import runnerToggleActiveMutation from '~/ci/runner/graphql/shared/runner_toggle_active.mutation.graphql';
import waitForPromises from 'helpers/wait_for_promises';
import { captureException } from '~/ci/runner/sentry_utils';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import {
I18N_PAUSE,
I18N_PAUSE_TOOLTIP,
@@ -22,7 +22,7 @@ const mockRunner = allRunnersData.data.runners.nodes[0];
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
describe('RunnerPauseButton', () => {
@@ -46,7 +46,7 @@ describe('RunnerPauseButton', () => {
},
apolloProvider: createMockApollo([[runnerToggleActiveMutation, runnerToggleActiveHandler]]),
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
@@ -74,10 +74,6 @@ describe('RunnerPauseButton', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Pause/Resume action', () => {
describe.each`
runnerState | icon | content | tooltip | isActive | newActiveValue
diff --git a/spec/frontend/ci/runner/components/runner_paused_badge_spec.js b/spec/frontend/ci/runner/components/runner_paused_badge_spec.js
index b051ebe99a7..54768ea50da 100644
--- a/spec/frontend/ci/runner/components/runner_paused_badge_spec.js
+++ b/spec/frontend/ci/runner/components/runner_paused_badge_spec.js
@@ -16,7 +16,7 @@ describe('RunnerTypeBadge', () => {
...props,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
@@ -25,10 +25,6 @@ describe('RunnerTypeBadge', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders paused state', () => {
expect(wrapper.text()).toBe(I18N_PAUSED);
expect(findBadge().props('variant')).toBe('warning');
diff --git a/spec/frontend/ci/runner/components/runner_projects_spec.js b/spec/frontend/ci/runner/components/runner_projects_spec.js
index 17517c4db66..ccc1bc18675 100644
--- a/spec/frontend/ci/runner/components/runner_projects_spec.js
+++ b/spec/frontend/ci/runner/components/runner_projects_spec.js
@@ -4,7 +4,7 @@ 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 { createAlert } from '~/alert';
import { sprintf } from '~/locale';
import {
I18N_ASSIGNED_PROJECTS,
@@ -22,7 +22,7 @@ import runnerProjectsQuery from '~/ci/runner/graphql/show/runner_projects.query.
import { runnerData, runnerProjectsData } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
const mockRunner = runnerData.data.runner;
@@ -56,7 +56,6 @@ describe('RunnerProjects', () => {
afterEach(() => {
mockRunnerProjectsQuery.mockReset();
- wrapper.destroy();
});
it('Requests runner projects', async () => {
diff --git a/spec/frontend/ci/runner/components/runner_status_badge_spec.js b/spec/frontend/ci/runner/components/runner_status_badge_spec.js
index 45b410df2d4..e1eb81f2d23 100644
--- a/spec/frontend/ci/runner/components/runner_status_badge_spec.js
+++ b/spec/frontend/ci/runner/components/runner_status_badge_spec.js
@@ -31,7 +31,7 @@ describe('RunnerTypeBadge', () => {
...props,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
@@ -43,8 +43,6 @@ describe('RunnerTypeBadge', () => {
afterEach(() => {
jest.useFakeTimers({ legacyFakeTimers: true });
-
- wrapper.destroy();
});
it('renders online state', () => {
diff --git a/spec/frontend/ci/runner/components/runner_tag_spec.js b/spec/frontend/ci/runner/components/runner_tag_spec.js
index 7bcb046ae43..e3d46e5d6df 100644
--- a/spec/frontend/ci/runner/components/runner_tag_spec.js
+++ b/spec/frontend/ci/runner/components/runner_tag_spec.js
@@ -29,8 +29,8 @@ describe('RunnerTag', () => {
...props,
},
directives: {
- GlTooltip: createMockDirective(),
- GlResizeObserver: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
+ GlResizeObserver: createMockDirective('gl-resize-observer'),
},
});
};
@@ -39,10 +39,6 @@ describe('RunnerTag', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Displays tag text', () => {
expect(wrapper.text()).toBe(mockTag);
});
diff --git a/spec/frontend/ci/runner/components/runner_tags_spec.js b/spec/frontend/ci/runner/components/runner_tags_spec.js
index 96bec00302b..bcb1d1f9e13 100644
--- a/spec/frontend/ci/runner/components/runner_tags_spec.js
+++ b/spec/frontend/ci/runner/components/runner_tags_spec.js
@@ -21,10 +21,6 @@ describe('RunnerTags', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Displays tags text', () => {
expect(wrapper.text()).toMatchInterpolatedText('tag1 tag2');
diff --git a/spec/frontend/ci/runner/components/runner_type_badge_spec.js b/spec/frontend/ci/runner/components/runner_type_badge_spec.js
index 58f09362759..7a0fb6f69ea 100644
--- a/spec/frontend/ci/runner/components/runner_type_badge_spec.js
+++ b/spec/frontend/ci/runner/components/runner_type_badge_spec.js
@@ -23,15 +23,11 @@ describe('RunnerTypeBadge', () => {
...props,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe.each`
type | text
${INSTANCE_TYPE} | ${I18N_INSTANCE_TYPE}
diff --git a/spec/frontend/ci/runner/components/runner_type_tabs_spec.js b/spec/frontend/ci/runner/components/runner_type_tabs_spec.js
index 3347c190083..6e15c84ad7e 100644
--- a/spec/frontend/ci/runner/components/runner_type_tabs_spec.js
+++ b/spec/frontend/ci/runner/components/runner_type_tabs_spec.js
@@ -63,10 +63,6 @@ describe('RunnerTypeTabs', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Renders all options to filter runners by default', () => {
createComponent();
diff --git a/spec/frontend/ci/runner/components/runner_update_form_spec.js b/spec/frontend/ci/runner/components/runner_update_form_spec.js
index a0e51ebf958..620e2f85890 100644
--- a/spec/frontend/ci/runner/components/runner_update_form_spec.js
+++ b/spec/frontend/ci/runner/components/runner_update_form_spec.js
@@ -5,7 +5,7 @@ import { __ } from '~/locale';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert, VARIANT_SUCCESS } from '~/flash';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { redirectTo } from '~/lib/utils/url_utility';
import RunnerUpdateForm from '~/ci/runner/components/runner_update_form.vue';
import {
@@ -21,7 +21,7 @@ import { saveAlertToLocalStorage } from '~/ci/runner/local_storage_alert/save_al
import { runnerFormData } from '../mock_data';
jest.mock('~/ci/runner/local_storage_alert/save_alert_to_local_storage');
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
jest.mock('~/lib/utils/url_utility');
@@ -107,10 +107,6 @@ describe('RunnerUpdateForm', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Form has a submit button', () => {
expect(findSubmit().exists()).toBe(true);
});
diff --git a/spec/frontend/ci/runner/components/search_tokens/tag_token_spec.js b/spec/frontend/ci/runner/components/search_tokens/tag_token_spec.js
index b7d9d3ad23e..e9f2e888b9a 100644
--- a/spec/frontend/ci/runner/components/search_tokens/tag_token_spec.js
+++ b/spec/frontend/ci/runner/components/search_tokens/tag_token_spec.js
@@ -3,14 +3,14 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import TagToken, { TAG_SUGGESTIONS_PATH } from '~/ci/runner/components/search_tokens/tag_token.vue';
import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants';
import { getRecentlyUsedSuggestions } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/vue_shared/components/filtered_search_bar/filtered_search_utils', () => ({
...jest.requireActual('~/vue_shared/components/filtered_search_bar/filtered_search_utils'),
@@ -90,7 +90,6 @@ describe('TagToken', () => {
afterEach(() => {
getRecentlyUsedSuggestions.mockReset();
- wrapper.destroy();
});
describe('when the tags token is displayed', () => {
diff --git a/spec/frontend/ci/runner/components/stat/runner_single_stat_spec.js b/spec/frontend/ci/runner/components/stat/runner_single_stat_spec.js
index cad61f26012..f30b75ee614 100644
--- a/spec/frontend/ci/runner/components/stat/runner_single_stat_spec.js
+++ b/spec/frontend/ci/runner/components/stat/runner_single_stat_spec.js
@@ -32,10 +32,6 @@ describe('RunnerStats', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
case | count | value
${'number'} | ${99} | ${'99'}
diff --git a/spec/frontend/ci/runner/components/stat/runner_stats_spec.js b/spec/frontend/ci/runner/components/stat/runner_stats_spec.js
index 3d45674d106..13366a788d5 100644
--- a/spec/frontend/ci/runner/components/stat/runner_stats_spec.js
+++ b/spec/frontend/ci/runner/components/stat/runner_stats_spec.js
@@ -47,10 +47,6 @@ describe('RunnerStats', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Displays all the stats', () => {
createComponent({
mountFn: mount,
diff --git a/spec/frontend/ci/runner/group_runner_show/group_runner_show_app_spec.js b/spec/frontend/ci/runner/group_runner_show/group_runner_show_app_spec.js
index 2ad31dea774..fadc6e5ebc5 100644
--- a/spec/frontend/ci/runner/group_runner_show/group_runner_show_app_spec.js
+++ b/spec/frontend/ci/runner/group_runner_show/group_runner_show_app_spec.js
@@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert, VARIANT_SUCCESS } from '~/flash';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { redirectTo } from '~/lib/utils/url_utility';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
@@ -24,7 +24,7 @@ import { saveAlertToLocalStorage } from '~/ci/runner/local_storage_alert/save_al
import { runnerData } from '../mock_data';
jest.mock('~/ci/runner/local_storage_alert/save_alert_to_local_storage');
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
jest.mock('~/lib/utils/url_utility');
@@ -74,7 +74,6 @@ describe('GroupRunnerShowApp', () => {
afterEach(() => {
mockRunnerQuery.mockReset();
- wrapper.destroy();
});
describe('When showing runner details', () => {
diff --git a/spec/frontend/ci/runner/group_runners/group_runners_app_spec.js b/spec/frontend/ci/runner/group_runners/group_runners_app_spec.js
index 39ea5cade28..00c7262e38b 100644
--- a/spec/frontend/ci/runner/group_runners/group_runners_app_spec.js
+++ b/spec/frontend/ci/runner/group_runners/group_runners_app_spec.js
@@ -9,7 +9,7 @@ import {
mountExtended,
} from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { s__ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { updateHistory } from '~/lib/utils/url_utility';
@@ -74,7 +74,7 @@ const mockGroupRunnersCount = mockGroupRunnersEdges.length;
const mockGroupRunnersHandler = jest.fn();
const mockGroupRunnersCountHandler = jest.fn();
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
@@ -138,7 +138,6 @@ describe('GroupRunnersApp', () => {
afterEach(() => {
mockGroupRunnersHandler.mockReset();
mockGroupRunnersCountHandler.mockReset();
- wrapper.destroy();
});
it('shows the runner tabs with a runner count for each type', async () => {
diff --git a/spec/frontend/ci/runner/local_storage_alert/show_alert_from_local_storage_spec.js b/spec/frontend/ci/runner/local_storage_alert/show_alert_from_local_storage_spec.js
index 03908891cfd..30e49fc7644 100644
--- a/spec/frontend/ci/runner/local_storage_alert/show_alert_from_local_storage_spec.js
+++ b/spec/frontend/ci/runner/local_storage_alert/show_alert_from_local_storage_spec.js
@@ -2,9 +2,9 @@ import AccessorUtilities from '~/lib/utils/accessor';
import { showAlertFromLocalStorage } from '~/ci/runner/local_storage_alert/show_alert_from_local_storage';
import { LOCAL_STORAGE_ALERT_KEY } from '~/ci/runner/local_storage_alert/constants';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('showAlertFromLocalStorage', () => {
useLocalStorageSpy();
diff --git a/spec/frontend/ci/runner/mock_data.js b/spec/frontend/ci/runner/mock_data.js
index 5cdf0ea4e3b..092a419c1fe 100644
--- a/spec/frontend/ci/runner/mock_data.js
+++ b/spec/frontend/ci/runner/mock_data.js
@@ -1,6 +1,10 @@
// Fixtures generated by: spec/frontend/fixtures/runner.rb
+// Register runner queries
+import runnerForRegistration from 'test_fixtures/graphql/ci/runner/register/runner_for_registration.query.graphql.json';
+
// Show runner queries
+import runnerCreateResult from 'test_fixtures/graphql/ci/runner/new/runner_create.mutation.graphql.json';
import runnerData from 'test_fixtures/graphql/ci/runner/show/runner.query.graphql.json';
import runnerWithGroupData from 'test_fixtures/graphql/ci/runner/show/runner.query.graphql.with_group.json';
import runnerProjectsData from 'test_fixtures/graphql/ci/runner/show/runner_projects.query.graphql.json';
@@ -9,6 +13,8 @@ import runnerJobsData from 'test_fixtures/graphql/ci/runner/show/runner_jobs.que
// Edit runner queries
import runnerFormData from 'test_fixtures/graphql/ci/runner/edit/runner_form.query.graphql.json';
+// New runner queries
+
// List queries
import allRunnersData from 'test_fixtures/graphql/ci/runner/list/all_runners.query.graphql.json';
import allRunnersDataPaginated from 'test_fixtures/graphql/ci/runner/list/all_runners.query.graphql.paginated.json';
@@ -321,4 +327,6 @@ export {
runnerProjectsData,
runnerJobsData,
runnerFormData,
+ runnerCreateResult,
+ runnerForRegistration,
};
diff --git a/spec/frontend/ci/runner/runner_edit/runner_edit_app_spec.js b/spec/frontend/ci/runner/runner_edit/runner_edit_app_spec.js
index a9369a5e626..79bbf95f8f0 100644
--- a/spec/frontend/ci/runner/runner_edit/runner_edit_app_spec.js
+++ b/spec/frontend/ci/runner/runner_edit/runner_edit_app_spec.js
@@ -3,7 +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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerHeader from '~/ci/runner/components/runner_header.vue';
@@ -15,7 +15,7 @@ import { I18N_STATUS_NEVER_CONTACTED, I18N_INSTANCE_TYPE } from '~/ci/runner/con
import { runnerFormData } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
const mockRunner = runnerFormData.data.runner;
@@ -51,7 +51,6 @@ describe('RunnerEditApp', () => {
afterEach(() => {
mockRunnerQuery.mockReset();
- wrapper.destroy();
});
it('expect GraphQL ID to be requested', async () => {
diff --git a/spec/frontend/ci_secure_files/components/metadata/__snapshots__/modal_spec.js.snap b/spec/frontend/ci_secure_files/components/metadata/__snapshots__/modal_spec.js.snap
index b2084e3a7de..1be89ae832d 100644
--- a/spec/frontend/ci_secure_files/components/metadata/__snapshots__/modal_spec.js.snap
+++ b/spec/frontend/ci_secure_files/components/metadata/__snapshots__/modal_spec.js.snap
@@ -15,7 +15,7 @@ exports[`Secure File Metadata Modal when a .cer file is supplied matches cer the
>
<table
- aria-busy="false"
+ aria-busy=""
aria-colcount="2"
class="table b-table gl-table"
role="table"
@@ -196,7 +196,7 @@ exports[`Secure File Metadata Modal when a .mobileprovision file is supplied mat
>
<table
- aria-busy="false"
+ aria-busy=""
aria-colcount="2"
class="table b-table gl-table"
role="table"
diff --git a/spec/frontend/ci_secure_files/components/metadata/button_spec.js b/spec/frontend/ci_secure_files/components/metadata/button_spec.js
index 4ac5b3325d4..5bd4bab25af 100644
--- a/spec/frontend/ci_secure_files/components/metadata/button_spec.js
+++ b/spec/frontend/ci_secure_files/components/metadata/button_spec.js
@@ -12,10 +12,6 @@ describe('Secure File Metadata Button', () => {
const findButton = () => wrapper.findComponent(GlButton);
- afterEach(() => {
- wrapper.destroy();
- });
-
const createWrapper = (secureFile = {}, admin = false) => {
wrapper = mount(Button, {
propsData: {
diff --git a/spec/frontend/ci_secure_files/components/metadata/modal_spec.js b/spec/frontend/ci_secure_files/components/metadata/modal_spec.js
index 230507d32d7..e181d15f2f9 100644
--- a/spec/frontend/ci_secure_files/components/metadata/modal_spec.js
+++ b/spec/frontend/ci_secure_files/components/metadata/modal_spec.js
@@ -37,7 +37,6 @@ describe('Secure File Metadata Modal', () => {
afterEach(() => {
unmockTracking();
- wrapper.destroy();
});
describe('when a .cer file is supplied', () => {
diff --git a/spec/frontend/ci_secure_files/components/secure_files_list_spec.js b/spec/frontend/ci_secure_files/components/secure_files_list_spec.js
index ab6200ca6f4..17b5fdc4dde 100644
--- a/spec/frontend/ci_secure_files/components/secure_files_list_spec.js
+++ b/spec/frontend/ci_secure_files/components/secure_files_list_spec.js
@@ -15,11 +15,6 @@ const dummyApiVersion = 'v3000';
const dummyProjectId = 1;
const fileSizeLimit = 5;
const dummyUrlRoot = '/gitlab';
-const dummyGon = {
- api_version: dummyApiVersion,
- relative_url_root: dummyUrlRoot,
-};
-let originalGon;
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${dummyProjectId}/secure_files`;
describe('SecureFilesList', () => {
@@ -28,16 +23,16 @@ describe('SecureFilesList', () => {
let trackingSpy;
beforeEach(() => {
- originalGon = window.gon;
trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
- window.gon = { ...dummyGon };
+ window.gon = {
+ api_version: dummyApiVersion,
+ relative_url_root: dummyUrlRoot,
+ };
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
unmockTracking();
- window.gon = originalGon;
});
const createWrapper = (admin = true, props = {}) => {
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 6b374b6620d..770815a9403 100644
--- a/spec/frontend/clusters/agents/components/activity_events_list_spec.js
+++ b/spec/frontend/clusters/agents/components/activity_events_list_spec.js
@@ -44,10 +44,6 @@ describe('ActivityEvents', () => {
const findAllActivityHistoryItems = () => wrapper.findAllComponents(ActivityHistoryItem);
const findSectionTitle = (at) => wrapper.findAllByTestId('activity-section-title').at(at);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('while the agentEvents query is loading', () => {
it('displays a loading icon', async () => {
createWrapper();
diff --git a/spec/frontend/clusters/agents/components/activity_history_item_spec.js b/spec/frontend/clusters/agents/components/activity_history_item_spec.js
index 68f6f11aa8f..48460519c6c 100644
--- a/spec/frontend/clusters/agents/components/activity_history_item_spec.js
+++ b/spec/frontend/clusters/agents/components/activity_history_item_spec.js
@@ -25,10 +25,6 @@ describe('ActivityHistoryItem', () => {
const findHistoryItem = () => wrapper.findComponent(HistoryItem);
const findTimeAgo = () => wrapper.findComponent(TimeAgoTooltip);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe.each`
kind | icon | title | lineNumber
${'token_created'} | ${EVENT_DETAILS.token_created.eventTypeIcon} | ${sprintf(EVENT_DETAILS.token_created.title, { tokenName: agentName })} | ${0}
diff --git a/spec/frontend/clusters/agents/components/agent_integration_status_row_spec.js b/spec/frontend/clusters/agents/components/agent_integration_status_row_spec.js
index db1219ccb41..ac0ce89f334 100644
--- a/spec/frontend/clusters/agents/components/agent_integration_status_row_spec.js
+++ b/spec/frontend/clusters/agents/components/agent_integration_status_row_spec.js
@@ -25,10 +25,6 @@ describe('IntegrationStatus', () => {
const findIcon = () => wrapper.findComponent(GlIcon);
const findBadge = () => wrapper.findComponent(GlBadge);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('icon', () => {
const icon = 'status-success';
const iconClass = 'gl-text-green-500';
diff --git a/spec/frontend/clusters/agents/components/create_token_button_spec.js b/spec/frontend/clusters/agents/components/create_token_button_spec.js
index 73856b74a8d..5a8906813cf 100644
--- a/spec/frontend/clusters/agents/components/create_token_button_spec.js
+++ b/spec/frontend/clusters/agents/components/create_token_button_spec.js
@@ -21,7 +21,7 @@ describe('CreateTokenButton', () => {
...provideData,
},
directives: {
- GlModalDirective: createMockDirective(),
+ GlModalDirective: createMockDirective('gl-modal-directive'),
},
stubs: {
GlTooltip,
@@ -29,10 +29,6 @@ describe('CreateTokenButton', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when user can create token', () => {
beforeEach(() => {
createWrapper();
diff --git a/spec/frontend/clusters/agents/components/create_token_modal_spec.js b/spec/frontend/clusters/agents/components/create_token_modal_spec.js
index 0d10801e80e..ff698952c6b 100644
--- a/spec/frontend/clusters/agents/components/create_token_modal_spec.js
+++ b/spec/frontend/clusters/agents/components/create_token_modal_spec.js
@@ -119,7 +119,6 @@ describe('CreateTokenModal', () => {
});
afterEach(() => {
- wrapper.destroy();
apolloProvider = null;
createResponse = null;
});
diff --git a/spec/frontend/clusters/agents/components/integration_status_spec.js b/spec/frontend/clusters/agents/components/integration_status_spec.js
index 36f0e622452..28a59391578 100644
--- a/spec/frontend/clusters/agents/components/integration_status_spec.js
+++ b/spec/frontend/clusters/agents/components/integration_status_spec.js
@@ -27,10 +27,6 @@ describe('IntegrationStatus', () => {
const findAgentStatus = () => wrapper.findByTestId('agent-status');
const findAgentIntegrationStatusRows = () => wrapper.findAllComponents(AgentIntegrationStatusRow);
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
lastUsedAt | status | iconName
${null} | ${'Never connected'} | ${'status-neutral'}
diff --git a/spec/frontend/clusters/agents/components/revoke_token_button_spec.js b/spec/frontend/clusters/agents/components/revoke_token_button_spec.js
index 6521221cbd7..ed7c940bb04 100644
--- a/spec/frontend/clusters/agents/components/revoke_token_button_spec.js
+++ b/spec/frontend/clusters/agents/components/revoke_token_button_spec.js
@@ -45,7 +45,7 @@ describe('RevokeTokenButton', () => {
const findInput = () => wrapper.findComponent(GlFormInput);
const findTooltip = () => wrapper.findComponent(GlTooltip);
const findPrimaryAction = () => findModal().props('actionPrimary');
- const findPrimaryActionAttributes = (attr) => findPrimaryAction().attributes[0][attr];
+ const findPrimaryActionAttributes = (attr) => findPrimaryAction().attributes[attr];
const createMockApolloProvider = ({ mutationResponse }) => {
revokeSpy = jest.fn().mockResolvedValue(mutationResponse);
@@ -105,7 +105,6 @@ describe('RevokeTokenButton', () => {
});
afterEach(() => {
- wrapper.destroy();
apolloProvider = null;
revokeSpy = null;
});
diff --git a/spec/frontend/clusters/agents/components/show_spec.js b/spec/frontend/clusters/agents/components/show_spec.js
index efa85136b17..118a3af48e0 100644
--- a/spec/frontend/clusters/agents/components/show_spec.js
+++ b/spec/frontend/clusters/agents/components/show_spec.js
@@ -79,10 +79,6 @@ describe('ClusterAgentShow', () => {
const findActivity = () => wrapper.findComponent(ActivityEvents);
const findIntegrationStatus = () => wrapper.findComponent(IntegrationStatus);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default behaviour', () => {
beforeEach(async () => {
createWrapper({ clusterAgent: defaultClusterAgent });
diff --git a/spec/frontend/clusters/agents/components/token_table_spec.js b/spec/frontend/clusters/agents/components/token_table_spec.js
index 334615f1818..1a6aeedb694 100644
--- a/spec/frontend/clusters/agents/components/token_table_spec.js
+++ b/spec/frontend/clusters/agents/components/token_table_spec.js
@@ -57,10 +57,6 @@ describe('ClusterAgentTokenTable', () => {
return createComponent(defaultTokens);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays the create token button', () => {
expect(findCreateTokenBtn().exists()).toBe(true);
});
diff --git a/spec/frontend/clusters/components/__snapshots__/new_cluster_spec.js.snap b/spec/frontend/clusters/components/__snapshots__/new_cluster_spec.js.snap
index 656e72baf77..21ffda8578a 100644
--- a/spec/frontend/clusters/components/__snapshots__/new_cluster_spec.js.snap
+++ b/spec/frontend/clusters/components/__snapshots__/new_cluster_spec.js.snap
@@ -3,7 +3,7 @@
exports[`NewCluster renders the cluster component correctly 1`] = `
"<div class=\\"gl-pt-4\\">
<h4>Enter your Kubernetes cluster certificate details</h4>
- <p>Enter details about your cluster. <b-link-stub href=\\"/help/user/project/clusters/add_existing_cluster\\" event=\\"click\\" routertag=\\"a\\" class=\\"gl-link\\">How do I use a certificate to connect to my cluster?</b-link-stub>
+ <p>Enter details about your cluster. <b-link-stub href=\\"/help/user/project/clusters/add_existing_cluster\\" class=\\"gl-link\\">How do I use a certificate to connect to my cluster?</b-link-stub>
</p>
</div>"
`;
diff --git a/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap b/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap
index 46ee123a12d..67b0ecdf7eb 100644
--- a/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap
+++ b/spec/frontend/clusters/components/__snapshots__/remove_cluster_confirmation_spec.js.snap
@@ -43,3 +43,212 @@ exports[`Remove cluster confirmation modal renders buttons with modal included 1
<!---->
</div>
`;
+
+exports[`Remove cluster confirmation modal two buttons open modal with "cleanup" option 1`] = `
+<div
+ class="gl-display-flex"
+>
+ <button
+ class="btn gl-mr-3 btn-danger btn-md gl-button"
+ data-testid="remove-integration-and-resources-button"
+ type="button"
+ >
+ <!---->
+
+ <!---->
+
+ <span
+ class="gl-button-text"
+ >
+
+ Remove integration and resources
+
+ </span>
+ </button>
+
+ <button
+ class="btn btn-danger btn-md gl-button btn-danger-secondary"
+ data-testid="remove-integration-button"
+ type="button"
+ >
+ <!---->
+
+ <!---->
+
+ <span
+ class="gl-button-text"
+ >
+
+ Remove integration
+
+ </span>
+ </button>
+
+ <div
+ kind="danger"
+ >
+ <p>
+ You are about to remove your cluster integration and all GitLab-created resources associated with this cluster.
+ </p>
+
+ <div>
+
+ This will permanently delete the following resources:
+
+ <ul>
+ <li>
+ Any project namespaces
+ </li>
+
+ <li>
+ <code>
+ clusterroles
+ </code>
+ </li>
+
+ <li>
+ <code>
+ clusterrolebindings
+ </code>
+ </li>
+ </ul>
+ </div>
+
+ <strong>
+ To remove your integration and resources, type
+ <code>
+ my-test-cluster
+ </code>
+ to confirm:
+ </strong>
+
+ <form
+ action="clusterPath"
+ class="gl-mb-5"
+ method="post"
+ >
+ <input
+ name="_method"
+ type="hidden"
+ value="delete"
+ />
+
+ <input
+ name="authenticity_token"
+ type="hidden"
+ />
+
+ <input
+ name="cleanup"
+ type="hidden"
+ value="true"
+ />
+
+ <input
+ autocomplete="off"
+ class="gl-form-input form-control"
+ id="__BVID__14"
+ name="confirm_cluster_name_input"
+ type="text"
+ />
+ </form>
+
+ <span>
+ If you do not wish to delete all associated GitLab resources, you can simply remove the integration.
+ </span>
+ </div>
+</div>
+`;
+
+exports[`Remove cluster confirmation modal two buttons open modal without "cleanup" option 1`] = `
+<div
+ class="gl-display-flex"
+>
+ <button
+ class="btn gl-mr-3 btn-danger btn-md gl-button"
+ data-testid="remove-integration-and-resources-button"
+ type="button"
+ >
+ <!---->
+
+ <!---->
+
+ <span
+ class="gl-button-text"
+ >
+
+ Remove integration and resources
+
+ </span>
+ </button>
+
+ <button
+ class="btn btn-danger btn-md gl-button btn-danger-secondary"
+ data-testid="remove-integration-button"
+ type="button"
+ >
+ <!---->
+
+ <!---->
+
+ <span
+ class="gl-button-text"
+ >
+
+ Remove integration
+
+ </span>
+ </button>
+
+ <div
+ kind="danger"
+ >
+ <p>
+ You are about to remove your cluster integration.
+ </p>
+
+ <!---->
+
+ <strong>
+ To remove your integration, type
+ <code>
+ my-test-cluster
+ </code>
+ to confirm:
+ </strong>
+
+ <form
+ action="clusterPath"
+ class="gl-mb-5"
+ method="post"
+ >
+ <input
+ name="_method"
+ type="hidden"
+ value="delete"
+ />
+
+ <input
+ name="authenticity_token"
+ type="hidden"
+ />
+
+ <input
+ name="cleanup"
+ type="hidden"
+ value="true"
+ />
+
+ <input
+ autocomplete="off"
+ class="gl-form-input form-control"
+ id="__BVID__21"
+ name="confirm_cluster_name_input"
+ type="text"
+ />
+ </form>
+
+ <!---->
+ </div>
+</div>
+`;
diff --git a/spec/frontend/clusters/components/new_cluster_spec.js b/spec/frontend/clusters/components/new_cluster_spec.js
index ef39c90aaef..398b472a3a7 100644
--- a/spec/frontend/clusters/components/new_cluster_spec.js
+++ b/spec/frontend/clusters/components/new_cluster_spec.js
@@ -20,10 +20,6 @@ describe('NewCluster', () => {
return createWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the cluster component correctly', () => {
expect(wrapper.html()).toMatchSnapshot();
});
diff --git a/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js b/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js
index 53683af893a..04b7909b534 100644
--- a/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js
+++ b/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js
@@ -6,6 +6,7 @@ import RemoveClusterConfirmation from '~/clusters/components/remove_cluster_conf
describe('Remove cluster confirmation modal', () => {
let wrapper;
+ const showMock = jest.fn();
const createComponent = ({ props = {}, stubs = {} } = {}) => {
wrapper = mount(RemoveClusterConfirmation, {
@@ -18,11 +19,6 @@ describe('Remove cluster confirmation modal', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders buttons with modal included', () => {
createComponent();
expect(wrapper.element).toMatchSnapshot();
@@ -38,9 +34,13 @@ describe('Remove cluster confirmation modal', () => {
beforeEach(() => {
createComponent({
props: { clusterName: 'my-test-cluster' },
- stubs: { GlSprintf, GlModal: stubComponent(GlModal) },
+ stubs: {
+ GlSprintf,
+ GlModal: stubComponent(GlModal, {
+ methods: { show: showMock },
+ }),
+ },
});
- jest.spyOn(findModal().vm, 'show').mockReturnValue();
});
it('open modal with "cleanup" option', async () => {
@@ -48,8 +48,8 @@ describe('Remove cluster confirmation modal', () => {
await nextTick();
- expect(findModal().vm.show).toHaveBeenCalled();
- expect(wrapper.vm.confirmCleanup).toEqual(true);
+ expect(showMock).toHaveBeenCalled();
+ expect(wrapper.element).toMatchSnapshot();
expect(findModal().html()).toContain(
'<strong>To remove your integration and resources, type <code>my-test-cluster</code> to confirm:</strong>',
);
@@ -60,8 +60,8 @@ describe('Remove cluster confirmation modal', () => {
await nextTick();
- expect(findModal().vm.show).toHaveBeenCalled();
- expect(wrapper.vm.confirmCleanup).toEqual(false);
+ expect(showMock).toHaveBeenCalled();
+ expect(wrapper.element).toMatchSnapshot();
expect(findModal().html()).toContain(
'<strong>To remove your integration, type <code>my-test-cluster</code> to confirm:</strong>',
);
diff --git a/spec/frontend/clusters/forms/components/integration_form_spec.js b/spec/frontend/clusters/forms/components/integration_form_spec.js
index b17886a5826..396f8215b9f 100644
--- a/spec/frontend/clusters/forms/components/integration_form_spec.js
+++ b/spec/frontend/clusters/forms/components/integration_form_spec.js
@@ -1,6 +1,6 @@
import { GlToggle, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue, { nextTick } from 'vue';
+import Vue from 'vue';
import Vuex from 'vuex';
import IntegrationForm from '~/clusters/forms/components/integration_form.vue';
import { createStore } from '~/clusters/forms/stores/index';
@@ -27,17 +27,9 @@ describe('ClusterIntegrationForm', () => {
});
};
- const destroyWrapper = () => {
- wrapper.destroy();
- wrapper = null;
- };
-
const findSubmitButton = () => wrapper.findComponent(GlButton);
const findGlToggle = () => wrapper.findComponent(GlToggle);
-
- afterEach(() => {
- destroyWrapper();
- });
+ const findClusterEnvironmentScopeInput = () => wrapper.find('[id="cluster_environment_scope"]');
describe('rendering', () => {
beforeEach(() => createWrapper());
@@ -50,7 +42,9 @@ describe('ClusterIntegrationForm', () => {
});
it('sets the envScope to default', () => {
- expect(wrapper.find('[id="cluster_environment_scope"]').attributes('value')).toBe('*');
+ expect(findClusterEnvironmentScopeInput().attributes('value')).toBe(
+ defaultStoreValues.environmentScope,
+ );
});
it('sets the baseDomain to default', () => {
@@ -76,20 +70,15 @@ describe('ClusterIntegrationForm', () => {
beforeEach(() => createWrapper());
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 });
+ await findGlToggle().vm.$emit('change', false);
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` });
+ await findClusterEnvironmentScopeInput().vm.$emit(
+ 'input',
+ `${defaultStoreValues.environmentScope}1`,
+ );
expect(findSubmitButton().props('disabled')).toBe(false);
});
});
diff --git a/spec/frontend/clusters_list/components/agent_token_spec.js b/spec/frontend/clusters_list/components/agent_token_spec.js
index a92a03fedb6..edb8b22d79e 100644
--- a/spec/frontend/clusters_list/components/agent_token_spec.js
+++ b/spec/frontend/clusters_list/components/agent_token_spec.js
@@ -53,10 +53,6 @@ describe('InstallAgentModal', () => {
createWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('initial state', () => {
it('shows basic agent installation instructions', () => {
expect(wrapper.text()).toContain(I18N_AGENT_TOKEN.basicInstallTitle);
diff --git a/spec/frontend/clusters_list/components/agents_spec.js b/spec/frontend/clusters_list/components/agents_spec.js
index 2372ab30300..d91245ba9b4 100644
--- a/spec/frontend/clusters_list/components/agents_spec.js
+++ b/spec/frontend/clusters_list/components/agents_spec.js
@@ -83,8 +83,6 @@ describe('Agents', () => {
const findBanner = () => wrapper.findComponent(GlBanner);
afterEach(() => {
- wrapper.destroy();
-
localStorage.removeItem(AGENT_FEEDBACK_KEY);
});
diff --git a/spec/frontend/clusters_list/components/ancestor_notice_spec.js b/spec/frontend/clusters_list/components/ancestor_notice_spec.js
index 758f6586e1a..4a2effa3463 100644
--- a/spec/frontend/clusters_list/components/ancestor_notice_spec.js
+++ b/spec/frontend/clusters_list/components/ancestor_notice_spec.js
@@ -18,10 +18,6 @@ describe('ClustersAncestorNotice', () => {
return createWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when cluster does not have ancestors', () => {
beforeEach(async () => {
store.state.hasAncestorClusters = false;
diff --git a/spec/frontend/clusters_list/components/clusters_actions_spec.js b/spec/frontend/clusters_list/components/clusters_actions_spec.js
index f4ee3f93cb5..e4e1986f705 100644
--- a/spec/frontend/clusters_list/components/clusters_actions_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_actions_spec.js
@@ -35,7 +35,7 @@ describe('ClustersActionsComponent', () => {
...provideData,
},
directives: {
- GlModalDirective: createMockDirective(),
+ GlModalDirective: createMockDirective('gl-modal-directive'),
},
});
};
@@ -44,9 +44,6 @@ describe('ClustersActionsComponent', () => {
createWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- });
describe('when the certificate based clusters are enabled', () => {
it('renders actions menu', () => {
expect(findDropdown().exists()).toBe(true);
diff --git a/spec/frontend/clusters_list/components/clusters_empty_state_spec.js b/spec/frontend/clusters_list/components/clusters_empty_state_spec.js
index 2c3a224f3c8..5a5006d24c4 100644
--- a/spec/frontend/clusters_list/components/clusters_empty_state_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_empty_state_spec.js
@@ -21,10 +21,6 @@ describe('ClustersEmptyStateComponent', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when the help text is not provided', () => {
beforeEach(() => {
createWrapper();
diff --git a/spec/frontend/clusters_list/components/clusters_main_view_spec.js b/spec/frontend/clusters_list/components/clusters_main_view_spec.js
index 6f23ed47d2a..af8d3b59869 100644
--- a/spec/frontend/clusters_list/components/clusters_main_view_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_main_view_spec.js
@@ -40,10 +40,6 @@ describe('ClustersMainViewComponent', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findTabs = () => wrapper.findComponent(GlTabs);
const findAllTabs = () => wrapper.findAllComponents(GlTab);
const findGlTabAtIndex = (index) => findAllTabs().at(index);
diff --git a/spec/frontend/clusters_list/components/clusters_spec.js b/spec/frontend/clusters_list/components/clusters_spec.js
index 20dbff9df15..207bfddcb4f 100644
--- a/spec/frontend/clusters_list/components/clusters_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_spec.js
@@ -75,7 +75,6 @@ describe('Clusters', () => {
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
captureException.mockRestore();
});
@@ -271,9 +270,7 @@ describe('Clusters', () => {
describe('when updating currentPage', () => {
beforeEach(() => {
mockPollingApi(HTTP_STATUS_OK, apiData, paginationHeader(totalSecondPage, perPage, 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({ currentPage: 2 });
+ findPaginatedButtons().vm.$emit('input', 2);
return axios.waitForAll();
});
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 b4eb9242003..e81b242dd90 100644
--- a/spec/frontend/clusters_list/components/clusters_view_all_spec.js
+++ b/spec/frontend/clusters_list/components/clusters_view_all_spec.js
@@ -60,10 +60,6 @@ describe('ClustersViewAllComponent', () => {
createWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when agents and clusters are not loaded', () => {
const initialState = {
loadingClusters: true,
diff --git a/spec/frontend/clusters_list/components/delete_agent_button_spec.js b/spec/frontend/clusters_list/components/delete_agent_button_spec.js
index 82850b9dea4..53cf67bca0f 100644
--- a/spec/frontend/clusters_list/components/delete_agent_button_spec.js
+++ b/spec/frontend/clusters_list/components/delete_agent_button_spec.js
@@ -33,7 +33,7 @@ describe('DeleteAgentButton', () => {
const findDeleteBtn = () => wrapper.findComponent(GlButton);
const findInput = () => wrapper.findComponent(GlFormInput);
const findPrimaryAction = () => findModal().props('actionPrimary');
- const findPrimaryActionAttributes = (attr) => findPrimaryAction().attributes[0][attr];
+ const findPrimaryActionAttributes = (attr) => findPrimaryAction().attributes[attr];
const findDeleteAgentButtonTooltip = () => wrapper.findByTestId('delete-agent-button-tooltip');
const getTooltipText = (el) => {
const binding = getBinding(el, 'gl-tooltip');
@@ -84,7 +84,7 @@ describe('DeleteAgentButton', () => {
...provideData,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
propsData,
mocks: { $toast: { show: toast } },
@@ -108,7 +108,6 @@ describe('DeleteAgentButton', () => {
});
afterEach(() => {
- wrapper.destroy();
apolloProvider = null;
deleteResponse = null;
toast = null;
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 10264d6a011..3156eaaecfc 100644
--- a/spec/frontend/clusters_list/components/install_agent_modal_spec.js
+++ b/spec/frontend/clusters_list/components/install_agent_modal_spec.js
@@ -139,7 +139,6 @@ describe('InstallAgentModal', () => {
});
afterEach(() => {
- wrapper.destroy();
apolloProvider = null;
});
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 3211ba44eff..a3dfc848fc8 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
@@ -13,10 +13,6 @@ describe('NodeErrorHelpText', () => {
const findPopover = () => wrapper.findComponent(GlPopover);
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
errorType | wrapperText | popoverText
${'authentication_error'} | ${'Unable to Authenticate'} | ${'GitLab failed to authenticate'}
diff --git a/spec/frontend/clusters_list/store/actions_spec.js b/spec/frontend/clusters_list/store/actions_spec.js
index 360fd3b2842..6d23db0517d 100644
--- a/spec/frontend/clusters_list/store/actions_spec.js
+++ b/spec/frontend/clusters_list/store/actions_spec.js
@@ -5,13 +5,13 @@ import waitForPromises from 'helpers/wait_for_promises';
import { MAX_REQUESTS } from '~/clusters_list/constants';
import * as actions from '~/clusters_list/store/actions';
import * as types from '~/clusters_list/store/mutation_types';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import Poll from '~/lib/utils/poll';
import { apiData } from '../mock_data';
-jest.mock('~/flash.js');
+jest.mock('~/alert');
describe('Clusters store actions', () => {
let captureException;
@@ -81,7 +81,7 @@ describe('Clusters store actions', () => {
);
});
- it('should show flash on API error', async () => {
+ it('should show alert on API error', async () => {
mock.onGet().reply(HTTP_STATUS_BAD_REQUEST, 'Not Found');
await testAction(
diff --git a/spec/frontend/code_navigation/components/app_spec.js b/spec/frontend/code_navigation/components/app_spec.js
index b9be262efd0..88861b0d08a 100644
--- a/spec/frontend/code_navigation/components/app_spec.js
+++ b/spec/frontend/code_navigation/components/app_spec.js
@@ -32,10 +32,6 @@ function factory(initialState = {}, props = {}) {
}
describe('Code navigation app component', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it('sets initial data on mount if the correct props are passed', () => {
const codeNavigationPath = 'code/nav/path.js';
const path = 'blob/path.js';
diff --git a/spec/frontend/code_navigation/components/popover_spec.js b/spec/frontend/code_navigation/components/popover_spec.js
index 874263e046a..1bfaf7e959e 100644
--- a/spec/frontend/code_navigation/components/popover_spec.js
+++ b/spec/frontend/code_navigation/components/popover_spec.js
@@ -61,10 +61,6 @@ function factory({ position, data, definitionPathPrefix, blobPath = 'index.js' }
}
describe('Code navigation popover component', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders popover', () => {
factory({
position: { x: 0, y: 0, height: 0 },
diff --git a/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js b/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js
index debd10de118..64623968aa0 100644
--- a/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js
+++ b/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js
@@ -5,7 +5,7 @@ import { shallowMount } from '@vue/test-utils';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import CommitBoxPipelineMiniGraph from '~/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue';
import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
import { COMMIT_BOX_POLL_INTERVAL } from '~/projects/commit_box/info/constants';
@@ -20,7 +20,7 @@ import {
mockUpstreamQueryResponse,
} from './mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(VueApollo);
@@ -69,10 +69,6 @@ describe('Commit box pipeline mini graph', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('loading state', () => {
it('should display loading state when loading', () => {
createComponent();
diff --git a/spec/frontend/commit/commit_pipeline_status_component_spec.js b/spec/frontend/commit/commit_pipeline_status_component_spec.js
index e75fb697a7b..e474ef9c635 100644
--- a/spec/frontend/commit/commit_pipeline_status_component_spec.js
+++ b/spec/frontend/commit/commit_pipeline_status_component_spec.js
@@ -3,14 +3,14 @@ import { shallowMount } from '@vue/test-utils';
import Visibility from 'visibilityjs';
import { nextTick } from 'vue';
import fixture from 'test_fixtures/pipelines/pipelines.json';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import Poll from '~/lib/utils/poll';
import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
jest.mock('~/lib/utils/poll');
jest.mock('visibilityjs');
-jest.mock('~/flash');
+jest.mock('~/alert');
const mockFetchData = jest.fn();
jest.mock('~/projects/tree/services/commit_pipeline_service', () =>
@@ -41,11 +41,6 @@ describe('Commit pipeline status component', () => {
const findLink = () => wrapper.find('a');
const findCiIcon = () => findLink().findComponent(CiIcon);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('Visibility management', () => {
describe('when component is hidden', () => {
beforeEach(() => {
@@ -169,7 +164,7 @@ describe('Commit pipeline status component', () => {
});
});
- it('displays flash error message', () => {
+ it('displays alert error message', () => {
expect(createAlert).toHaveBeenCalled();
});
});
diff --git a/spec/frontend/commit/components/commit_box_pipeline_status_spec.js b/spec/frontend/commit/components/commit_box_pipeline_status_spec.js
index 8d455f8a3d7..9c7a41b3506 100644
--- a/spec/frontend/commit/components/commit_box_pipeline_status_spec.js
+++ b/spec/frontend/commit/components/commit_box_pipeline_status_spec.js
@@ -4,7 +4,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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import CommitBoxPipelineStatus from '~/projects/commit_box/info/components/commit_box_pipeline_status.vue';
import {
@@ -23,7 +23,7 @@ const mockProvide = {
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Commit box pipeline status', () => {
let wrapper;
@@ -54,10 +54,6 @@ describe('Commit box pipeline status', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('loading state', () => {
it('should display loading state when loading', () => {
createComponent();
diff --git a/spec/frontend/commit/components/signature_badge_spec.js b/spec/frontend/commit/components/signature_badge_spec.js
new file mode 100644
index 00000000000..d52ad2b43e2
--- /dev/null
+++ b/spec/frontend/commit/components/signature_badge_spec.js
@@ -0,0 +1,134 @@
+import { GlBadge, GlLink, GlPopover } from '@gitlab/ui';
+import { stubComponent, RENDER_ALL_SLOTS_TEMPLATE } from 'helpers/stub_component';
+import SignatureBadge from '~/commit/components/signature_badge.vue';
+import X509CertificateDetails from '~/commit/components/x509_certificate_details.vue';
+import { typeConfig, statusConfig, verificationStatuses, signatureTypes } from '~/commit/constants';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { sshSignatureProp, gpgSignatureProp, x509SignatureProp } from '../mock_data';
+
+describe('Commit signature', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = mountExtended(SignatureBadge, {
+ propsData: {
+ signature: {
+ ...props,
+ },
+ stubs: {
+ GlBadge,
+ GlLink,
+ X509CertificateDetails,
+ GlPopover: stubComponent(GlPopover, { template: RENDER_ALL_SLOTS_TEMPLATE }),
+ },
+ },
+ });
+ };
+
+ const signatureBadge = () => wrapper.findComponent(GlBadge);
+ const signaturePopover = () => wrapper.findComponent(GlPopover);
+ const signatureDescription = () => wrapper.findByTestId('signature-description');
+ const signatureKeyLabel = () => wrapper.findByTestId('signature-key-label');
+ const signatureKey = () => wrapper.findByTestId('signature-key');
+ const helpLink = () => wrapper.findComponent(GlLink);
+ const X509CertificateDetailsComponents = () => wrapper.findAllComponents(X509CertificateDetails);
+
+ describe.each`
+ signatureType | verificationStatus
+ ${signatureTypes.GPG} | ${verificationStatuses.VERIFIED}
+ ${signatureTypes.GPG} | ${verificationStatuses.UNVERIFIED}
+ ${signatureTypes.GPG} | ${verificationStatuses.UNVERIFIED_KEY}
+ ${signatureTypes.GPG} | ${verificationStatuses.UNKNOWN_KEY}
+ ${signatureTypes.GPG} | ${verificationStatuses.OTHER_USER}
+ ${signatureTypes.GPG} | ${verificationStatuses.SAME_USER_DIFFERENT_EMAIL}
+ ${signatureTypes.GPG} | ${verificationStatuses.MULTIPLE_SIGNATURES}
+ ${signatureTypes.X509} | ${verificationStatuses.VERIFIED}
+ ${signatureTypes.SSH} | ${verificationStatuses.VERIFIED}
+ ${signatureTypes.SSH} | ${verificationStatuses.REVOKED_KEY}
+ `(
+ 'For a specified `$signatureType` and `$verificationStatus` it renders component correctly',
+ ({ signatureType, verificationStatus }) => {
+ beforeEach(() => {
+ createComponent({ __typename: signatureType, verificationStatus });
+ });
+ it('renders correct badge class', () => {
+ expect(signatureBadge().props('variant')).toBe(statusConfig[verificationStatus].variant);
+ });
+ it('renders badge text', () => {
+ expect(signatureBadge().text()).toBe(statusConfig[verificationStatus].label);
+ });
+ it('renders popover header text', () => {
+ expect(signaturePopover().text()).toMatch(statusConfig[verificationStatus].title);
+ });
+ it('renders signature description', () => {
+ expect(signatureDescription().text()).toBe(statusConfig[verificationStatus].description);
+ });
+ it('renders help link with correct path', () => {
+ expect(helpLink().text()).toBe(typeConfig[signatureType].helpLink.label);
+ expect(helpLink().attributes('href')).toBe(
+ helpPagePath(typeConfig[signatureType].helpLink.path),
+ );
+ });
+ },
+ );
+
+ describe('SSH signature', () => {
+ beforeEach(() => {
+ createComponent(sshSignatureProp);
+ });
+
+ it('renders key label', () => {
+ expect(signatureKeyLabel().text()).toMatch(typeConfig[signatureTypes.SSH].keyLabel);
+ });
+
+ it('renders key signature', () => {
+ expect(signatureKey().text()).toBe(sshSignatureProp.keyFingerprintSha256);
+ });
+ });
+
+ describe('GPG signature', () => {
+ beforeEach(() => {
+ createComponent(gpgSignatureProp);
+ });
+
+ it('renders key label', () => {
+ expect(signatureKeyLabel().text()).toMatch(typeConfig[signatureTypes.GPG].keyLabel);
+ });
+
+ it('renders key signature for GGP signature', () => {
+ expect(signatureKey().text()).toBe(gpgSignatureProp.gpgKeyPrimaryKeyid);
+ });
+ });
+
+ describe('X509 signature', () => {
+ beforeEach(() => {
+ createComponent(x509SignatureProp);
+ });
+
+ it('does not render key label', () => {
+ expect(signatureKeyLabel().exists()).toBe(false);
+ });
+
+ it('renders X509 certificate details components', () => {
+ expect(X509CertificateDetailsComponents()).toHaveLength(2);
+ });
+
+ it('passes correct props', () => {
+ expect(X509CertificateDetailsComponents().at(0).props()).toStrictEqual({
+ subject: x509SignatureProp.x509Certificate.subject,
+ title: typeConfig[signatureTypes.X509].subjectTitle,
+ subjectKeyIdentifier: wrapper.vm.getSubjectKeyIdentifierToDisplay(
+ x509SignatureProp.x509Certificate.subjectKeyIdentifier,
+ ),
+ });
+ expect(X509CertificateDetailsComponents().at(1).props()).toStrictEqual({
+ subject: x509SignatureProp.x509Certificate.x509Issuer.subject,
+ title: typeConfig[signatureTypes.X509].issuerTitle,
+ subjectKeyIdentifier: wrapper.vm.getSubjectKeyIdentifierToDisplay(
+ x509SignatureProp.x509Certificate.x509Issuer.subjectKeyIdentifier,
+ ),
+ });
+ });
+ });
+});
diff --git a/spec/frontend/commit/components/x509_certificate_details_spec.js b/spec/frontend/commit/components/x509_certificate_details_spec.js
new file mode 100644
index 00000000000..5d9398b572b
--- /dev/null
+++ b/spec/frontend/commit/components/x509_certificate_details_spec.js
@@ -0,0 +1,36 @@
+import { shallowMount } from '@vue/test-utils';
+import X509CertificateDetails from '~/commit/components/x509_certificate_details.vue';
+import { X509_CERTIFICATE_KEY_IDENTIFIER_TITLE } from '~/commit/constants';
+import { x509CertificateDetailsProp } from '../mock_data';
+
+describe('X509 certificate details', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(X509CertificateDetails, {
+ propsData: x509CertificateDetailsProp,
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ const findTitle = () => wrapper.find('strong');
+ const findSubjectValues = () => wrapper.findAll("[data-testid='subject-value']");
+ const findKeyIdentifier = () => wrapper.find("[data-testid='key-identifier']");
+
+ it('renders a title', () => {
+ expect(findTitle().text()).toBe(x509CertificateDetailsProp.title);
+ });
+
+ it('renders subject values', () => {
+ expect(findSubjectValues()).toHaveLength(3);
+ });
+
+ it('renders key identifier', () => {
+ expect(findKeyIdentifier().text()).toBe(
+ `${X509_CERTIFICATE_KEY_IDENTIFIER_TITLE} ${x509CertificateDetailsProp.subjectKeyIdentifier}`,
+ );
+ });
+});
diff --git a/spec/frontend/commit/mock_data.js b/spec/frontend/commit/mock_data.js
index a13ef9c563e..3b6971d9607 100644
--- a/spec/frontend/commit/mock_data.js
+++ b/spec/frontend/commit/mock_data.js
@@ -201,3 +201,34 @@ export const mockUpstreamQueryResponse = {
},
},
};
+
+export const sshSignatureProp = {
+ __typename: 'SshSignature',
+ verificationStatus: 'VERIFIED',
+ keyFingerprintSha256: 'xxx',
+};
+
+export const gpgSignatureProp = {
+ __typename: 'GpgSignature',
+ verificationStatus: 'VERIFIED',
+ gpgKeyPrimaryKeyid: 'yyy',
+};
+
+export const x509SignatureProp = {
+ __typename: 'X509Signature',
+ verificationStatus: 'VERIFIED',
+ x509Certificate: {
+ subject: 'CN=gitlab@example.org,OU=Example,O=World',
+ subjectKeyIdentifier: 'BC:BC:BC:BC:BC:BC:BC:BC',
+ x509Issuer: {
+ subject: 'CN=PKI,OU=Example,O=World',
+ subjectKeyIdentifier: 'AB:AB:AB:AB:AB:AB:AB:AB:',
+ },
+ },
+};
+
+export const x509CertificateDetailsProp = {
+ title: 'Title',
+ subject: 'CN=gitlab@example.org,OU=Example,O=World',
+ subjectKeyIdentifier: 'BC BC BC BC BC BC BC BC',
+};
diff --git a/spec/frontend/commit/pipelines/pipelines_table_spec.js b/spec/frontend/commit/pipelines/pipelines_table_spec.js
index 4bffb6a0fd3..009ec68ddcf 100644
--- a/spec/frontend/commit/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/commit/pipelines/pipelines_table_spec.js
@@ -4,6 +4,7 @@ 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 { stubComponent } from 'helpers/stub_component';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
import PipelinesTable from '~/commit/pipelines/pipelines_table.vue';
@@ -13,7 +14,7 @@ import {
HTTP_STATUS_OK,
HTTP_STATUS_UNAUTHORIZED,
} from '~/lib/utils/http_status';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { TOAST_MESSAGE } from '~/pipelines/constants';
import axios from '~/lib/utils/axios_utils';
@@ -21,12 +22,13 @@ const $toast = {
show: jest.fn(),
};
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Pipelines table in Commits and Merge requests', () => {
let wrapper;
let pipeline;
let mock;
+ const showMock = jest.fn();
const findRunPipelineBtn = () => wrapper.findByTestId('run_pipeline_button');
const findRunPipelineBtnMobile = () => wrapper.findByTestId('run_pipeline_button_mobile');
@@ -38,7 +40,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
const findModal = () => wrapper.findComponent(GlModal);
const findMrPipelinesDocsLink = () => wrapper.findByTestId('mr-pipelines-docs-link');
- const createComponent = (props = {}) => {
+ const createComponent = ({ props = {} } = {}) => {
wrapper = extendedWrapper(
mount(PipelinesTable, {
propsData: {
@@ -50,6 +52,12 @@ describe('Pipelines table in Commits and Merge requests', () => {
mocks: {
$toast,
},
+ stubs: {
+ GlModal: stubComponent(GlModal, {
+ template: '<div />',
+ methods: { show: showMock },
+ }),
+ },
}),
);
};
@@ -62,11 +70,6 @@ describe('Pipelines table in Commits and Merge requests', () => {
pipeline = pipelines.find((p) => p.user !== null && p.commit !== null);
});
- afterEach(() => {
- wrapper.destroy();
- mock.restore();
- });
-
describe('successful request', () => {
describe('without pipelines', () => {
beforeEach(async () => {
@@ -95,6 +98,35 @@ describe('Pipelines table in Commits and Merge requests', () => {
});
});
+ describe('with pagination', () => {
+ beforeEach(async () => {
+ mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, [pipeline], {
+ 'X-TOTAL': 10,
+ 'X-PER-PAGE': 2,
+ 'X-PAGE': 1,
+ 'X-TOTAL-PAGES': 5,
+ 'X-NEXT-PAGE': 2,
+ 'X-PREV-PAGE': 2,
+ });
+
+ createComponent();
+
+ await waitForPromises();
+ });
+
+ it('should make an API request when using pagination', async () => {
+ expect(mock.history.get).toHaveLength(1);
+ expect(mock.history.get[0].params.page).toBe('1');
+
+ wrapper.find('.next-page-item').trigger('click');
+
+ await waitForPromises();
+
+ expect(mock.history.get).toHaveLength(2);
+ expect(mock.history.get[1].params.page).toBe('2');
+ });
+ });
+
describe('with pipelines', () => {
beforeEach(async () => {
mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, [pipeline], { 'x-total': 10 });
@@ -111,32 +143,6 @@ describe('Pipelines table in Commits and Merge requests', () => {
expect(findErrorEmptyState().exists()).toBe(false);
});
- describe('with pagination', () => {
- it('should make an API request when using pagination', async () => {
- jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {});
-
- // 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({
- store: {
- state: {
- pageInfo: {
- page: 1,
- total: 10,
- perPage: 2,
- nextPage: 2,
- totalPages: 5,
- },
- },
- },
- });
-
- wrapper.find('.next-page-item').trigger('click');
-
- expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ page: '2' });
- });
- });
-
describe('pipeline badge counts', () => {
it('should receive update-pipelines-count event', () => {
const element = document.createElement('div');
@@ -203,16 +209,18 @@ describe('Pipelines table in Commits and Merge requests', () => {
mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, [pipelineCopy]);
createComponent({
- canRunPipeline: true,
- projectId: '5',
- mergeRequestId: 3,
+ props: {
+ canRunPipeline: true,
+ projectId: '5',
+ mergeRequestId: 3,
+ },
});
await waitForPromises();
});
describe('success', () => {
beforeEach(() => {
- jest.spyOn(Api, 'postMergeRequestPipeline').mockReturnValue(Promise.resolve());
+ jest.spyOn(Api, 'postMergeRequestPipeline').mockResolvedValue();
});
it('displays a toast message during pipeline creation', async () => {
await findRunPipelineBtn().trigger('click');
@@ -255,9 +263,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
`('displays permissions error message', async ({ status, message }) => {
const response = { response: { status } };
- jest
- .spyOn(Api, 'postMergeRequestPipeline')
- .mockImplementation(() => Promise.reject(response));
+ jest.spyOn(Api, 'postMergeRequestPipeline').mockRejectedValue(response);
await findRunPipelineBtn().trigger('click');
@@ -281,14 +287,16 @@ describe('Pipelines table in Commits and Merge requests', () => {
mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, [pipelineCopy]);
createComponent({
- projectId: '5',
- mergeRequestId: 3,
- canCreatePipelineInTargetProject: true,
- sourceProjectFullPath: 'test/parent-project',
- targetProjectFullPath: 'test/fork-project',
+ props: {
+ projectId: '5',
+ mergeRequestId: 3,
+ canCreatePipelineInTargetProject: true,
+ sourceProjectFullPath: 'test/parent-project',
+ targetProjectFullPath: 'test/fork-project',
+ },
});
- jest.spyOn(Api, 'postMergeRequestPipeline').mockReturnValue(Promise.resolve());
+ jest.spyOn(Api, 'postMergeRequestPipeline').mockResolvedValue();
await waitForPromises();
});
@@ -313,15 +321,15 @@ describe('Pipelines table in Commits and Merge requests', () => {
mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, []);
createComponent({
- projectId: '5',
- mergeRequestId: 3,
- canCreatePipelineInTargetProject: true,
- sourceProjectFullPath: 'test/parent-project',
- targetProjectFullPath: 'test/fork-project',
+ props: {
+ projectId: '5',
+ mergeRequestId: 3,
+ canCreatePipelineInTargetProject: true,
+ sourceProjectFullPath: 'test/parent-project',
+ targetProjectFullPath: 'test/fork-project',
+ },
});
- jest.spyOn(findModal().vm, 'show').mockReturnValue();
-
await waitForPromises();
});
@@ -331,7 +339,7 @@ describe('Pipelines table in Commits and Merge requests', () => {
findRunPipelineBtn().trigger('click');
- expect(findModal().vm.show).toHaveBeenCalled();
+ expect(showMock).toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/confidential_merge_request/components/project_form_group_spec.js b/spec/frontend/confidential_merge_request/components/project_form_group_spec.js
index d6f16f1a644..a7ae07a36d9 100644
--- a/spec/frontend/confidential_merge_request/components/project_form_group_spec.js
+++ b/spec/frontend/confidential_merge_request/components/project_form_group_spec.js
@@ -46,7 +46,6 @@ function factory(projects = mockData) {
describe('Confidential merge request project form group component', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
it('renders fork dropdown', async () => {
diff --git a/spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap b/spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap
index a63cca006da..b8e6bcbc3c4 100644
--- a/spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap
+++ b/spec/frontend/content_editor/components/__snapshots__/toolbar_button_spec.js.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`content_editor/components/toolbar_button displays tertiary, medium button with a provided label and icon 1`] = `
-"<b-button-stub size=\\"md\\" variant=\\"default\\" type=\\"button\\" tag=\\"button\\" aria-label=\\"Bold\\" title=\\"Bold\\" class=\\"gl-button btn-default-tertiary btn-icon\\">
+"<b-button-stub size=\\"md\\" tag=\\"button\\" type=\\"button\\" variant=\\"default\\" aria-label=\\"Bold\\" title=\\"Bold\\" class=\\"gl-button btn-default-tertiary btn-icon\\">
<!---->
<gl-icon-stub name=\\"bold\\" size=\\"16\\" class=\\"gl-button-icon\\"></gl-icon-stub>
<!---->
diff --git a/spec/frontend/content_editor/components/bubble_menus/bubble_menu_spec.js b/spec/frontend/content_editor/components/bubble_menus/bubble_menu_spec.js
index 0700cf5d529..271e63abf21 100644
--- a/spec/frontend/content_editor/components/bubble_menus/bubble_menu_spec.js
+++ b/spec/frontend/content_editor/components/bubble_menus/bubble_menu_spec.js
@@ -51,10 +51,6 @@ describe('content_editor/components/bubble_menus/bubble_menu', () => {
setupMocks();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('initializes BubbleMenuPlugin', async () => {
createWrapper({});
diff --git a/spec/frontend/content_editor/components/bubble_menus/code_block_bubble_menu_spec.js b/spec/frontend/content_editor/components/bubble_menus/code_block_bubble_menu_spec.js
index 378b11f4ae9..085a6d3a28d 100644
--- a/spec/frontend/content_editor/components/bubble_menus/code_block_bubble_menu_spec.js
+++ b/spec/frontend/content_editor/components/bubble_menus/code_block_bubble_menu_spec.js
@@ -64,10 +64,6 @@ describe('content_editor/components/bubble_menus/code_block_bubble_menu', () =>
buildWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders bubble menu component', async () => {
tiptapEditor.commands.insertContent(preTag());
bubbleMenu = wrapper.findComponent(BubbleMenu);
diff --git a/spec/frontend/content_editor/components/bubble_menus/formatting_bubble_menu_spec.js b/spec/frontend/content_editor/components/bubble_menus/formatting_bubble_menu_spec.js
index 98001858851..7bab473529f 100644
--- a/spec/frontend/content_editor/components/bubble_menus/formatting_bubble_menu_spec.js
+++ b/spec/frontend/content_editor/components/bubble_menus/formatting_bubble_menu_spec.js
@@ -37,10 +37,6 @@ describe('content_editor/components/bubble_menus/formatting_bubble_menu', () =>
buildEditor();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders bubble menu component', () => {
buildWrapper();
const bubbleMenu = wrapper.findComponent(BubbleMenu);
diff --git a/spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js b/spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js
index 9aa9c6483f4..eb5a3b61591 100644
--- a/spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js
+++ b/spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js
@@ -71,10 +71,6 @@ describe('content_editor/components/bubble_menus/link_bubble_menu', () => {
.run();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders bubble menu component', async () => {
await buildWrapperAndDisplayMenu();
diff --git a/spec/frontend/content_editor/components/bubble_menus/media_bubble_menu_spec.js b/spec/frontend/content_editor/components/bubble_menus/media_bubble_menu_spec.js
index 13c6495ac41..c918f068c07 100644
--- a/spec/frontend/content_editor/components/bubble_menus/media_bubble_menu_spec.js
+++ b/spec/frontend/content_editor/components/bubble_menus/media_bubble_menu_spec.js
@@ -4,22 +4,28 @@ import BubbleMenu from '~/content_editor/components/bubble_menus/bubble_menu.vue
import MediaBubbleMenu from '~/content_editor/components/bubble_menus/media_bubble_menu.vue';
import { stubComponent } from 'helpers/stub_component';
import eventHubFactory from '~/helpers/event_hub_factory';
-import Image from '~/content_editor/extensions/image';
import Audio from '~/content_editor/extensions/audio';
+import DrawioDiagram from '~/content_editor/extensions/drawio_diagram';
+import Image from '~/content_editor/extensions/image';
import Video from '~/content_editor/extensions/video';
import { createTestEditor, emitEditorEvent, mockChainedCommands } from '../../test_utils';
import {
PROJECT_WIKI_ATTACHMENT_IMAGE_HTML,
PROJECT_WIKI_ATTACHMENT_AUDIO_HTML,
PROJECT_WIKI_ATTACHMENT_VIDEO_HTML,
+ PROJECT_WIKI_ATTACHMENT_DRAWIO_DIAGRAM_HTML,
} from '../../test_constants';
-const TIPTAP_IMAGE_HTML = `<p>
+const TIPTAP_AUDIO_HTML = `<p>
+ <span class="media-container audio-container"><audio src="https://gitlab.com/favicon.png" controls="true" data-setup="{}" data-title="gitlab favicon"></audio><a href="https://gitlab.com/favicon.png">gitlab favicon</a></span>
+</p>`;
+
+const TIPTAP_DIAGRAM_HTML = `<p>
<img src="https://gitlab.com/favicon.png" alt="gitlab favicon" title="gitlab favicon">
</p>`;
-const TIPTAP_AUDIO_HTML = `<p>
- <span class="media-container audio-container"><audio src="https://gitlab.com/favicon.png" controls="true" data-setup="{}" data-title="gitlab favicon"></audio><a href="https://gitlab.com/favicon.png">gitlab favicon</a></span>
+const TIPTAP_IMAGE_HTML = `<p>
+ <img src="https://gitlab.com/favicon.png" alt="gitlab favicon" title="gitlab favicon">
</p>`;
const TIPTAP_VIDEO_HTML = `<p>
@@ -29,10 +35,11 @@ const TIPTAP_VIDEO_HTML = `<p>
const createFakeEvent = () => ({ preventDefault: jest.fn(), stopPropagation: jest.fn() });
describe.each`
- mediaType | mediaHTML | filePath | mediaOutputHTML
- ${'image'} | ${PROJECT_WIKI_ATTACHMENT_IMAGE_HTML} | ${'test-file.png'} | ${TIPTAP_IMAGE_HTML}
- ${'audio'} | ${PROJECT_WIKI_ATTACHMENT_AUDIO_HTML} | ${'test-file.mp3'} | ${TIPTAP_AUDIO_HTML}
- ${'video'} | ${PROJECT_WIKI_ATTACHMENT_VIDEO_HTML} | ${'test-file.mp4'} | ${TIPTAP_VIDEO_HTML}
+ mediaType | mediaHTML | filePath | mediaOutputHTML
+ ${'image'} | ${PROJECT_WIKI_ATTACHMENT_IMAGE_HTML} | ${'test-file.png'} | ${TIPTAP_IMAGE_HTML}
+ ${'drawio_diagram'} | ${PROJECT_WIKI_ATTACHMENT_DRAWIO_DIAGRAM_HTML} | ${'test-file.drawio.svg'} | ${TIPTAP_DIAGRAM_HTML}
+ ${'audio'} | ${PROJECT_WIKI_ATTACHMENT_AUDIO_HTML} | ${'test-file.mp3'} | ${TIPTAP_AUDIO_HTML}
+ ${'video'} | ${PROJECT_WIKI_ATTACHMENT_VIDEO_HTML} | ${'test-file.mp4'} | ${TIPTAP_VIDEO_HTML}
`(
'content_editor/components/bubble_menus/media_bubble_menu ($mediaType)',
({ mediaType, mediaHTML, filePath, mediaOutputHTML }) => {
@@ -43,7 +50,7 @@ describe.each`
let eventHub;
const buildEditor = () => {
- tiptapEditor = createTestEditor({ extensions: [Image, Audio, Video] });
+ tiptapEditor = createTestEditor({ extensions: [Image, Audio, Video, DrawioDiagram] });
contentEditor = { resolveUrl: jest.fn() };
eventHub = eventHubFactory();
};
@@ -93,10 +100,6 @@ describe.each`
bubbleMenu = wrapper.findComponent(BubbleMenu);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders bubble menu component', async () => {
expect(bubbleMenu.classes()).toEqual(['gl-shadow', 'gl-rounded-base', 'gl-bg-white']);
});
@@ -114,6 +117,24 @@ describe.each`
expect(link.text()).toBe(filePath);
});
+ describe('when BubbleMenu emits hidden event', () => {
+ it('resets media bubble menu state', async () => {
+ // Switch to edit mode to access component state in form fields
+ await wrapper.findByTestId('edit-media').vm.$emit('click');
+
+ const mediaSrcInput = wrapper.findByTestId('media-src').vm.$el;
+ const mediaAltInput = wrapper.findByTestId('media-alt').vm.$el;
+
+ expect(mediaSrcInput.value).not.toBe('');
+ expect(mediaAltInput.value).not.toBe('');
+
+ await wrapper.findComponent(BubbleMenu).vm.$emit('hidden');
+
+ expect(mediaSrcInput.value).toBe('');
+ expect(mediaAltInput.value).toBe('');
+ });
+ });
+
describe('copy button', () => {
it(`copies the canonical link to the ${mediaType} to clipboard`, async () => {
jest.spyOn(navigator.clipboard, 'writeText');
@@ -133,23 +154,39 @@ describe.each`
});
describe(`replace ${mediaType} button`, () => {
- it('uploads and replaces the selected image when file input changes', async () => {
- const commands = mockChainedCommands(tiptapEditor, [
- 'focus',
- 'deleteSelection',
- 'uploadAttachment',
- 'run',
- ]);
- const file = new File(['foo'], 'foo.png', { type: 'image/png' });
-
- await wrapper.findByTestId('replace-media').vm.$emit('click');
- await selectFile(file);
-
- expect(commands.focus).toHaveBeenCalled();
- expect(commands.deleteSelection).toHaveBeenCalled();
- expect(commands.uploadAttachment).toHaveBeenCalledWith({ file });
- expect(commands.run).toHaveBeenCalled();
- });
+ if (mediaType !== 'drawio_diagram') {
+ it('uploads and replaces the selected image when file input changes', async () => {
+ const commands = mockChainedCommands(tiptapEditor, [
+ 'focus',
+ 'deleteSelection',
+ 'uploadAttachment',
+ 'run',
+ ]);
+ const file = new File(['foo'], 'foo.png', { type: 'image/png' });
+
+ await wrapper.findByTestId('replace-media').vm.$emit('click');
+ await selectFile(file);
+
+ expect(commands.focus).toHaveBeenCalled();
+ expect(commands.deleteSelection).toHaveBeenCalled();
+ expect(commands.uploadAttachment).toHaveBeenCalledWith({ file });
+ expect(commands.run).toHaveBeenCalled();
+ });
+ } else {
+ // draw.io diagrams are replaced using the edit diagram button
+ it('invokes editDiagram command', async () => {
+ const commands = mockChainedCommands(tiptapEditor, [
+ 'focus',
+ 'createOrEditDiagram',
+ 'run',
+ ]);
+ await wrapper.findByTestId('edit-diagram').vm.$emit('click');
+
+ expect(commands.focus).toHaveBeenCalled();
+ expect(commands.createOrEditDiagram).toHaveBeenCalled();
+ expect(commands.run).toHaveBeenCalled();
+ });
+ }
});
describe('edit button', () => {
diff --git a/spec/frontend/content_editor/components/content_editor_alert_spec.js b/spec/frontend/content_editor/components/content_editor_alert_spec.js
index ee9ead8f8a7..e62e2331d25 100644
--- a/spec/frontend/content_editor/components/content_editor_alert_spec.js
+++ b/spec/frontend/content_editor/components/content_editor_alert_spec.js
@@ -29,10 +29,6 @@ describe('content_editor/components/content_editor_alert', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
variant | message
${'danger'} | ${'An error occurred'}
diff --git a/spec/frontend/content_editor/components/content_editor_spec.js b/spec/frontend/content_editor/components/content_editor_spec.js
index 1a3cd36a8bb..b642ac9c46b 100644
--- a/spec/frontend/content_editor/components/content_editor_spec.js
+++ b/spec/frontend/content_editor/components/content_editor_spec.js
@@ -1,7 +1,8 @@
-import { GlAlert } from '@gitlab/ui';
+import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { EditorContent, Editor } from '@tiptap/vue-2';
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import EditorModeDropdown from '~/vue_shared/components/markdown/editor_mode_dropdown.vue';
import ContentEditor from '~/content_editor/components/content_editor.vue';
import ContentEditorAlert from '~/content_editor/components/content_editor_alert.vue';
import ContentEditorProvider from '~/content_editor/components/content_editor_provider.vue';
@@ -27,19 +28,23 @@ describe('ContentEditor', () => {
const findEditorStateObserver = () => wrapper.findComponent(EditorStateObserver);
const findLoadingIndicator = () => wrapper.findComponent(LoadingIndicator);
const findContentEditorAlert = () => wrapper.findComponent(ContentEditorAlert);
- const createWrapper = ({ markdown, autofocus, useBottomToolbar } = {}) => {
+ const createWrapper = ({ markdown, autofocus, ...props } = {}) => {
wrapper = shallowMountExtended(ContentEditor, {
propsData: {
renderMarkdown,
uploadsPath,
markdown,
autofocus,
- useBottomToolbar,
+ placeholder: 'Enter some text here...',
+ ...props,
},
stubs: {
EditorStateObserver,
ContentEditorProvider,
ContentEditorAlert,
+ GlLink,
+ GlSprintf,
+ EditorModeDropdown,
},
});
};
@@ -48,10 +53,6 @@ describe('ContentEditor', () => {
renderMarkdown = jest.fn();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('triggers initialized event', () => {
createWrapper();
@@ -87,22 +88,29 @@ describe('ContentEditor', () => {
expect(wrapper.findComponent(ContentEditorProvider).exists()).toBe(true);
});
- it('renders top toolbar component', () => {
+ it('renders toolbar component', () => {
createWrapper();
expect(wrapper.findComponent(FormattingToolbar).exists()).toBe(true);
- expect(wrapper.findComponent(FormattingToolbar).classes('gl-border-t')).toBe(false);
- expect(wrapper.findComponent(FormattingToolbar).classes('gl-border-b')).toBe(true);
});
- it('renders bottom toolbar component', () => {
- createWrapper({
- useBottomToolbar: true,
- });
+ it('renders footer containing quick actions help text if quick actions docs path is defined', () => {
+ createWrapper({ quickActionsDocsPath: '/foo/bar' });
- expect(wrapper.findComponent(FormattingToolbar).exists()).toBe(true);
- expect(wrapper.findComponent(FormattingToolbar).classes('gl-border-t')).toBe(true);
- expect(wrapper.findComponent(FormattingToolbar).classes('gl-border-b')).toBe(false);
+ expect(findEditorElement().text()).toContain('For quick actions, type /');
+ expect(wrapper.findComponent(GlLink).attributes('href')).toBe('/foo/bar');
+ });
+
+ it('does not render footer containing quick actions help text if quick actions docs path is not defined', () => {
+ createWrapper();
+
+ expect(findEditorElement().text()).not.toContain('For quick actions, type /');
+ });
+
+ it('renders an editor mode dropdown', () => {
+ createWrapper();
+
+ expect(wrapper.findComponent(EditorModeDropdown).exists()).toBe(true);
});
describe('when setting initial content', () => {
@@ -124,9 +132,9 @@ describe('ContentEditor', () => {
describe('succeeds', () => {
beforeEach(async () => {
- renderMarkdown.mockResolvedValueOnce('hello world');
+ renderMarkdown.mockResolvedValueOnce('');
- createWrapper({ markddown: 'hello world' });
+ createWrapper({ markddown: '' });
await nextTick();
});
@@ -138,13 +146,17 @@ describe('ContentEditor', () => {
it('emits loadingSuccess event', () => {
expect(wrapper.emitted('loadingSuccess')).toHaveLength(1);
});
+
+ it('shows placeholder text', () => {
+ expect(wrapper.text()).toContain('Enter some text here...');
+ });
});
describe('fails', () => {
beforeEach(async () => {
renderMarkdown.mockRejectedValueOnce(new Error());
- createWrapper({ markddown: 'hello world' });
+ createWrapper({ markdown: 'hello world' });
await nextTick();
});
@@ -209,11 +221,17 @@ describe('ContentEditor', () => {
expect(findEditorElement().classes()).not.toContain('is-focused');
});
+
+ it('hides placeholder text', () => {
+ expect(wrapper.text()).not.toContain('Enter some text here...');
+ });
});
describe('when editorStateObserver emits docUpdate event', () => {
- it('emits change event with the latest markdown', async () => {
- const markdown = 'Loaded content';
+ let markdown;
+
+ beforeEach(async () => {
+ markdown = 'Loaded content';
renderMarkdown.mockResolvedValueOnce(markdown);
@@ -223,7 +241,9 @@ describe('ContentEditor', () => {
await waitForPromises();
findEditorStateObserver().vm.$emit('docUpdate');
+ });
+ it('emits change event with the latest markdown', () => {
expect(wrapper.emitted('change')).toEqual([
[
{
@@ -234,6 +254,10 @@ describe('ContentEditor', () => {
],
]);
});
+
+ it('hides the placeholder text', () => {
+ expect(wrapper.text()).not.toContain('Enter some text here...');
+ });
});
describe('when editorStateObserver emits keydown event', () => {
diff --git a/spec/frontend/content_editor/components/editor_state_observer_spec.js b/spec/frontend/content_editor/components/editor_state_observer_spec.js
index 9b42f61c98c..80fb20e5258 100644
--- a/spec/frontend/content_editor/components/editor_state_observer_spec.js
+++ b/spec/frontend/content_editor/components/editor_state_observer_spec.js
@@ -45,10 +45,6 @@ describe('content_editor/components/editor_state_observer', () => {
buildEditor();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when editor content changes', () => {
it('emits update, selectionUpdate, and transaction events', () => {
const content = '<p>My paragraph</p>';
diff --git a/spec/frontend/content_editor/components/formatting_toolbar_spec.js b/spec/frontend/content_editor/components/formatting_toolbar_spec.js
index c4bf21ba813..4a7b7cedf19 100644
--- a/spec/frontend/content_editor/components/formatting_toolbar_spec.js
+++ b/spec/frontend/content_editor/components/formatting_toolbar_spec.js
@@ -1,3 +1,4 @@
+import { GlTabs, GlTab } from '@gitlab/ui';
import { mockTracking } from 'helpers/tracking_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import FormattingToolbar from '~/content_editor/components/formatting_toolbar.vue';
@@ -6,22 +7,23 @@ import {
CONTENT_EDITOR_TRACKING_LABEL,
} from '~/content_editor/constants';
-describe('content_editor/components/top_toolbar', () => {
+describe('content_editor/components/formatting_toolbar', () => {
let wrapper;
let trackingSpy;
const buildWrapper = () => {
- wrapper = shallowMountExtended(FormattingToolbar);
+ wrapper = shallowMountExtended(FormattingToolbar, {
+ stubs: {
+ GlTabs,
+ GlTab,
+ },
+ });
};
beforeEach(() => {
trackingSpy = mockTracking(undefined, null, jest.spyOn);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe.each`
testId | controlProps
${'text-styles'} | ${{}}
diff --git a/spec/frontend/content_editor/components/loading_indicator_spec.js b/spec/frontend/content_editor/components/loading_indicator_spec.js
index 0065103d01b..1b0ffaee6c6 100644
--- a/spec/frontend/content_editor/components/loading_indicator_spec.js
+++ b/spec/frontend/content_editor/components/loading_indicator_spec.js
@@ -11,10 +11,6 @@ describe('content_editor/components/loading_indicator', () => {
wrapper = shallowMountExtended(LoadingIndicator);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when loading content', () => {
beforeEach(() => {
createWrapper();
diff --git a/spec/frontend/content_editor/components/toolbar_button_spec.js b/spec/frontend/content_editor/components/toolbar_button_spec.js
index 1f1f7b338c6..1556f761682 100644
--- a/spec/frontend/content_editor/components/toolbar_button_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_button_spec.js
@@ -42,10 +42,6 @@ describe('content_editor/components/toolbar_button', () => {
buildEditor();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays tertiary, medium button with a provided label and icon', () => {
buildWrapper();
diff --git a/spec/frontend/content_editor/components/toolbar_image_button_spec.js b/spec/frontend/content_editor/components/toolbar_image_button_spec.js
index 5473d43f5a1..0ec950137fc 100644
--- a/spec/frontend/content_editor/components/toolbar_image_button_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_image_button_spec.js
@@ -50,7 +50,6 @@ describe('content_editor/components/toolbar_image_button', () => {
afterEach(() => {
editor.destroy();
- wrapper.destroy();
});
it('sets the image to the value in the URL input when "Insert" button is clicked', async () => {
diff --git a/spec/frontend/content_editor/components/toolbar_link_button_spec.js b/spec/frontend/content_editor/components/toolbar_link_button_spec.js
index 40e859e96af..80090c0278f 100644
--- a/spec/frontend/content_editor/components/toolbar_link_button_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_link_button_spec.js
@@ -43,7 +43,6 @@ describe('content_editor/components/toolbar_link_button', () => {
afterEach(() => {
editor.destroy();
- wrapper.destroy();
});
it('renders dropdown component', () => {
diff --git a/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js b/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js
index d4fc47601cf..5af4784f358 100644
--- a/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_more_dropdown_spec.js
@@ -9,12 +9,14 @@ import { createTestEditor, mockChainedCommands, emitEditorEvent } from '../test_
describe('content_editor/components/toolbar_more_dropdown', () => {
let wrapper;
let tiptapEditor;
+ let contentEditor;
let eventHub;
const buildEditor = () => {
tiptapEditor = createTestEditor({
extensions: [Diagram, HorizontalRule],
});
+ contentEditor = { drawioEnabled: true };
eventHub = eventHubFactory();
};
@@ -22,6 +24,7 @@ describe('content_editor/components/toolbar_more_dropdown', () => {
wrapper = mountExtended(ToolbarMoreDropdown, {
provide: {
tiptapEditor,
+ contentEditor,
eventHub,
},
propsData,
@@ -32,29 +35,27 @@ describe('content_editor/components/toolbar_more_dropdown', () => {
beforeEach(() => {
buildEditor();
- buildWrapper();
- });
-
- afterEach(() => {
- wrapper.destroy();
});
describe.each`
- name | contentType | command | params
- ${'Code block'} | ${'codeBlock'} | ${'setNode'} | ${['codeBlock']}
- ${'Details block'} | ${'details'} | ${'toggleList'} | ${['details', 'detailsContent']}
- ${'Bullet list'} | ${'bulletList'} | ${'toggleList'} | ${['bulletList', 'listItem']}
- ${'Ordered list'} | ${'orderedList'} | ${'toggleList'} | ${['orderedList', 'listItem']}
- ${'Task list'} | ${'taskList'} | ${'toggleList'} | ${['taskList', 'taskItem']}
- ${'Mermaid diagram'} | ${'diagram'} | ${'setNode'} | ${['diagram', { language: 'mermaid' }]}
- ${'PlantUML diagram'} | ${'diagram'} | ${'setNode'} | ${['diagram', { language: 'plantuml' }]}
- ${'Table of contents'} | ${'tableOfContents'} | ${'insertTableOfContents'} | ${[]}
- ${'Horizontal rule'} | ${'horizontalRule'} | ${'setHorizontalRule'} | ${[]}
+ name | contentType | command | params
+ ${'Code block'} | ${'codeBlock'} | ${'setNode'} | ${['codeBlock']}
+ ${'Details block'} | ${'details'} | ${'toggleList'} | ${['details', 'detailsContent']}
+ ${'Bullet list'} | ${'bulletList'} | ${'toggleList'} | ${['bulletList', 'listItem']}
+ ${'Ordered list'} | ${'orderedList'} | ${'toggleList'} | ${['orderedList', 'listItem']}
+ ${'Task list'} | ${'taskList'} | ${'toggleList'} | ${['taskList', 'taskItem']}
+ ${'Mermaid diagram'} | ${'diagram'} | ${'setNode'} | ${['diagram', { language: 'mermaid' }]}
+ ${'PlantUML diagram'} | ${'diagram'} | ${'setNode'} | ${['diagram', { language: 'plantuml' }]}
+ ${'Table of contents'} | ${'tableOfContents'} | ${'insertTableOfContents'} | ${[]}
+ ${'Horizontal rule'} | ${'horizontalRule'} | ${'setHorizontalRule'} | ${[]}
+ ${'Create or edit diagram'} | ${'drawioDiagram'} | ${'createOrEditDiagram'} | ${[]}
`('when option $name is clicked', ({ name, command, contentType, params }) => {
let commands;
let btn;
beforeEach(async () => {
+ buildWrapper();
+
commands = mockChainedCommands(tiptapEditor, [command, 'focus', 'run']);
btn = wrapper.findByRole('button', { name });
});
@@ -71,8 +72,17 @@ describe('content_editor/components/toolbar_more_dropdown', () => {
});
});
+ it('does not show drawio option when drawio is disabled', () => {
+ contentEditor.drawioEnabled = false;
+ buildWrapper();
+
+ expect(wrapper.findByRole('button', { name: 'Create or edit diagram' }).exists()).toBe(false);
+ });
+
describe('a11y tests', () => {
it('sets toggleText and text-sr-only properties to the table button dropdown', () => {
+ buildWrapper();
+
expect(findDropdown().props()).toMatchObject({
textSrOnly: true,
toggleText: 'More options',
diff --git a/spec/frontend/content_editor/components/toolbar_table_button_spec.js b/spec/frontend/content_editor/components/toolbar_table_button_spec.js
index aa4604661e5..35741971488 100644
--- a/spec/frontend/content_editor/components/toolbar_table_button_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_table_button_spec.js
@@ -30,7 +30,6 @@ describe('content_editor/components/toolbar_table_button', () => {
afterEach(() => {
editor.destroy();
- wrapper.destroy();
});
it('renders a grid of 5x5 buttons to create a table', () => {
diff --git a/spec/frontend/content_editor/components/toolbar_text_style_dropdown_spec.js b/spec/frontend/content_editor/components/toolbar_text_style_dropdown_spec.js
index 5a725ac1ca4..31ed13541e6 100644
--- a/spec/frontend/content_editor/components/toolbar_text_style_dropdown_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_text_style_dropdown_spec.js
@@ -39,10 +39,6 @@ describe('content_editor/components/toolbar_text_style_dropdown', () => {
buildEditor();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders all text styles as dropdown items', () => {
buildWrapper();
diff --git a/spec/frontend/content_editor/components/wrappers/code_block_spec.js b/spec/frontend/content_editor/components/wrappers/code_block_spec.js
index a5ef19fb8e8..057e50cd0e2 100644
--- a/spec/frontend/content_editor/components/wrappers/code_block_spec.js
+++ b/spec/frontend/content_editor/components/wrappers/code_block_spec.js
@@ -55,10 +55,6 @@ describe('content/components/wrappers/code_block', () => {
codeBlockLanguageLoader.findOrCreateLanguageBySyntax.mockReturnValue({ syntax: language });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a node-view-wrapper as a pre element', () => {
createWrapper();
diff --git a/spec/frontend/content_editor/components/wrappers/details_spec.js b/spec/frontend/content_editor/components/wrappers/details_spec.js
index d746b9fa2f1..232c1e9aede 100644
--- a/spec/frontend/content_editor/components/wrappers/details_spec.js
+++ b/spec/frontend/content_editor/components/wrappers/details_spec.js
@@ -13,10 +13,6 @@ describe('content/components/wrappers/details', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a node-view-content as a ul element', () => {
createWrapper();
diff --git a/spec/frontend/content_editor/components/wrappers/footnote_definition_spec.js b/spec/frontend/content_editor/components/wrappers/footnote_definition_spec.js
index 1ff750eb2ac..91c6799478e 100644
--- a/spec/frontend/content_editor/components/wrappers/footnote_definition_spec.js
+++ b/spec/frontend/content_editor/components/wrappers/footnote_definition_spec.js
@@ -12,10 +12,6 @@ describe('content/components/wrappers/footnote_definition', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders footnote label as a readyonly element', () => {
const label = 'footnote';
diff --git a/spec/frontend/content_editor/components/wrappers/label_spec.js b/spec/frontend/content_editor/components/wrappers/label_spec.js
index 9e58669b0ea..fa32b746142 100644
--- a/spec/frontend/content_editor/components/wrappers/label_spec.js
+++ b/spec/frontend/content_editor/components/wrappers/label_spec.js
@@ -11,10 +11,6 @@ describe('content/components/wrappers/label', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it("renders a GlLabel with the node's text and color", () => {
createWrapper({ attrs: { color: '#ff0000', text: 'foo bar', originalText: '~"foo bar"' } });
diff --git a/spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js b/spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js
index 1fdddce3962..d8f34565705 100644
--- a/spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js
+++ b/spec/frontend/content_editor/components/wrappers/table_cell_base_spec.js
@@ -1,12 +1,12 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { NodeViewWrapper } from '@tiptap/vue-2';
-import { selectedRect as getSelectedRect } from '@_ueberdosis/prosemirror-tables';
+import { selectedRect as getSelectedRect } from '@tiptap/pm/tables';
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import TableCellBaseWrapper from '~/content_editor/components/wrappers/table_cell_base.vue';
import { createTestEditor, mockChainedCommands, emitEditorEvent } from '../../test_utils';
-jest.mock('@_ueberdosis/prosemirror-tables');
+jest.mock('@tiptap/pm/tables');
describe('content/components/wrappers/table_cell_base', () => {
let wrapper;
@@ -52,10 +52,6 @@ describe('content/components/wrappers/table_cell_base', () => {
editor = createTestEditor({});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a td node-view-wrapper with relative position', () => {
createWrapper();
expect(wrapper.findComponent(NodeViewWrapper).classes()).toContain('gl-relative');
diff --git a/spec/frontend/content_editor/components/wrappers/table_cell_body_spec.js b/spec/frontend/content_editor/components/wrappers/table_cell_body_spec.js
index 2aefbc77545..506f442bcc7 100644
--- a/spec/frontend/content_editor/components/wrappers/table_cell_body_spec.js
+++ b/spec/frontend/content_editor/components/wrappers/table_cell_body_spec.js
@@ -22,10 +22,6 @@ describe('content/components/wrappers/table_cell_body', () => {
editor = createTestEditor({});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a TableCellBase component', () => {
createWrapper();
expect(wrapper.findComponent(TableCellBaseWrapper).props()).toEqual({
diff --git a/spec/frontend/content_editor/components/wrappers/table_cell_header_spec.js b/spec/frontend/content_editor/components/wrappers/table_cell_header_spec.js
index e48df8734a6..bebe7fb4124 100644
--- a/spec/frontend/content_editor/components/wrappers/table_cell_header_spec.js
+++ b/spec/frontend/content_editor/components/wrappers/table_cell_header_spec.js
@@ -22,10 +22,6 @@ describe('content/components/wrappers/table_cell_header', () => {
editor = createTestEditor({});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a TableCellBase component', () => {
createWrapper();
expect(wrapper.findComponent(TableCellBaseWrapper).props()).toEqual({
diff --git a/spec/frontend/content_editor/components/wrappers/table_of_contents_spec.js b/spec/frontend/content_editor/components/wrappers/table_of_contents_spec.js
index bfda89a8b09..4d5911dda0c 100644
--- a/spec/frontend/content_editor/components/wrappers/table_of_contents_spec.js
+++ b/spec/frontend/content_editor/components/wrappers/table_of_contents_spec.js
@@ -70,10 +70,6 @@ describe('content/components/wrappers/table_of_contents', () => {
await nextTick();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a node-view-wrapper as a ul element', () => {
expect(wrapper.findComponent(NodeViewWrapper).props().as).toBe('ul');
});
diff --git a/spec/frontend/content_editor/extensions/attachment_spec.js b/spec/frontend/content_editor/extensions/attachment_spec.js
index 6b804b3b4c6..24b75ba6805 100644
--- a/spec/frontend/content_editor/extensions/attachment_spec.js
+++ b/spec/frontend/content_editor/extensions/attachment_spec.js
@@ -2,12 +2,13 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import Attachment from '~/content_editor/extensions/attachment';
+import DrawioDiagram from '~/content_editor/extensions/drawio_diagram';
import Image from '~/content_editor/extensions/image';
import Audio from '~/content_editor/extensions/audio';
import Video from '~/content_editor/extensions/video';
import Link from '~/content_editor/extensions/link';
import Loading from '~/content_editor/extensions/loading';
-import { VARIANT_DANGER } from '~/flash';
+import { VARIANT_DANGER } from '~/alert';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import eventHubFactory from '~/helpers/event_hub_factory';
import { createTestEditor, createDocBuilder } from '../test_utils';
@@ -16,6 +17,7 @@ import {
PROJECT_WIKI_ATTACHMENT_AUDIO_HTML,
PROJECT_WIKI_ATTACHMENT_VIDEO_HTML,
PROJECT_WIKI_ATTACHMENT_LINK_HTML,
+ PROJECT_WIKI_ATTACHMENT_DRAWIO_DIAGRAM_HTML,
} from '../test_constants';
describe('content_editor/extensions/attachment', () => {
@@ -24,6 +26,7 @@ describe('content_editor/extensions/attachment', () => {
let p;
let image;
let audio;
+ let drawioDiagram;
let video;
let loading;
let link;
@@ -35,6 +38,7 @@ describe('content_editor/extensions/attachment', () => {
const imageFile = new File(['foo'], 'test-file.png', { type: 'image/png' });
const audioFile = new File(['foo'], 'test-file.mp3', { type: 'audio/mpeg' });
const videoFile = new File(['foo'], 'test-file.mp4', { type: 'video/mp4' });
+ const drawioDiagramFile = new File(['foo'], 'test-file.drawio.svg', { type: 'image/svg+xml' });
const attachmentFile = new File(['foo'], 'test-file.zip', { type: 'application/zip' });
const expectDocumentAfterTransaction = ({ number, expectedDoc, action }) => {
@@ -67,12 +71,13 @@ describe('content_editor/extensions/attachment', () => {
Image,
Audio,
Video,
+ DrawioDiagram,
Attachment.configure({ renderMarkdown, uploadsPath, eventHub }),
],
});
({
- builders: { doc, p, image, audio, video, loading, link },
+ builders: { doc, p, image, audio, video, loading, link, drawioDiagram },
} = createDocBuilder({
tiptapEditor,
names: {
@@ -81,6 +86,7 @@ describe('content_editor/extensions/attachment', () => {
link: { nodeType: Link.name },
audio: { nodeType: Audio.name },
video: { nodeType: Video.name },
+ drawioDiagram: { nodeType: DrawioDiagram.name },
},
}));
@@ -113,10 +119,11 @@ describe('content_editor/extensions/attachment', () => {
});
describe.each`
- nodeType | mimeType | html | file | mediaType
- ${'image'} | ${'image/png'} | ${PROJECT_WIKI_ATTACHMENT_IMAGE_HTML} | ${imageFile} | ${(attrs) => image(attrs)}
- ${'audio'} | ${'audio/mpeg'} | ${PROJECT_WIKI_ATTACHMENT_AUDIO_HTML} | ${audioFile} | ${(attrs) => audio(attrs)}
- ${'video'} | ${'video/mp4'} | ${PROJECT_WIKI_ATTACHMENT_VIDEO_HTML} | ${videoFile} | ${(attrs) => video(attrs)}
+ nodeType | mimeType | html | file | mediaType
+ ${'image'} | ${'image/png'} | ${PROJECT_WIKI_ATTACHMENT_IMAGE_HTML} | ${imageFile} | ${(attrs) => image(attrs)}
+ ${'audio'} | ${'audio/mpeg'} | ${PROJECT_WIKI_ATTACHMENT_AUDIO_HTML} | ${audioFile} | ${(attrs) => audio(attrs)}
+ ${'video'} | ${'video/mp4'} | ${PROJECT_WIKI_ATTACHMENT_VIDEO_HTML} | ${videoFile} | ${(attrs) => video(attrs)}
+ ${'drawioDiagram'} | ${'image/svg+xml'} | ${PROJECT_WIKI_ATTACHMENT_DRAWIO_DIAGRAM_HTML} | ${drawioDiagramFile} | ${(attrs) => drawioDiagram(attrs)}
`('when the file has $nodeType mime type', ({ mimeType, html, file, mediaType }) => {
const base64EncodedFile = `data:${mimeType};base64,Zm9v`;
@@ -151,7 +158,7 @@ describe('content_editor/extensions/attachment', () => {
mediaType({
canonicalSrc: file.name,
src: base64EncodedFile,
- alt: 'test-file',
+ alt: expect.stringContaining('test-file'),
uploading: false,
}),
),
diff --git a/spec/frontend/content_editor/extensions/drawio_diagram_spec.js b/spec/frontend/content_editor/extensions/drawio_diagram_spec.js
new file mode 100644
index 00000000000..61dc164c99a
--- /dev/null
+++ b/spec/frontend/content_editor/extensions/drawio_diagram_spec.js
@@ -0,0 +1,103 @@
+import DrawioDiagram from '~/content_editor/extensions/drawio_diagram';
+import Image from '~/content_editor/extensions/image';
+import createAssetResolver from '~/content_editor/services/asset_resolver';
+import { create } from '~/drawio/content_editor_facade';
+import { launchDrawioEditor } from '~/drawio/drawio_editor';
+import { createTestEditor, createDocBuilder } from '../test_utils';
+import {
+ PROJECT_WIKI_ATTACHMENT_IMAGE_HTML,
+ PROJECT_WIKI_ATTACHMENT_DRAWIO_DIAGRAM_HTML,
+} from '../test_constants';
+
+jest.mock('~/content_editor/services/asset_resolver');
+jest.mock('~/drawio/content_editor_facade');
+jest.mock('~/drawio/drawio_editor');
+
+describe('content_editor/extensions/drawio_diagram', () => {
+ let tiptapEditor;
+ let doc;
+ let paragraph;
+ let image;
+ let drawioDiagram;
+ const uploadsPath = '/uploads';
+ const renderMarkdown = () => {};
+
+ beforeEach(() => {
+ tiptapEditor = createTestEditor({
+ extensions: [Image, DrawioDiagram.configure({ uploadsPath, renderMarkdown })],
+ });
+ const { builders } = createDocBuilder({
+ tiptapEditor,
+ names: {
+ image: { nodeType: Image.name },
+ drawioDiagram: { nodeType: DrawioDiagram.name },
+ },
+ });
+
+ doc = builders.doc;
+ paragraph = builders.paragraph;
+ image = builders.image;
+ drawioDiagram = builders.drawioDiagram;
+ });
+
+ describe('parsing', () => {
+ it('distinguishes a drawio diagram from an image', () => {
+ const expectedDocWithDiagram = doc(
+ paragraph(
+ drawioDiagram({
+ alt: 'test-file',
+ canonicalSrc: 'test-file.drawio.svg',
+ src: '/group1/project1/-/wikis/test-file.drawio.svg',
+ }),
+ ),
+ );
+ const expectedDocWithImage = doc(
+ paragraph(
+ image({
+ alt: 'test-file',
+ canonicalSrc: 'test-file.png',
+ src: '/group1/project1/-/wikis/test-file.png',
+ }),
+ ),
+ );
+ tiptapEditor.commands.setContent(PROJECT_WIKI_ATTACHMENT_DRAWIO_DIAGRAM_HTML);
+
+ expect(tiptapEditor.state.doc.toJSON()).toEqual(expectedDocWithDiagram.toJSON());
+
+ tiptapEditor.commands.setContent(PROJECT_WIKI_ATTACHMENT_IMAGE_HTML);
+
+ expect(tiptapEditor.state.doc.toJSON()).toEqual(expectedDocWithImage.toJSON());
+ });
+ });
+
+ describe('createOrEditDiagram command', () => {
+ let editorFacade;
+ let assetResolver;
+
+ beforeEach(() => {
+ editorFacade = {};
+ assetResolver = {};
+ tiptapEditor.commands.createOrEditDiagram();
+
+ create.mockReturnValueOnce(editorFacade);
+ createAssetResolver.mockReturnValueOnce(assetResolver);
+ });
+
+ it('creates a new instance of asset resolver', () => {
+ expect(createAssetResolver).toHaveBeenCalledWith({ renderMarkdown });
+ });
+
+ it('creates a new instance of the content_editor_facade', () => {
+ expect(create).toHaveBeenCalledWith({
+ tiptapEditor,
+ drawioNodeName: DrawioDiagram.name,
+ uploadsPath,
+ assetResolver,
+ });
+ });
+
+ it('calls launchDrawioEditor and provides content_editor_facade', () => {
+ expect(launchDrawioEditor).toHaveBeenCalledWith({ editorFacade });
+ });
+ });
+});
diff --git a/spec/frontend/content_editor/extensions/paste_markdown_spec.js b/spec/frontend/content_editor/extensions/paste_markdown_spec.js
index 30e798e8817..8f3a4934e77 100644
--- a/spec/frontend/content_editor/extensions/paste_markdown_spec.js
+++ b/spec/frontend/content_editor/extensions/paste_markdown_spec.js
@@ -3,7 +3,7 @@ import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight
import Diagram from '~/content_editor/extensions/diagram';
import Frontmatter from '~/content_editor/extensions/frontmatter';
import Bold from '~/content_editor/extensions/bold';
-import { VARIANT_DANGER } from '~/flash';
+import { VARIANT_DANGER } from '~/alert';
import eventHubFactory from '~/helpers/event_hub_factory';
import { ALERT_EVENT } from '~/content_editor/constants';
import waitForPromises from 'helpers/wait_for_promises';
diff --git a/spec/frontend/content_editor/render_html_and_json_for_all_examples.js b/spec/frontend/content_editor/render_html_and_json_for_all_examples.js
index 5df901e0f15..bf29d4bdf23 100644
--- a/spec/frontend/content_editor/render_html_and_json_for_all_examples.js
+++ b/spec/frontend/content_editor/render_html_and_json_for_all_examples.js
@@ -1,4 +1,4 @@
-import { DOMSerializer } from 'prosemirror-model';
+import { DOMSerializer } from '@tiptap/pm/model';
import createMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer';
import { createTiptapEditor } from 'jest/content_editor/test_utils';
diff --git a/spec/frontend/content_editor/services/create_content_editor_spec.js b/spec/frontend/content_editor/services/create_content_editor_spec.js
index e1a30819ac8..00cc628ca72 100644
--- a/spec/frontend/content_editor/services/create_content_editor_spec.js
+++ b/spec/frontend/content_editor/services/create_content_editor_spec.js
@@ -20,7 +20,7 @@ describe('content_editor/services/create_content_editor', () => {
preserveUnchangedMarkdown: false,
},
};
- editor = createContentEditor({ renderMarkdown, uploadsPath });
+ editor = createContentEditor({ renderMarkdown, uploadsPath, drawioEnabled: true });
});
describe('when preserveUnchangedMarkdown feature is on', () => {
@@ -45,10 +45,10 @@ describe('content_editor/services/create_content_editor', () => {
});
});
- it('sets gl-outline-0! class selector to the tiptapEditor instance', () => {
+ it('sets gl-shadow-none! class selector to the tiptapEditor instance', () => {
expect(editor.tiptapEditor.options.editorProps).toMatchObject({
attributes: {
- class: 'gl-outline-0!',
+ class: 'gl-shadow-none!',
},
});
});
@@ -82,4 +82,14 @@ describe('content_editor/services/create_content_editor', () => {
renderMarkdown,
});
});
+
+ it('provides uploadsPath and renderMarkdown function to DrawioDiagram extension', () => {
+ expect(
+ editor.tiptapEditor.extensionManager.extensions.find((e) => e.name === 'drawioDiagram')
+ .options,
+ ).toMatchObject({
+ uploadsPath,
+ renderMarkdown,
+ });
+ });
});
diff --git a/spec/frontend/content_editor/services/gl_api_markdown_deserializer_spec.js b/spec/frontend/content_editor/services/gl_api_markdown_deserializer_spec.js
index 90d83820c70..8ee37282ee9 100644
--- a/spec/frontend/content_editor/services/gl_api_markdown_deserializer_spec.js
+++ b/spec/frontend/content_editor/services/gl_api_markdown_deserializer_spec.js
@@ -35,12 +35,10 @@ describe('content_editor/services/gl_api_markdown_deserializer', () => {
beforeEach(async () => {
const deserializer = createMarkdownDeserializer({ render: renderMarkdown });
- renderMarkdown.mockResolvedValueOnce(
- `<p><strong>${text}</strong></p><pre lang="javascript"></pre><!-- some comment -->`,
- );
+ renderMarkdown.mockResolvedValueOnce(`<p><strong>${text}</strong></p><!-- some comment -->`);
result = await deserializer.deserialize({
- content: 'content',
+ markdown: '**Bold text**\n<!-- some comment -->',
schema: tiptapEditor.schema,
});
});
@@ -53,12 +51,22 @@ describe('content_editor/services/gl_api_markdown_deserializer', () => {
});
describe('when the render function returns an empty value', () => {
- it('returns an empty object', async () => {
- const deserializer = createMarkdownDeserializer({ render: renderMarkdown });
+ it('returns an empty prosemirror document', async () => {
+ const deserializer = createMarkdownDeserializer({
+ render: renderMarkdown,
+ schema: tiptapEditor.schema,
+ });
renderMarkdown.mockResolvedValueOnce(null);
- expect(await deserializer.deserialize({ content: 'content' })).toEqual({});
+ const result = await deserializer.deserialize({
+ markdown: '',
+ schema: tiptapEditor.schema,
+ });
+
+ const document = doc(p());
+
+ expect(result.document.toJSON()).toEqual(document.toJSON());
});
});
});
diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js
index 2cd8b8a0d6f..c4d302547a5 100644
--- a/spec/frontend/content_editor/services/markdown_serializer_spec.js
+++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js
@@ -8,6 +8,7 @@ import DescriptionItem from '~/content_editor/extensions/description_item';
import DescriptionList from '~/content_editor/extensions/description_list';
import Details from '~/content_editor/extensions/details';
import DetailsContent from '~/content_editor/extensions/details_content';
+import DrawioDiagram from '~/content_editor/extensions/drawio_diagram';
import Emoji from '~/content_editor/extensions/emoji';
import Figure from '~/content_editor/extensions/figure';
import FigureCaption from '~/content_editor/extensions/figure_caption';
@@ -57,6 +58,7 @@ const {
div,
descriptionItem,
descriptionList,
+ drawioDiagram,
emoji,
footnoteDefinition,
footnoteReference,
@@ -96,6 +98,7 @@ const {
detailsContent: { nodeType: DetailsContent.name },
descriptionItem: { nodeType: DescriptionItem.name },
descriptionList: { nodeType: DescriptionList.name },
+ drawioDiagram: { nodeType: DrawioDiagram.name },
emoji: { markType: Emoji.name },
figure: { nodeType: Figure.name },
figureCaption: { nodeType: FigureCaption.name },
@@ -397,6 +400,12 @@ this is not really json:table but just trying out whether this case works or not
);
});
+ it('correctly serializes a drawio_diagram', () => {
+ expect(
+ serialize(paragraph(drawioDiagram({ src: 'diagram.drawio.svg', alt: 'Draw.io Diagram' }))),
+ ).toBe('![Draw.io Diagram](diagram.drawio.svg)');
+ });
+
it.each`
width | height | outputAttributes
${300} | ${undefined} | ${'width=300'}
diff --git a/spec/frontend/content_editor/test_constants.js b/spec/frontend/content_editor/test_constants.js
index 45a0e4a8bd1..bd462ecec22 100644
--- a/spec/frontend/content_editor/test_constants.js
+++ b/spec/frontend/content_editor/test_constants.js
@@ -20,6 +20,12 @@ export const PROJECT_WIKI_ATTACHMENT_AUDIO_HTML = `<p data-sourcepos="3:1-3:74"
</span>
</p>`;
+export const PROJECT_WIKI_ATTACHMENT_DRAWIO_DIAGRAM_HTML = `<p data-sourcepos="1:1-1:27" dir="auto">
+ <a class="no-attachment-icon" href="/group1/project1/-/wikis/test-file.drawio.svg" target="_blank" rel="noopener noreferrer" data-canonical-src="test-file.drawio.svg">
+ <img alt="test-file" class="lazy" data-src="/group1/project1/-/wikis/test-file.drawio.svg" data-canonical-src="test-file.drawio.svg">
+ </a>
+</p>`;
+
export const PROJECT_WIKI_ATTACHMENT_LINK_HTML = `<p data-sourcepos="1:1-1:26" dir="auto">
<a href="/group1/project1/-/wikis/test-file.zip" data-canonical-src="test-file.zip">test-file</a>
</p>`;
diff --git a/spec/frontend/content_editor/test_utils.js b/spec/frontend/content_editor/test_utils.js
index 0fa0e65cd26..16f90a15c24 100644
--- a/spec/frontend/content_editor/test_utils.js
+++ b/spec/frontend/content_editor/test_utils.js
@@ -17,6 +17,7 @@ import DescriptionList from '~/content_editor/extensions/description_list';
import Details from '~/content_editor/extensions/details';
import DetailsContent from '~/content_editor/extensions/details_content';
import Diagram from '~/content_editor/extensions/diagram';
+import DrawioDiagram from '~/content_editor/extensions/drawio_diagram';
import Emoji from '~/content_editor/extensions/emoji';
import FootnoteDefinition from '~/content_editor/extensions/footnote_definition';
import FootnoteReference from '~/content_editor/extensions/footnote_reference';
@@ -218,6 +219,7 @@ export const createTiptapEditor = (extensions = []) =>
DescriptionList,
Details,
DetailsContent,
+ DrawioDiagram,
Diagram,
Emoji,
FootnoteDefinition,
diff --git a/spec/frontend/contributors/component/__snapshots__/contributors_spec.js.snap b/spec/frontend/contributors/component/__snapshots__/contributors_spec.js.snap
index 2f441f0f747..4b7439f6fd2 100644
--- a/spec/frontend/contributors/component/__snapshots__/contributors_spec.js.snap
+++ b/spec/frontend/contributors/component/__snapshots__/contributors_spec.js.snap
@@ -53,23 +53,22 @@ exports[`Contributors charts should render charts and a RefSelector when loading
Excluding merge commits. Limited to 6,000 commits.
</span>
- <div>
- <glareachart-stub
- annotations=""
- class="gl-mb-5"
- data="[object Object]"
- height="264"
- includelegendavgmax="true"
- legendaveragetext="Avg"
- legendcurrenttext="Current"
- legendlayout="inline"
- legendmaxtext="Max"
- legendmintext="Min"
- option="[object Object]"
- thresholds=""
- width="0"
- />
- </div>
+ <glareachart-stub
+ annotations=""
+ class="gl-mb-5"
+ data="[object Object]"
+ height="264"
+ includelegendavgmax="true"
+ legendaveragetext="Avg"
+ legendcurrenttext="Current"
+ legendlayout="inline"
+ legendmaxtext="Max"
+ legendmintext="Min"
+ option="[object Object]"
+ responsive=""
+ thresholds=""
+ width="auto"
+ />
<div
class="row"
@@ -91,22 +90,21 @@ exports[`Contributors charts should render charts and a RefSelector when loading
</p>
- <div>
- <glareachart-stub
- annotations=""
- data="[object Object]"
- height="216"
- includelegendavgmax="true"
- legendaveragetext="Avg"
- legendcurrenttext="Current"
- legendlayout="inline"
- legendmaxtext="Max"
- legendmintext="Min"
- option="[object Object]"
- thresholds=""
- width="0"
- />
- </div>
+ <glareachart-stub
+ annotations=""
+ data="[object Object]"
+ height="216"
+ includelegendavgmax="true"
+ legendaveragetext="Avg"
+ legendcurrenttext="Current"
+ legendlayout="inline"
+ legendmaxtext="Max"
+ legendmintext="Min"
+ option="[object Object]"
+ responsive=""
+ thresholds=""
+ width="auto"
+ />
</div>
</div>
</div>
diff --git a/spec/frontend/contributors/component/contributors_spec.js b/spec/frontend/contributors/component/contributors_spec.js
index 03b1e977548..f915b834aff 100644
--- a/spec/frontend/contributors/component/contributors_spec.js
+++ b/spec/frontend/contributors/component/contributors_spec.js
@@ -1,5 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
-import Vue, { nextTick } from 'vue';
+import { nextTick } from 'vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import ContributorsCharts from '~/contributors/components/contributors.vue';
import { createStore } from '~/contributors/stores';
@@ -16,7 +16,6 @@ jest.mock('~/lib/utils/url_utility', () => ({
let wrapper;
let mock;
let store;
-const Component = Vue.extend(ContributorsCharts);
const endpoint = 'contributors/-/graphs';
const branch = 'main';
const chartData = [
@@ -32,7 +31,7 @@ function factory() {
mock.onGet().reply(HTTP_STATUS_OK, chartData);
store = createStore();
- wrapper = mountExtended(Component, {
+ wrapper = mountExtended(ContributorsCharts, {
propsData: {
endpoint,
branch,
@@ -60,7 +59,6 @@ describe('Contributors charts', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
it('should fetch chart data when mounted', () => {
diff --git a/spec/frontend/contributors/store/actions_spec.js b/spec/frontend/contributors/store/actions_spec.js
index b2ebdf2f53c..a15b9ad2978 100644
--- a/spec/frontend/contributors/store/actions_spec.js
+++ b/spec/frontend/contributors/store/actions_spec.js
@@ -2,11 +2,11 @@ import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/contributors/stores/actions';
import * as types from '~/contributors/stores/mutation_types';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
-jest.mock('~/flash.js');
+jest.mock('~/alert');
describe('Contributors store actions', () => {
describe('fetchChartData', () => {
@@ -38,7 +38,7 @@ describe('Contributors store actions', () => {
);
});
- it('should show flash on API error', async () => {
+ it('should show alert on API error', async () => {
mock.onGet().reply(HTTP_STATUS_BAD_REQUEST, 'Not Found');
await testAction(
diff --git a/spec/frontend/crm/contact_form_wrapper_spec.js b/spec/frontend/crm/contact_form_wrapper_spec.js
index 50b432943fb..2fb6940a415 100644
--- a/spec/frontend/crm/contact_form_wrapper_spec.js
+++ b/spec/frontend/crm/contact_form_wrapper_spec.js
@@ -47,7 +47,6 @@ describe('Customer relations contact form wrapper', () => {
});
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
diff --git a/spec/frontend/crm/contacts_root_spec.js b/spec/frontend/crm/contacts_root_spec.js
index ec7172434bf..63b64a6c984 100644
--- a/spec/frontend/crm/contacts_root_spec.js
+++ b/spec/frontend/crm/contacts_root_spec.js
@@ -61,7 +61,6 @@ describe('Customer relations contacts root app', () => {
});
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
router = null;
});
diff --git a/spec/frontend/crm/crm_form_spec.js b/spec/frontend/crm/crm_form_spec.js
index eabcf5b1b1b..fabf43ceb9d 100644
--- a/spec/frontend/crm/crm_form_spec.js
+++ b/spec/frontend/crm/crm_form_spec.js
@@ -188,10 +188,6 @@ describe('Reusable form component', () => {
};
const asTestParams = (...keys) => keys.map((name) => [name, forms[name]]);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe.each(asTestParams(FORM_CREATE_CONTACT, FORM_UPDATE_CONTACT))(
'%s form save button',
(name, { mountFunction }) => {
diff --git a/spec/frontend/crm/organization_form_wrapper_spec.js b/spec/frontend/crm/organization_form_wrapper_spec.js
index d795c585622..8408c1920a9 100644
--- a/spec/frontend/crm/organization_form_wrapper_spec.js
+++ b/spec/frontend/crm/organization_form_wrapper_spec.js
@@ -40,10 +40,6 @@ describe('Customer relations organization form wrapper', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('in edit mode', () => {
it('should render organization form with correct props', () => {
mountComponent({ isEditMode: true });
diff --git a/spec/frontend/crm/organizations_root_spec.js b/spec/frontend/crm/organizations_root_spec.js
index 1fcf6aa8f50..0b26a49a6b3 100644
--- a/spec/frontend/crm/organizations_root_spec.js
+++ b/spec/frontend/crm/organizations_root_spec.js
@@ -65,7 +65,6 @@ describe('Customer relations organizations root app', () => {
});
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
router = null;
});
diff --git a/spec/frontend/custom_metrics/components/custom_metrics_form_fields_spec.js b/spec/frontend/custom_metrics/components/custom_metrics_form_fields_spec.js
index 7d9ae548c9a..12fef9d5ddf 100644
--- a/spec/frontend/custom_metrics/components/custom_metrics_form_fields_spec.js
+++ b/spec/frontend/custom_metrics/components/custom_metrics_form_fields_spec.js
@@ -42,7 +42,6 @@ describe('custom metrics form fields component', () => {
});
afterEach(() => {
- wrapper.destroy();
mockAxios.restore();
});
diff --git a/spec/frontend/custom_metrics/components/custom_metrics_form_spec.js b/spec/frontend/custom_metrics/components/custom_metrics_form_spec.js
index af56b94f90b..c633583f2cb 100644
--- a/spec/frontend/custom_metrics/components/custom_metrics_form_spec.js
+++ b/spec/frontend/custom_metrics/components/custom_metrics_form_spec.js
@@ -26,10 +26,6 @@ describe('CustomMetricsForm', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Computed', () => {
it('Form button and title text indicate the custom metric is being edited', () => {
mountComponent({ metricPersisted: true });
diff --git a/spec/frontend/deploy_freeze/components/deploy_freeze_modal_spec.js b/spec/frontend/deploy_freeze/components/deploy_freeze_modal_spec.js
index 113e0d8f60d..77118ae140a 100644
--- a/spec/frontend/deploy_freeze/components/deploy_freeze_modal_spec.js
+++ b/spec/frontend/deploy_freeze/components/deploy_freeze_modal_spec.js
@@ -46,11 +46,6 @@ describe('Deploy freeze modal', () => {
wrapper.findComponent(TimezoneDropdown).trigger('input');
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('Basic interactions', () => {
it('button is disabled when freeze period is invalid', () => {
expect(submitDeployFreezeButton().attributes('disabled')).toBe('true');
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 27d8fea9d5e..883cc6a344a 100644
--- a/spec/frontend/deploy_freeze/components/deploy_freeze_settings_spec.js
+++ b/spec/frontend/deploy_freeze/components/deploy_freeze_settings_spec.js
@@ -24,11 +24,6 @@ describe('Deploy freeze settings', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('Deploy freeze table contains components', () => {
it('contains deploy freeze table', () => {
expect(wrapper.findComponent(DeployFreezeTable).exists()).toBe(true);
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 c2d6eb399bc..6a9e482a184 100644
--- a/spec/frontend/deploy_freeze/components/deploy_freeze_table_spec.js
+++ b/spec/frontend/deploy_freeze/components/deploy_freeze_table_spec.js
@@ -37,11 +37,6 @@ describe('Deploy freeze table', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('dispatches fetchFreezePeriods when mounted', () => {
expect(store.dispatch).toHaveBeenCalledWith('fetchFreezePeriods');
});
diff --git a/spec/frontend/deploy_freeze/store/actions_spec.js b/spec/frontend/deploy_freeze/store/actions_spec.js
index 9b96ce5d252..d39577baa59 100644
--- a/spec/frontend/deploy_freeze/store/actions_spec.js
+++ b/spec/frontend/deploy_freeze/store/actions_spec.js
@@ -4,14 +4,14 @@ import Api from '~/api';
import * as actions from '~/deploy_freeze/store/actions';
import * as types from '~/deploy_freeze/store/mutation_types';
import getInitialState from '~/deploy_freeze/store/state';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import * as logger from '~/lib/logger';
import axios from '~/lib/utils/axios_utils';
import { freezePeriodsFixture } from '../helpers';
import { timezoneDataFixture } from '../../vue_shared/components/timezone_dropdown/helpers';
jest.mock('~/api.js');
-jest.mock('~/flash.js');
+jest.mock('~/alert');
describe('deploy freeze store actions', () => {
const freezePeriodFixture = freezePeriodsFixture[0];
diff --git a/spec/frontend/deploy_keys/components/app_spec.js b/spec/frontend/deploy_keys/components/app_spec.js
index d11ecf95de6..3dfb828b449 100644
--- a/spec/frontend/deploy_keys/components/app_spec.js
+++ b/spec/frontend/deploy_keys/components/app_spec.js
@@ -33,7 +33,6 @@ describe('Deploy keys app component', () => {
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
});
diff --git a/spec/frontend/deploy_keys/components/key_spec.js b/spec/frontend/deploy_keys/components/key_spec.js
index 8599c55c908..5f20d4ad542 100644
--- a/spec/frontend/deploy_keys/components/key_spec.js
+++ b/spec/frontend/deploy_keys/components/key_spec.js
@@ -26,11 +26,6 @@ describe('Deploy keys key', () => {
store.keys = data;
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('enabled key', () => {
const deployKey = data.enabled_keys[0];
diff --git a/spec/frontend/deploy_keys/components/keys_panel_spec.js b/spec/frontend/deploy_keys/components/keys_panel_spec.js
index f5f76d5d493..e0f86aadad4 100644
--- a/spec/frontend/deploy_keys/components/keys_panel_spec.js
+++ b/spec/frontend/deploy_keys/components/keys_panel_spec.js
@@ -23,11 +23,6 @@ describe('Deploy keys panel', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders list of keys', () => {
mountComponent();
expect(wrapper.findAll('.deploy-key').length).toBe(wrapper.vm.keys.length);
diff --git a/spec/frontend/deploy_tokens/components/new_deploy_token_spec.js b/spec/frontend/deploy_tokens/components/new_deploy_token_spec.js
index 46f7b2f3604..a3fdab88270 100644
--- a/spec/frontend/deploy_tokens/components/new_deploy_token_spec.js
+++ b/spec/frontend/deploy_tokens/components/new_deploy_token_spec.js
@@ -7,20 +7,12 @@ import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/h
import { TEST_HOST } from 'helpers/test_constants';
import NewDeployToken from '~/deploy_tokens/components/new_deploy_token.vue';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert, VARIANT_INFO } from '~/flash';
+import { createAlert, VARIANT_INFO } from '~/alert';
const createNewTokenPath = `${TEST_HOST}/create`;
const deployTokensHelpUrl = `${TEST_HOST}/help`;
-jest.mock('~/flash', () => {
- const original = jest.requireActual('~/flash');
-
- return {
- __esModule: true,
- ...original,
- createAlert: jest.fn(),
- };
-});
+jest.mock('~/alert');
describe('New Deploy Token', () => {
let wrapper;
@@ -43,13 +35,12 @@ describe('New Deploy Token', () => {
createNewTokenPath,
tokenType,
},
+ stubs: {
+ GlFormCheckbox,
+ },
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('without a container registry', () => {
beforeEach(() => {
wrapper = factory({ containerRegistryEnabled: false });
@@ -69,7 +60,7 @@ describe('New Deploy Token', () => {
it('should show the read registry scope', () => {
const checkbox = wrapper.findAllComponents(GlFormCheckbox).at(1);
- expect(checkbox.text()).toBe('read_registry');
+ expect(checkbox.text()).toContain('read_registry');
});
function submitTokenThenCheck() {
@@ -91,7 +82,7 @@ describe('New Deploy Token', () => {
});
}
- it('should flash error message if token creation fails', async () => {
+ it('should alert error message if token creation fails', async () => {
const mockAxios = new MockAdapter(axios);
const date = new Date();
@@ -222,4 +213,32 @@ describe('New Deploy Token', () => {
return submitTokenThenCheck();
});
});
+
+ describe('help text for write_package_registry scope', () => {
+ const findWriteRegistryScopeCheckbox = () => wrapper.findAllComponents(GlFormCheckbox).at(4);
+
+ describe('with project tokenType', () => {
+ beforeEach(() => {
+ wrapper = factory();
+ });
+
+ it('should show the correct help text', () => {
+ expect(findWriteRegistryScopeCheckbox().text()).toContain(
+ 'Allows read, write and delete access to the package registry.',
+ );
+ });
+ });
+
+ describe('with group tokenType', () => {
+ beforeEach(() => {
+ wrapper = factory({ tokenType: 'group' });
+ });
+
+ it('should show the correct help text', () => {
+ expect(findWriteRegistryScopeCheckbox().text()).toContain(
+ 'Allows read and write access to the package registry.',
+ );
+ });
+ });
+ });
});
diff --git a/spec/frontend/deploy_tokens/components/revoke_button_spec.js b/spec/frontend/deploy_tokens/components/revoke_button_spec.js
index fa2a7d9b155..6e81205d1c1 100644
--- a/spec/frontend/deploy_tokens/components/revoke_button_spec.js
+++ b/spec/frontend/deploy_tokens/components/revoke_button_spec.js
@@ -52,10 +52,6 @@ describe('RevokeButton', () => {
);
}
- afterEach(() => {
- wrapper.destroy();
- });
-
const findRevokeButton = () => wrapper.findByTestId('revoke-button');
const findModal = () => wrapper.findComponent(GlModal);
const findPrimaryModalButton = () => wrapper.findByTestId('primary-revoke-btn');
diff --git a/spec/frontend/design_management/components/delete_button_spec.js b/spec/frontend/design_management/components/delete_button_spec.js
index 426a61f5a47..81e3b21a910 100644
--- a/spec/frontend/design_management/components/delete_button_spec.js
+++ b/spec/frontend/design_management/components/delete_button_spec.js
@@ -21,10 +21,6 @@ describe('Batch delete button component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders non-disabled button by default', () => {
createComponent();
diff --git a/spec/frontend/design_management/components/design_notes/__snapshots__/design_note_spec.js.snap b/spec/frontend/design_management/components/design_notes/__snapshots__/design_note_spec.js.snap
index 402e55347af..e2f1d6e4b10 100644
--- a/spec/frontend/design_management/components/design_notes/__snapshots__/design_note_spec.js.snap
+++ b/spec/frontend/design_management/components/design_notes/__snapshots__/design_note_spec.js.snap
@@ -70,6 +70,8 @@ exports[`Design note component should match the snapshot 1`] = `
>
<!---->
+
+ <!---->
</div>
</div>
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 2091e1e08dd..56bf0fa60a7 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,18 +1,22 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import { ApolloMutation } from 'vue-apollo';
import { nextTick } from 'vue';
+import waitForPromises from 'helpers/wait_for_promises';
+import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
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';
import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue';
import ToggleRepliesWidget from '~/design_management/components/design_notes/toggle_replies_widget.vue';
-import createNoteMutation from '~/design_management/graphql/mutations/create_note.mutation.graphql';
import toggleResolveDiscussionMutation from '~/design_management/graphql/mutations/toggle_resolve_discussion.mutation.graphql';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
+import destroyNoteMutation from '~/design_management/graphql/mutations/destroy_note.mutation.graphql';
+import { DELETE_NOTE_ERROR_MSG } from '~/design_management/constants';
import mockDiscussion from '../../mock_data/discussion';
import notes from '../../mock_data/notes';
+jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal');
+
const defaultMockDiscussion = {
id: '0',
resolved: false,
@@ -23,7 +27,6 @@ const defaultMockDiscussion = {
const DEFAULT_TODO_COUNT = 2;
describe('Design discussions component', () => {
- const originalGon = window.gon;
let wrapper;
const findDesignNotes = () => wrapper.findAllComponents(DesignNote);
@@ -34,18 +37,7 @@ describe('Design discussions component', () => {
const findResolvedMessage = () => wrapper.find('[data-testid="resolved-message"]');
const findResolveLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findResolveCheckbox = () => wrapper.find('[data-testid="resolve-checkbox"]');
- const findApolloMutation = () => wrapper.findComponent(ApolloMutation);
- const mutationVariables = {
- mutation: createNoteMutation,
- variables: {
- input: {
- noteableId: 'noteable-id',
- body: 'test',
- discussionId: '0',
- },
- },
- };
const registerPath = '/users/sign_up?redirect_to_referer=yes';
const signInPath = '/users/sign_in?redirect_to_referer=yes';
const mutate = jest.fn().mockResolvedValue({ data: { createNote: { errors: [] } } });
@@ -59,7 +51,7 @@ describe('Design discussions component', () => {
provider: { clients: { defaultClient: { readQuery } } },
};
- function createComponent(props = {}, data = {}) {
+ function createComponent({ props = {}, data = {}, apolloConfig = {} } = {}) {
wrapper = mount(DesignDiscussion, {
propsData: {
resolvedDiscussionsExpanded: true,
@@ -82,7 +74,10 @@ describe('Design discussions component', () => {
issueIid: '1',
},
mocks: {
- $apollo,
+ $apollo: {
+ ...$apollo,
+ ...apolloConfig,
+ },
$route: {
hash: '#note_1',
params: {
@@ -101,16 +96,17 @@ describe('Design discussions component', () => {
});
afterEach(() => {
- wrapper.destroy();
- window.gon = originalGon;
+ confirmAction.mockReset();
});
describe('when discussion is not resolvable', () => {
beforeEach(() => {
createComponent({
- discussion: {
- ...defaultMockDiscussion,
- resolvable: false,
+ props: {
+ discussion: {
+ ...defaultMockDiscussion,
+ resolvable: false,
+ },
},
});
});
@@ -171,11 +167,13 @@ describe('Design discussions component', () => {
innerText: DEFAULT_TODO_COUNT,
});
createComponent({
- discussion: {
- ...defaultMockDiscussion,
- resolved: true,
- resolvedBy: notes[0].author,
- resolvedAt: '2020-05-08T07:10:45Z',
+ props: {
+ discussion: {
+ ...defaultMockDiscussion,
+ resolved: true,
+ resolvedBy: notes[0].author,
+ resolvedAt: '2020-05-08T07:10:45Z',
+ },
},
});
});
@@ -206,10 +204,10 @@ describe('Design discussions component', () => {
});
it('emit todo:toggle when discussion is resolved', async () => {
- createComponent(
- { discussionWithOpenForm: defaultMockDiscussion.id },
- { discussionComment: 'test', isFormRendered: true },
- );
+ createComponent({
+ props: { discussionWithOpenForm: defaultMockDiscussion.id },
+ data: { isFormRendered: true },
+ });
findResolveButton().trigger('click');
findReplyForm().vm.$emit('submitForm');
@@ -261,32 +259,28 @@ describe('Design discussions component', () => {
expect(findReplyForm().exists()).toBe(true);
});
- it('calls mutation on submitting form and closes the form', async () => {
- createComponent(
- { discussionWithOpenForm: defaultMockDiscussion.id },
- { discussionComment: 'test', isFormRendered: true },
- );
+ it('closes the form when note submit mutation is completed', async () => {
+ createComponent({
+ props: { discussionWithOpenForm: defaultMockDiscussion.id },
+ data: { isFormRendered: true },
+ });
- findReplyForm().vm.$emit('submit-form');
- expect(mutate).toHaveBeenCalledWith(mutationVariables);
+ findReplyForm().vm.$emit('note-submit-complete', { data: { createNote: {} } });
- await mutate();
await nextTick();
expect(findReplyForm().exists()).toBe(false);
});
it('clears the discussion comment on closing comment form', async () => {
- createComponent(
- { discussionWithOpenForm: defaultMockDiscussion.id },
- { discussionComment: 'test', isFormRendered: true },
- );
+ createComponent({
+ props: { discussionWithOpenForm: defaultMockDiscussion.id },
+ data: { isFormRendered: true },
+ });
await nextTick();
findReplyForm().vm.$emit('cancel-form');
- expect(wrapper.vm.discussionComment).toBe('');
-
await nextTick();
expect(findReplyForm().exists()).toBe(false);
});
@@ -295,15 +289,15 @@ describe('Design discussions component', () => {
it.each([notes[0], notes[0].discussion.notes.nodes[1]])(
'applies correct class to all notes in the active discussion',
(note) => {
- createComponent(
- { discussion: mockDiscussion },
- {
+ createComponent({
+ props: { discussion: mockDiscussion },
+ data: {
activeDiscussion: {
id: note.id,
source: 'pin',
},
},
- );
+ });
expect(
wrapper
@@ -329,10 +323,10 @@ describe('Design discussions component', () => {
});
it('calls toggleResolveDiscussion mutation after adding a note if checkbox was checked', () => {
- createComponent(
- { discussionWithOpenForm: defaultMockDiscussion.id },
- { discussionComment: 'test', isFormRendered: true },
- );
+ createComponent({
+ props: { discussionWithOpenForm: defaultMockDiscussion.id },
+ data: { isFormRendered: true },
+ });
findResolveButton().trigger('click');
findReplyForm().vm.$emit('submitForm');
@@ -359,15 +353,15 @@ describe('Design discussions component', () => {
beforeEach(() => {
window.gon = { current_user_id: null };
- createComponent(
- {
+ createComponent({
+ props: {
discussion: {
...defaultMockDiscussion,
},
discussionWithOpenForm: defaultMockDiscussion.id,
},
- { discussionComment: 'test', isFormRendered: true },
- );
+ data: { isFormRendered: true },
+ });
});
it('does not render resolve discussion button', () => {
@@ -378,10 +372,6 @@ describe('Design discussions component', () => {
expect(findReplyPlaceholder().exists()).toBe(false);
});
- it('does not render apollo-mutation component', () => {
- expect(findApolloMutation().exists()).toBe(false);
- });
-
it('renders design-note-signed-out component', () => {
expect(findDesignNoteSignedOut().exists()).toBe(true);
expect(findDesignNoteSignedOut().props()).toMatchObject({
@@ -390,4 +380,64 @@ describe('Design discussions component', () => {
});
});
});
+
+ it('should open confirmation modal when the note emits `delete-note` event', async () => {
+ createComponent();
+
+ findDesignNotes().at(0).vm.$emit('delete-note', { id: '1' });
+ expect(confirmAction).toHaveBeenCalled();
+ });
+
+ describe('when confirmation modal is opened', () => {
+ const noteId = 'note-test-id';
+
+ it('sends the mutation with correct variables', async () => {
+ confirmAction.mockResolvedValueOnce(true);
+ const destroyNoteMutationSuccess = jest.fn().mockResolvedValue({
+ data: { destroyNote: { note: null, __typename: 'DestroyNote', errors: [] } },
+ });
+ createComponent({ apolloConfig: { mutate: destroyNoteMutationSuccess } });
+
+ findDesignNotes().at(0).vm.$emit('delete-note', { id: noteId });
+
+ expect(confirmAction).toHaveBeenCalled();
+
+ await waitForPromises();
+
+ expect(destroyNoteMutationSuccess).toHaveBeenCalledWith({
+ update: expect.any(Function),
+ mutation: destroyNoteMutation,
+ variables: {
+ input: {
+ id: noteId,
+ },
+ },
+ optimisticResponse: {
+ destroyNote: {
+ note: null,
+ errors: [],
+ __typename: 'DestroyNotePayload',
+ },
+ },
+ });
+ });
+
+ it('emits `delete-note-error` event if GraphQL mutation fails', async () => {
+ confirmAction.mockResolvedValueOnce(true);
+ const destroyNoteMutationError = jest.fn().mockRejectedValue(new Error('GraphQL error'));
+ createComponent({ apolloConfig: { mutate: destroyNoteMutationError } });
+
+ findDesignNotes().at(0).vm.$emit('delete-note', { id: noteId });
+
+ await waitForPromises();
+
+ expect(destroyNoteMutationError).toHaveBeenCalled();
+
+ await waitForPromises();
+
+ expect(wrapper.emitted()).toEqual({
+ 'delete-note-error': [[DELETE_NOTE_ERROR_MSG]],
+ });
+ });
+ });
});
diff --git a/spec/frontend/design_management/components/design_notes/design_note_signed_out_spec.js b/spec/frontend/design_management/components/design_notes/design_note_signed_out_spec.js
index e71bb5ab520..95b08b89809 100644
--- a/spec/frontend/design_management/components/design_notes/design_note_signed_out_spec.js
+++ b/spec/frontend/design_management/components/design_notes/design_note_signed_out_spec.js
@@ -18,10 +18,6 @@ function createComponent(isAddDiscussion = false) {
describe('DesignNoteSignedOut', () => {
let wrapper;
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders message containing register and sign-in links while user wants to reply to a discussion', () => {
wrapper = createComponent();
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 df511586c10..82848bd1a19 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,6 +1,6 @@
import { ApolloMutation } from 'vue-apollo';
import { nextTick } from 'vue';
-import { GlAvatar, GlAvatarLink } from '@gitlab/ui';
+import { GlAvatar, GlAvatarLink, GlDropdown } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import DesignNote from '~/design_management/components/design_notes/design_note.vue';
import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue';
@@ -38,6 +38,8 @@ describe('Design note component', () => {
const findReplyForm = () => wrapper.findComponent(DesignReplyForm);
const findEditButton = () => wrapper.findByTestId('note-edit');
const findNoteContent = () => wrapper.findByTestId('note-text');
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findDeleteNoteButton = () => wrapper.find('[data-testid="delete-note-button"]');
function createComponent(props = {}, data = { isEditing: false }) {
wrapper = shallowMountExtended(DesignNote, {
@@ -63,10 +65,6 @@ describe('Design note component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should match the snapshot', () => {
createComponent({
note,
@@ -112,6 +110,14 @@ describe('Design note component', () => {
expect(findEditButton().exists()).toBe(false);
});
+ it('should not display a dropdown if user does not have a permission to delete note', () => {
+ createComponent({
+ note,
+ });
+
+ expect(findDropdown().exists()).toBe(false);
+ });
+
describe('when user has a permission to edit note', () => {
it('should open an edit form on edit button click', async () => {
createComponent({
@@ -158,15 +164,47 @@ describe('Design note component', () => {
expect(findNoteContent().exists()).toBe(true);
});
- it('calls a mutation on submit-form event and hides a form', async () => {
- findReplyForm().vm.$emit('submit-form');
- expect(mutate).toHaveBeenCalled();
+ it('hides a form after update mutation is completed', async () => {
+ findReplyForm().vm.$emit('note-submit-complete', { data: { updateNote: { errors: [] } } });
- await mutate();
await nextTick();
expect(findReplyForm().exists()).toBe(false);
expect(findNoteContent().exists()).toBe(true);
});
});
});
+
+ describe('when user has a permission to delete note', () => {
+ it('should display a dropdown', () => {
+ createComponent({
+ note: {
+ ...note,
+ userPermissions: {
+ adminNote: true,
+ },
+ },
+ });
+
+ expect(findDropdown().exists()).toBe(true);
+ });
+ });
+
+ it('should emit `delete-note` event with proper payload when delete note button is clicked', async () => {
+ const payload = {
+ ...note,
+ userPermissions: {
+ adminNote: true,
+ },
+ };
+
+ createComponent({
+ note: {
+ ...payload,
+ },
+ });
+
+ findDeleteNoteButton().vm.$emit('click');
+
+ expect(wrapper.emitted()).toEqual({ 'delete-note': [[{ ...payload }]] });
+ });
});
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 f4d4f9cf896..db1cfb4f504 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,46 +1,96 @@
+import { GlAlert } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import Autosave from '~/autosave';
+import waitForPromises from 'helpers/wait_for_promises';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
+import createNoteMutation from '~/design_management/graphql/mutations/create_note.mutation.graphql';
import DesignReplyForm from '~/design_management/components/design_notes/design_reply_form.vue';
+import {
+ ADD_DISCUSSION_COMMENT_ERROR,
+ ADD_IMAGE_DIFF_NOTE_ERROR,
+ UPDATE_IMAGE_DIFF_NOTE_ERROR,
+ UPDATE_NOTE_ERROR,
+} from '~/design_management/utils/error_messages';
+import {
+ mockNoteSubmitSuccessMutationResponse,
+ mockNoteSubmitFailureMutationResponse,
+} from '../../mock_data/apollo_mock';
jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal');
jest.mock('~/autosave');
describe('Design reply form component', () => {
let wrapper;
- let originalGon;
const findTextarea = () => wrapper.find('textarea');
const findSubmitButton = () => wrapper.findComponent({ ref: 'submitButton' });
const findCancelButton = () => wrapper.findComponent({ ref: 'cancelButton' });
-
- function createComponent(props = {}, mountOptions = {}) {
+ const findAlert = () => wrapper.findComponent(GlAlert);
+
+ const mockNoteableId = 'gid://gitlab/DesignManagement::Design/6';
+ const mockComment = 'New comment';
+ const mockDiscussionId = 'gid://gitlab/Discussion/6466a72f35b163f3c3e52d7976a09387f2c573e8';
+ const createNoteMutationData = {
+ mutation: createNoteMutation,
+ update: expect.anything(),
+ variables: {
+ input: {
+ noteableId: mockNoteableId,
+ discussionId: mockDiscussionId,
+ body: mockComment,
+ },
+ },
+ };
+
+ const ctrlKey = {
+ ctrlKey: true,
+ };
+ const metaKey = {
+ metaKey: true,
+ };
+ const mutationHandler = jest.fn().mockResolvedValue();
+
+ function createComponent({
+ props = {},
+ mountOptions = {},
+ data = {},
+ mutation = mutationHandler,
+ } = {}) {
wrapper = mount(DesignReplyForm, {
propsData: {
+ designNoteMutation: createNoteMutation,
+ noteableId: mockNoteableId,
+ markdownDocsPath: 'path/to/markdown/docs',
+ markdownPreviewPath: 'path/to/markdown/preview',
value: '',
- isSaving: false,
- noteableId: 'gid://gitlab/DesignManagement::Design/6',
...props,
},
...mountOptions,
+ mocks: {
+ $apollo: {
+ mutate: mutation,
+ },
+ },
+ data() {
+ return {
+ ...data,
+ };
+ },
});
}
beforeEach(() => {
- originalGon = window.gon;
window.gon.current_user_id = 1;
});
afterEach(() => {
- wrapper.destroy();
- window.gon = originalGon;
confirmAction.mockReset();
});
it('textarea has focus after component mount', () => {
// We need to attach to document, so that `document.activeElement` is properly set in jsdom
- createComponent({}, { attachTo: document.body });
+ createComponent({ mountOptions: { attachTo: document.body } });
expect(findTextarea().element).toEqual(document.activeElement);
});
@@ -64,7 +114,7 @@ describe('Design reply form component', () => {
});
it('renders button text as "Save comment" when creating a comment', () => {
- createComponent({ isNewComment: false });
+ createComponent({ props: { isNewComment: false } });
expect(findSubmitButton().html()).toMatchSnapshot();
});
@@ -76,7 +126,7 @@ describe('Design reply form component', () => {
`(
'initializes autosave support on discussion with proper key',
async ({ discussionId, shortDiscussionId }) => {
- createComponent({ discussionId });
+ createComponent({ props: { discussionId } });
await nextTick();
expect(Autosave).toHaveBeenCalledWith(expect.any(Element), [
@@ -88,32 +138,24 @@ describe('Design reply form component', () => {
);
describe('when form has no text', () => {
- beforeEach(() => {
- createComponent({
- value: '',
- });
+ beforeEach(async () => {
+ createComponent();
+ await nextTick();
});
it('submit button is disabled', () => {
expect(findSubmitButton().attributes().disabled).toBe('disabled');
});
- it('does not emit submitForm event on textarea ctrl+enter keydown', async () => {
- findTextarea().trigger('keydown.enter', {
- ctrlKey: true,
- });
-
- await nextTick();
- expect(wrapper.emitted('submit-form')).toBeUndefined();
- });
-
- it('does not emit submitForm event on textarea meta+enter keydown', async () => {
- findTextarea().trigger('keydown.enter', {
- metaKey: true,
- });
+ it.each`
+ key | keyData
+ ${'ctrl'} | ${ctrlKey}
+ ${'meta'} | ${metaKey}
+ `('does not perform mutation on textarea $key+enter keydown', async ({ keyData }) => {
+ findTextarea().trigger('keydown.enter', keyData);
await nextTick();
- expect(wrapper.emitted('submit-form')).toBeUndefined();
+ expect(mutationHandler).not.toHaveBeenCalled();
});
it('emits cancelForm event on pressing escape button on textarea', () => {
@@ -129,118 +171,159 @@ describe('Design reply form component', () => {
});
});
- describe('when form has text', () => {
- beforeEach(() => {
- createComponent({
- value: 'test',
- });
- });
-
+ describe('when the form has text', () => {
it('submit button is enabled', () => {
+ createComponent({ props: { value: mockComment } });
expect(findSubmitButton().attributes().disabled).toBeUndefined();
});
- it('emits submitForm event on Comment button click', async () => {
- const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset');
+ it('calls a mutation on submit button click event', async () => {
+ const mockMutationVariables = {
+ noteableId: mockNoteableId,
+ discussionId: mockDiscussionId,
+ };
+ const successfulMutation = jest.fn().mockResolvedValue(mockNoteSubmitSuccessMutationResponse);
+ createComponent({
+ props: {
+ designNoteMutation: createNoteMutation,
+ mutationVariables: mockMutationVariables,
+ value: mockComment,
+ },
+ mutation: successfulMutation,
+ });
findSubmitButton().vm.$emit('click');
await nextTick();
- expect(wrapper.emitted('submit-form')).toHaveLength(1);
- expect(autosaveResetSpy).toHaveBeenCalled();
- });
+ expect(successfulMutation).toHaveBeenCalledWith(createNoteMutationData);
- it('emits submitForm event on textarea ctrl+enter keydown', async () => {
- const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset');
+ await waitForPromises();
+ expect(wrapper.emitted('note-submit-complete')).toEqual([
+ [mockNoteSubmitSuccessMutationResponse],
+ ]);
+ });
- findTextarea().trigger('keydown.enter', {
- ctrlKey: true,
+ it.each`
+ key | keyData
+ ${'ctrl'} | ${ctrlKey}
+ ${'meta'} | ${metaKey}
+ `('does perform mutation on textarea $key+enter keydown', async ({ keyData }) => {
+ const mockMutationVariables = {
+ noteableId: mockNoteableId,
+ discussionId: mockDiscussionId,
+ };
+ const successfulMutation = jest.fn().mockResolvedValue(mockNoteSubmitSuccessMutationResponse);
+ createComponent({
+ props: {
+ designNoteMutation: createNoteMutation,
+ mutationVariables: mockMutationVariables,
+ value: mockComment,
+ },
+ mutation: successfulMutation,
});
+ findTextarea().trigger('keydown.enter', keyData);
+
await nextTick();
- expect(wrapper.emitted('submit-form')).toHaveLength(1);
- expect(autosaveResetSpy).toHaveBeenCalled();
- });
+ expect(successfulMutation).toHaveBeenCalledWith(createNoteMutationData);
- it('emits submitForm event on textarea meta+enter keydown', async () => {
- const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset');
+ await waitForPromises();
+ expect(wrapper.emitted('note-submit-complete')).toEqual([
+ [mockNoteSubmitSuccessMutationResponse],
+ ]);
+ });
- findTextarea().trigger('keydown.enter', {
- metaKey: true,
+ it('shows error message when mutation fails', async () => {
+ const failedMutation = jest.fn().mockRejectedValue(mockNoteSubmitFailureMutationResponse);
+ createComponent({
+ props: {
+ designNoteMutation: createNoteMutation,
+ value: mockComment,
+ },
+ mutation: failedMutation,
+ data: {
+ errorMessage: 'error',
+ },
});
- await nextTick();
- expect(wrapper.emitted('submit-form')).toHaveLength(1);
- expect(autosaveResetSpy).toHaveBeenCalled();
- });
-
- it('emits input event on changing textarea content', async () => {
- findTextarea().setValue('test2');
+ findSubmitButton().vm.$emit('click');
- await nextTick();
- expect(wrapper.emitted('input')).toEqual([['test2']]);
+ await waitForPromises();
+ expect(findAlert().exists()).toBe(true);
});
+ it.each`
+ isDiscussion | isNewComment | errorMessage
+ ${true} | ${true} | ${ADD_IMAGE_DIFF_NOTE_ERROR}
+ ${true} | ${false} | ${UPDATE_IMAGE_DIFF_NOTE_ERROR}
+ ${false} | ${true} | ${ADD_DISCUSSION_COMMENT_ERROR}
+ ${false} | ${false} | ${UPDATE_NOTE_ERROR}
+ `(
+ 'return proper error message on error in case of isDiscussion is $isDiscussion and isNewComment is $isNewComment',
+ async ({ isDiscussion, isNewComment, errorMessage }) => {
+ createComponent({ props: { isDiscussion, isNewComment } });
+
+ expect(wrapper.vm.getErrorMessage()).toBe(errorMessage);
+ },
+ );
+
it('emits cancelForm event on Escape key if text was not changed', () => {
+ createComponent();
+
findTextarea().trigger('keyup.esc');
expect(wrapper.emitted('cancel-form')).toHaveLength(1);
});
it('opens confirmation modal on Escape key when text has changed', async () => {
- wrapper.setProps({ value: 'test2' });
+ createComponent();
+
+ findTextarea().setValue(mockComment);
await nextTick();
findTextarea().trigger('keyup.esc');
- expect(confirmAction).toHaveBeenCalled();
- });
-
- it('emits cancelForm event on Cancel button click if text was not changed', () => {
- findCancelButton().trigger('click');
- expect(wrapper.emitted('cancel-form')).toHaveLength(1);
- });
-
- it('opens confirmation modal on Cancel button click when text has changed', async () => {
- wrapper.setProps({ value: 'test2' });
-
- await nextTick();
- findCancelButton().trigger('click');
expect(confirmAction).toHaveBeenCalled();
});
it('emits cancelForm event when confirmed', async () => {
confirmAction.mockResolvedValueOnce(true);
- const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset');
- wrapper.setProps({ value: 'test3' });
- await nextTick();
+ createComponent({ props: { value: mockComment } });
+ findTextarea().setValue('Comment changed');
- findTextarea().trigger('keyup.esc');
await nextTick();
+ findTextarea().trigger('keyup.esc');
expect(confirmAction).toHaveBeenCalled();
- await nextTick();
+ await waitForPromises();
expect(wrapper.emitted('cancel-form')).toHaveLength(1);
- expect(autosaveResetSpy).toHaveBeenCalled();
});
- it("doesn't emit cancelForm event when not confirmed", async () => {
+ it('does not emit cancelForm event when not confirmed', async () => {
confirmAction.mockResolvedValueOnce(false);
- const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset');
- wrapper.setProps({ value: 'test3' });
+ createComponent({ props: { value: mockComment } });
+ findTextarea().setValue('Comment changed');
await nextTick();
findTextarea().trigger('keyup.esc');
await nextTick();
expect(confirmAction).toHaveBeenCalled();
- await nextTick();
+ await waitForPromises();
expect(wrapper.emitted('cancel-form')).toBeUndefined();
- expect(autosaveResetSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when component is destroyed', () => {
+ it('calls autosave.reset', async () => {
+ const autosaveResetSpy = jest.spyOn(Autosave.prototype, 'reset');
+ createComponent();
+ await wrapper.destroy();
+ expect(autosaveResetSpy).toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/design_management/components/design_notes/toggle_replies_widget_spec.js b/spec/frontend/design_management/components/design_notes/toggle_replies_widget_spec.js
index 41129e2b58d..eaa5a620fa6 100644
--- a/spec/frontend/design_management/components/design_notes/toggle_replies_widget_spec.js
+++ b/spec/frontend/design_management/components/design_notes/toggle_replies_widget_spec.js
@@ -23,10 +23,6 @@ describe('Toggle replies widget component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when replies are collapsed', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/design_management/components/design_presentation_spec.js b/spec/frontend/design_management/components/design_presentation_spec.js
index 4a339899473..fdcea6d88c0 100644
--- a/spec/frontend/design_management/components/design_presentation_spec.js
+++ b/spec/frontend/design_management/components/design_presentation_spec.js
@@ -15,7 +15,6 @@ const mockOverlayData = {
};
describe('Design management design presentation component', () => {
- const originalGon = window.gon;
let wrapper;
function createComponent(
@@ -114,11 +113,6 @@ describe('Design management design presentation component', () => {
window.gon = { current_user_id: 1 };
});
- afterEach(() => {
- wrapper.destroy();
- window.gon = originalGon;
- });
-
it('renders image and overlay when image provided', async () => {
createComponent(
{
diff --git a/spec/frontend/design_management/components/design_scaler_spec.js b/spec/frontend/design_management/components/design_scaler_spec.js
index e1a66cea329..62a26a8f5dd 100644
--- a/spec/frontend/design_management/components/design_scaler_spec.js
+++ b/spec/frontend/design_management/components/design_scaler_spec.js
@@ -25,11 +25,6 @@ describe('Design management design scaler component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when `scale` value is greater than 1', () => {
beforeEach(async () => {
setScale(1.6);
diff --git a/spec/frontend/design_management/components/design_sidebar_spec.js b/spec/frontend/design_management/components/design_sidebar_spec.js
index af995f75ddc..90424175417 100644
--- a/spec/frontend/design_management/components/design_sidebar_spec.js
+++ b/spec/frontend/design_management/components/design_sidebar_spec.js
@@ -29,7 +29,6 @@ const $route = {
const mutate = jest.fn().mockResolvedValue();
describe('Design management design sidebar component', () => {
- const originalGon = window.gon;
let wrapper;
const findDiscussions = () => wrapper.findAllComponents(DesignDiscussion);
@@ -67,11 +66,6 @@ describe('Design management design sidebar component', () => {
window.gon = { current_user_id: 1 };
});
- afterEach(() => {
- wrapper.destroy();
- window.gon = originalGon;
- });
-
it('renders participants', () => {
createComponent();
@@ -143,8 +137,8 @@ describe('Design management design sidebar component', () => {
expect(findResolvedCommentsToggle().props('visible')).toBe(true);
});
- it('sends a mutation to set an active discussion when clicking on a discussion', () => {
- findFirstDiscussion().trigger('click');
+ it('emits correct event to send a mutation to set an active discussion when clicking on a discussion', () => {
+ findFirstDiscussion().vm.$emit('update-active-discussion');
expect(mutate).toHaveBeenCalledWith(updateActiveDiscussionMutationVariables);
});
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 ac26873b692..f713203c0ee 100644
--- a/spec/frontend/design_management/components/design_todo_button_spec.js
+++ b/spec/frontend/design_management/components/design_todo_button_spec.js
@@ -51,8 +51,6 @@ describe('Design management design todo button', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
jest.clearAllMocks();
});
diff --git a/spec/frontend/design_management/components/image_spec.js b/spec/frontend/design_management/components/image_spec.js
index 95d2ad504de..53abcc559d8 100644
--- a/spec/frontend/design_management/components/image_spec.js
+++ b/spec/frontend/design_management/components/image_spec.js
@@ -20,10 +20,6 @@ describe('Design management large image component', () => {
stubPerformanceWebAPI();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders loading state', () => {
createComponent({
isLoading: true,
diff --git a/spec/frontend/design_management/components/list/item_spec.js b/spec/frontend/design_management/components/list/item_spec.js
index e907e2e4ac5..4a0ad5a045b 100644
--- a/spec/frontend/design_management/components/list/item_spec.js
+++ b/spec/frontend/design_management/components/list/item_spec.js
@@ -54,10 +54,6 @@ describe('Design management list item component', () => {
);
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when item is not in view', () => {
it('image is not rendered', () => {
createComponent();
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 38a7fadee79..8427d83ceee 100644
--- a/spec/frontend/design_management/components/toolbar/design_navigation_spec.js
+++ b/spec/frontend/design_management/components/toolbar/design_navigation_spec.js
@@ -34,10 +34,6 @@ describe('Design management pagination component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('hides components when designs are empty', () => {
expect(wrapper.element).toMatchSnapshot();
});
diff --git a/spec/frontend/design_management/components/toolbar/index_spec.js b/spec/frontend/design_management/components/toolbar/index_spec.js
index 1776405ece9..764ad73805f 100644
--- a/spec/frontend/design_management/components/toolbar/index_spec.js
+++ b/spec/frontend/design_management/components/toolbar/index_spec.js
@@ -1,12 +1,18 @@
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue, { nextTick } from 'vue';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
import VueRouter from 'vue-router';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import permissionsQuery from 'shared_queries/design_management/design_permissions.query.graphql';
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';
+import { getPermissionsQueryResponse } from '../../mock_data/apollo_mock';
Vue.use(VueRouter);
+Vue.use(VueApollo);
const router = new VueRouter();
const RouterLinkStub = {
@@ -27,7 +33,12 @@ describe('Design management toolbar component', () => {
const updatedAt = new Date();
updatedAt.setHours(updatedAt.getHours() - 1);
+ const mockApollo = createMockApollo([
+ [permissionsQuery, jest.fn().mockResolvedValue(getPermissionsQueryResponse(createDesign))],
+ ]);
+
wrapper = shallowMount(Toolbar, {
+ apolloProvider: mockApollo,
router,
propsData: {
id: '1',
@@ -46,31 +57,20 @@ describe('Design management toolbar component', () => {
'router-link': RouterLinkStub,
},
});
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- permissions: {
- createDesign,
- },
- });
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders design and updated data', async () => {
createComponent();
- await nextTick();
+ await waitForPromises();
+
expect(wrapper.element).toMatchSnapshot();
});
it('links back to designs list', async () => {
createComponent();
- await nextTick();
+ await waitForPromises();
const link = wrapper.find('a');
expect(link.props('to')).toEqual({
@@ -84,35 +84,41 @@ describe('Design management toolbar component', () => {
it('renders delete button on latest designs version with logged in user', async () => {
createComponent();
- await nextTick();
+ await waitForPromises();
+
expect(wrapper.findComponent(DeleteButton).exists()).toBe(true);
});
it('does not render delete button on non-latest version', async () => {
createComponent(false, true, { isLatestVersion: false });
- await nextTick();
+ await waitForPromises();
+
expect(wrapper.findComponent(DeleteButton).exists()).toBe(false);
});
it('does not render delete button when user is not logged in', async () => {
createComponent(false, false);
- await nextTick();
+ await waitForPromises();
+
expect(wrapper.findComponent(DeleteButton).exists()).toBe(false);
});
it('emits `delete` event on deleteButton `delete-selected-designs` event', async () => {
createComponent();
- await nextTick();
+ await waitForPromises();
+
wrapper.findComponent(DeleteButton).vm.$emit('delete-selected-designs');
expect(wrapper.emitted().delete).toHaveLength(1);
});
- it('renders download button with correct link', () => {
+ it('renders download button with correct link', async () => {
createComponent();
+ await waitForPromises();
+
expect(wrapper.findComponent(GlButton).attributes('href')).toBe(
'/-/designs/306/7f747adcd4693afadbe968d7ba7d983349b9012d',
);
diff --git a/spec/frontend/design_management/components/upload/button_spec.js b/spec/frontend/design_management/components/upload/button_spec.js
index 59821218ab8..ceae7920e0d 100644
--- a/spec/frontend/design_management/components/upload/button_spec.js
+++ b/spec/frontend/design_management/components/upload/button_spec.js
@@ -14,10 +14,6 @@ describe('Design management upload button component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders upload design button', () => {
createComponent();
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 6ad10e707ab..cdfff61ba4f 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
@@ -42,10 +42,6 @@ describe('Design management design version dropdown component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
const findListbox = () => wrapper.findComponent(GlCollapsibleListbox);
const findAllListboxItems = () => wrapper.findAllComponents(GlListboxItem);
const findVersionLink = (index) => wrapper.findAllComponents(GlListboxItem).at(index);
diff --git a/spec/frontend/design_management/mock_data/apollo_mock.js b/spec/frontend/design_management/mock_data/apollo_mock.js
index 2a43b5debee..2b99dcf14da 100644
--- a/spec/frontend/design_management/mock_data/apollo_mock.js
+++ b/spec/frontend/design_management/mock_data/apollo_mock.js
@@ -91,7 +91,7 @@ export const designUploadMutationUpdatedResponse = {
},
};
-export const permissionsQueryResponse = {
+export const getPermissionsQueryResponse = (createDesign = true) => ({
data: {
project: {
__typename: 'Project',
@@ -99,11 +99,11 @@ export const permissionsQueryResponse = {
issue: {
__typename: 'Issue',
id: 'issue-1',
- userPermissions: { __typename: 'UserPermissions', createDesign: true },
+ userPermissions: { __typename: 'UserPermissions', createDesign },
},
},
},
-};
+});
export const reorderedDesigns = [
{
@@ -211,3 +211,109 @@ export const getDesignQueryResponse = {
},
},
};
+
+export const mockNoteSubmitSuccessMutationResponse = [
+ {
+ data: {
+ createNote: {
+ note: {
+ id: 'gid://gitlab/DiffNote/468',
+ author: {
+ id: 'gid://gitlab/User/1',
+ avatarUrl:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://127.0.0.1:3000/root',
+ __typename: 'UserCore',
+ },
+ body: 'New comment',
+ bodyHtml: "<p data-sourcepos='1:1-1:4' dir='auto'>asdd</p>",
+ createdAt: '2023-02-24T06:49:20Z',
+ resolved: false,
+ position: {
+ diffRefs: {
+ baseSha: 'f63ae53ed82d8765477c191383e1e6a000c10375',
+ startSha: 'f63ae53ed82d8765477c191383e1e6a000c10375',
+ headSha: 'f348c652f1a737151fc79047895e695fbe81464c',
+ __typename: 'DiffRefs',
+ },
+ x: 441,
+ y: 128,
+ height: 152,
+ width: 695,
+ __typename: 'DiffPosition',
+ },
+ userPermissions: {
+ adminNote: true,
+ repositionNote: true,
+ __typename: 'NotePermissions',
+ },
+ discussion: {
+ id: 'gid://gitlab/Discussion/6466a72f35b163f3c3e52d7976a09387f2c573e8',
+ notes: {
+ nodes: [
+ {
+ id: 'gid://gitlab/DiffNote/459',
+ __typename: 'Note',
+ },
+ ],
+ __typename: 'NoteConnection',
+ },
+ __typename: 'Discussion',
+ },
+ __typename: 'Note',
+ },
+ errors: [],
+ __typename: 'CreateNotePayload',
+ },
+ },
+ },
+];
+
+export const mockNoteSubmitFailureMutationResponse = [
+ {
+ errors: [
+ {
+ message:
+ 'Variable $input of type CreateNoteInput! was provided invalid value for bodyaa (Field is not defined on CreateNoteInput), body (Expected value to not be null)',
+ locations: [
+ {
+ line: 1,
+ column: 21,
+ },
+ ],
+ extensions: {
+ value: {
+ noteableId: 'gid://gitlab/DesignManagement::Design/10',
+ discussionId: 'gid://gitlab/Discussion/6466a72f35b163f3c3e52d7976a09387f2c573e8',
+ bodyaa: 'df',
+ },
+ problems: [
+ {
+ path: ['bodyaa'],
+ explanation: 'Field is not defined on CreateNoteInput',
+ },
+ {
+ path: ['body'],
+ explanation: 'Expected value to not be null',
+ },
+ ],
+ },
+ },
+ ],
+ },
+];
+
+export const mockCreateImageNoteDiffResponse = {
+ data: {
+ createImageDiffNote: {
+ note: {
+ author: {
+ username: '',
+ },
+ discussion: {},
+ },
+ },
+ },
+};
diff --git a/spec/frontend/design_management/mock_data/project.js b/spec/frontend/design_management/mock_data/project.js
new file mode 100644
index 00000000000..e1c2057d8d1
--- /dev/null
+++ b/spec/frontend/design_management/mock_data/project.js
@@ -0,0 +1,17 @@
+import design from './design';
+
+export default {
+ project: {
+ issue: {
+ designCollection: {
+ designs: {
+ nodes: [
+ {
+ ...design,
+ },
+ ],
+ },
+ },
+ },
+ },
+};
diff --git a/spec/frontend/design_management/pages/design/index_spec.js b/spec/frontend/design_management/pages/design/index_spec.js
index a11463ab663..6cec4036d40 100644
--- a/spec/frontend/design_management/pages/design/index_spec.js
+++ b/spec/frontend/design_management/pages/design/index_spec.js
@@ -1,15 +1,14 @@
import { GlAlert } from '@gitlab/ui';
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';
import Api from '~/api';
import DesignPresentation from '~/design_management/components/design_presentation.vue';
import DesignSidebar from '~/design_management/components/design_sidebar.vue';
import { DESIGN_DETAIL_LAYOUT_CLASSLIST } from '~/design_management/constants';
-import createImageDiffNoteMutation from '~/design_management/graphql/mutations/create_image_diff_note.mutation.graphql';
import updateActiveDiscussion from '~/design_management/graphql/mutations/update_active_discussion.mutation.graphql';
+import getDesignQuery from '~/design_management/graphql/queries/get_design.query.graphql';
import DesignIndex from '~/design_management/pages/design/index.vue';
import createRouter from '~/design_management/router';
import { DESIGNS_ROUTE_NAME, DESIGN_ROUTE_NAME } from '~/design_management/router/constants';
@@ -23,16 +22,23 @@ import {
DESIGN_SNOWPLOW_EVENT_TYPES,
DESIGN_SERVICE_PING_EVENT_TYPES,
} from '~/design_management/utils/tracking';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
+import * as cacheUpdate from '~/design_management/utils/cache_update';
import mockAllVersions from '../../mock_data/all_versions';
import design from '../../mock_data/design';
+import mockProject from '../../mock_data/project';
import mockResponseWithDesigns from '../../mock_data/designs';
import mockResponseNoDesigns from '../../mock_data/no_designs';
+import { mockCreateImageNoteDiffResponse } from '../../mock_data/apollo_mock';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/api.js');
const focusInput = jest.fn();
+const mockCacheObject = {
+ readQuery: jest.fn().mockReturnValue(mockProject),
+ writeQuery: jest.fn(),
+};
const mutate = jest.fn().mockResolvedValue();
const mockPageLayoutElement = {
classList: {
@@ -52,32 +58,13 @@ const mockDesignNoDiscussions = {
nodes: [],
},
};
-const newComment = 'new comment';
+
const annotationCoordinates = {
x: 10,
y: 10,
width: 100,
height: 100,
};
-const createDiscussionMutationVariables = {
- mutation: createImageDiffNoteMutation,
- update: expect.anything(),
- variables: {
- input: {
- body: newComment,
- noteableId: design.id,
- position: {
- headSha: 'headSha',
- baseSha: 'baseSha',
- startSha: 'startSha',
- paths: {
- newPath: 'full-design-path',
- },
- ...annotationCoordinates,
- },
- },
- },
-};
Vue.use(VueRouter);
@@ -85,7 +72,7 @@ describe('Design management design index page', () => {
let wrapper;
let router;
- const findDiscussionForm = () => wrapper.findComponent(DesignReplyForm);
+ const findDesignReplyForm = () => wrapper.findComponent(DesignReplyForm);
const findSidebar = () => wrapper.findComponent(DesignSidebar);
const findDesignPresentation = () => wrapper.findComponent(DesignPresentation);
@@ -95,7 +82,7 @@ describe('Design management design index page', () => {
data = {},
intialRouteOptions = {},
provide = {},
- stubs = { ApolloMutation, DesignSidebar, DesignReplyForm },
+ stubs = { DesignSidebar, DesignReplyForm },
} = {},
) {
const $apollo = {
@@ -105,6 +92,11 @@ describe('Design management design index page', () => {
},
},
mutate,
+ getClient() {
+ return {
+ cache: mockCacheObject,
+ };
+ },
};
router = createRouter();
@@ -133,10 +125,6 @@ describe('Design management design index page', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when navigating to component', () => {
it('applies fullscreen layout class', () => {
jest.spyOn(utils, 'getPageLayoutElement').mockReturnValue(mockPageLayoutElement);
@@ -216,7 +204,7 @@ describe('Design management design index page', () => {
findDesignPresentation().vm.$emit('openCommentForm', { x: 0, y: 0 });
await nextTick();
- expect(findDiscussionForm().exists()).toBe(true);
+ expect(findDesignReplyForm().exists()).toBe(true);
});
it('keeps new discussion form focused', () => {
@@ -235,24 +223,36 @@ describe('Design management design index page', () => {
expect(focusInput).toHaveBeenCalled();
});
- it('sends a mutation on submitting form and closes form', async () => {
+ it('sends a update and closes the form when mutation is completed', async () => {
createComponent(
{ loading: false },
{
data: {
design,
annotationCoordinates,
- comment: newComment,
},
},
);
- findDiscussionForm().vm.$emit('submit-form');
- expect(mutate).toHaveBeenCalledWith(createDiscussionMutationVariables);
+ const addImageDiffNoteToStore = jest.spyOn(cacheUpdate, 'updateStoreAfterAddImageDiffNote');
+
+ const mockDesignVariables = {
+ fullPath: 'project-path',
+ iid: '1',
+ filenames: ['gid::/gitlab/Design/1'],
+ atVersion: null,
+ };
+
+ findDesignReplyForm().vm.$emit('note-submit-complete', mockCreateImageNoteDiffResponse);
await nextTick();
- await mutate({ variables: createDiscussionMutationVariables });
- expect(findDiscussionForm().exists()).toBe(false);
+ expect(addImageDiffNoteToStore).toHaveBeenCalledWith(
+ mockCacheObject,
+ mockCreateImageNoteDiffResponse.data.createImageDiffNote,
+ getDesignQuery,
+ mockDesignVariables,
+ );
+ expect(findDesignReplyForm().exists()).toBe(false);
});
it('closes the form and clears the comment on canceling form', async () => {
@@ -262,17 +262,14 @@ describe('Design management design index page', () => {
data: {
design,
annotationCoordinates,
- comment: newComment,
},
},
);
- findDiscussionForm().vm.$emit('cancel-form');
-
- expect(wrapper.vm.comment).toBe('');
+ findDesignReplyForm().vm.$emit('cancel-form');
await nextTick();
- expect(findDiscussionForm().exists()).toBe(false);
+ expect(findDesignReplyForm().exists()).toBe(false);
});
describe('with error', () => {
diff --git a/spec/frontend/design_management/pages/index_spec.js b/spec/frontend/design_management/pages/index_spec.js
index 76ece922ded..1ddf757eb19 100644
--- a/spec/frontend/design_management/pages/index_spec.js
+++ b/spec/frontend/design_management/pages/index_spec.js
@@ -29,19 +29,19 @@ import {
DESIGN_TRACKING_PAGE_NAME,
DESIGN_SNOWPLOW_EVENT_TYPES,
} from '~/design_management/utils/tracking';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import DesignDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
import {
designListQueryResponse,
designUploadMutationCreatedResponse,
designUploadMutationUpdatedResponse,
- permissionsQueryResponse,
+ getPermissionsQueryResponse,
moveDesignMutationResponse,
reorderedDesigns,
moveDesignMutationResponseWithErrors,
} from '../mock_data/apollo_mock';
-jest.mock('~/flash.js');
+jest.mock('~/alert');
const mockPageEl = {
classList: {
remove: jest.fn(),
@@ -181,7 +181,7 @@ describe('Design management index page', () => {
const requestHandlers = [
[getDesignListQuery, jest.fn().mockResolvedValue(designListQueryResponse)],
- [permissionsQuery, jest.fn().mockResolvedValue(permissionsQueryResponse)],
+ [permissionsQuery, jest.fn().mockResolvedValue(getPermissionsQueryResponse())],
[moveDesignMutation, moveDesignHandler],
];
@@ -197,11 +197,6 @@ describe('Design management index page', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('designs', () => {
it('renders loading icon', () => {
createComponent({ loading: true });
@@ -800,7 +795,7 @@ describe('Design management index page', () => {
expect(draggableAttributes().disabled).toBe(false);
});
- it('displays flash if mutation had a recoverable error', async () => {
+ it('displays alert if mutation had a recoverable error', async () => {
createComponentWithApollo({
moveHandler: jest.fn().mockResolvedValue(moveDesignMutationResponseWithErrors),
});
diff --git a/spec/frontend/design_management/router_spec.js b/spec/frontend/design_management/router_spec.js
index b9edde559c8..3503725f741 100644
--- a/spec/frontend/design_management/router_spec.js
+++ b/spec/frontend/design_management/router_spec.js
@@ -11,8 +11,6 @@ import '~/commons/bootstrap';
function factory(routeArg) {
Vue.use(VueRouter);
- window.gon = { sprite_icons: '' };
-
const router = createRouter('/');
if (routeArg !== undefined) {
router.push(routeArg);
@@ -36,10 +34,6 @@ function factory(routeArg) {
}
describe('Design management router', () => {
- afterEach(() => {
- window.location.hash = '';
- });
-
describe.each([['/'], [{ name: DESIGNS_ROUTE_NAME }]])('root route', (routeArg) => {
it('pushes home component', () => {
const wrapper = factory(routeArg);
diff --git a/spec/frontend/design_management/utils/cache_update_spec.js b/spec/frontend/design_management/utils/cache_update_spec.js
index 42777adfd58..e89dfe9f860 100644
--- a/spec/frontend/design_management/utils/cache_update_spec.js
+++ b/spec/frontend/design_management/utils/cache_update_spec.js
@@ -10,10 +10,10 @@ import {
ADD_IMAGE_DIFF_NOTE_ERROR,
UPDATE_IMAGE_DIFF_NOTE_ERROR,
} from '~/design_management/utils/error_messages';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import design from '../mock_data/design';
-jest.mock('~/flash.js');
+jest.mock('~/alert');
describe('Design Management cache update', () => {
const mockErrors = ['code red!'];
diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js
index 513e67ea247..06995706a2b 100644
--- a/spec/frontend/diffs/components/app_spec.js
+++ b/spec/frontend/diffs/components/app_spec.js
@@ -59,6 +59,7 @@ describe('diffs/components/app', () => {
endpoint: TEST_ENDPOINT,
endpointMetadata: `${TEST_HOST}/diff/endpointMetadata`,
endpointBatch: `${TEST_HOST}/diff/endpointBatch`,
+ endpointDiffForPath: TEST_ENDPOINT,
endpointCoverage: `${TEST_HOST}/diff/endpointCoverage`,
endpointCodequality: '',
projectPath: 'namespace/project',
@@ -71,12 +72,6 @@ describe('diffs/components/app', () => {
},
provide,
store,
- stubs: {
- DynamicScroller: {
- template: `<div><slot :item="$store.state.diffs.diffFiles[0]"></slot></div>`,
- },
- DynamicScrollerItem: true,
- },
});
}
@@ -265,7 +260,7 @@ describe('diffs/components/app', () => {
it('sets width of tree list', () => {
createComponent({}, ({ state }) => {
- state.diffs.diffFiles = [{ file_hash: '111', file_path: '111.js' }];
+ state.diffs.treeEntries = { 111: { type: 'blob', fileHash: '111', path: '111.js' } };
});
expect(wrapper.find('.js-diff-tree-list').element.style.width).toEqual('320px');
@@ -294,13 +289,14 @@ describe('diffs/components/app', () => {
it('does not render empty state when diff files exist', () => {
createComponent({}, ({ state }) => {
- state.diffs.diffFiles.push({
- id: 1,
- });
+ state.diffs.diffFiles = ['anything'];
+ state.diffs.treeEntries['1'] = { type: 'blob', id: 1 };
});
expect(wrapper.findComponent(NoChanges).exists()).toBe(false);
- expect(wrapper.findAllComponents(DiffFile).length).toBe(1);
+ expect(wrapper.findComponent({ name: 'DynamicScroller' }).props('items')).toBe(
+ store.state.diffs.diffFiles,
+ );
});
});
@@ -388,19 +384,15 @@ describe('diffs/components/app', () => {
beforeEach(() => {
createComponent({}, () => {
- store.state.diffs.diffFiles = [
- { file_hash: '111', file_path: '111.js' },
- { file_hash: '222', file_path: '222.js' },
- { file_hash: '333', file_path: '333.js' },
+ store.state.diffs.treeEntries = [
+ { type: 'blob', fileHash: '111', path: '111.js' },
+ { type: 'blob', fileHash: '222', path: '222.js' },
+ { type: 'blob', fileHash: '333', path: '333.js' },
];
});
spy = jest.spyOn(store, 'dispatch');
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('jumps to next and previous files in the list', async () => {
await nextTick();
@@ -507,7 +499,6 @@ describe('diffs/components/app', () => {
describe('diffs', () => {
it('should render compare versions component', () => {
createComponent({}, ({ state }) => {
- state.diffs.diffFiles = [{ file_hash: '111', file_path: '111.js' }];
state.diffs.mergeRequestDiffs = diffsMockData;
state.diffs.targetBranchName = 'target-branch';
state.diffs.mergeRequestDiff = mergeRequestDiff;
@@ -578,10 +569,18 @@ describe('diffs/components/app', () => {
it('should display diff file if there are diff files', () => {
createComponent({}, ({ state }) => {
- state.diffs.diffFiles.push({ sha: '123' });
+ state.diffs.diffFiles = [{ file_hash: '111', file_path: '111.js' }];
+ state.diffs.treeEntries = {
+ 111: { type: 'blob', fileHash: '111', path: '111.js' },
+ 123: { type: 'blob', fileHash: '123', path: '123.js' },
+ 312: { type: 'blob', fileHash: '312', path: '312.js' },
+ };
});
- expect(wrapper.findComponent(DiffFile).exists()).toBe(true);
+ expect(wrapper.findComponent({ name: 'DynamicScroller' }).exists()).toBe(true);
+ expect(wrapper.findComponent({ name: 'DynamicScroller' }).props('items')).toBe(
+ store.state.diffs.diffFiles,
+ );
});
it("doesn't render tree list when no changes exist", () => {
@@ -592,7 +591,7 @@ describe('diffs/components/app', () => {
it('should render tree list', () => {
createComponent({}, ({ state }) => {
- state.diffs.diffFiles = [{ file_hash: '111', file_path: '111.js' }];
+ state.diffs.treeEntries = { 111: { type: 'blob', fileHash: '111', path: '111.js' } };
});
expect(wrapper.findComponent(TreeList).exists()).toBe(true);
@@ -606,7 +605,7 @@ describe('diffs/components/app', () => {
it('calls setShowTreeList when only 1 file', () => {
createComponent({}, ({ state }) => {
- state.diffs.diffFiles.push({ sha: '123' });
+ state.diffs.treeEntries = { 123: { type: 'blob', fileHash: '123' } };
});
jest.spyOn(store, 'dispatch');
wrapper.vm.setTreeDisplay();
@@ -617,10 +616,12 @@ describe('diffs/components/app', () => {
});
});
- it('calls setShowTreeList with true when more than 1 file is in diffs array', () => {
+ it('calls setShowTreeList with true when more than 1 file is in tree entries map', () => {
createComponent({}, ({ state }) => {
- state.diffs.diffFiles.push({ sha: '123' });
- state.diffs.diffFiles.push({ sha: '124' });
+ state.diffs.treeEntries = {
+ 111: { type: 'blob', fileHash: '111', path: '111.js' },
+ 123: { type: 'blob', fileHash: '123', path: '123.js' },
+ };
});
jest.spyOn(store, 'dispatch');
@@ -640,7 +641,7 @@ describe('diffs/components/app', () => {
localStorage.setItem('mr_tree_show', showTreeList);
createComponent({}, ({ state }) => {
- state.diffs.diffFiles.push({ sha: '123' });
+ state.diffs.treeEntries['123'] = { sha: '123' };
});
jest.spyOn(store, 'dispatch');
@@ -656,7 +657,10 @@ describe('diffs/components/app', () => {
describe('file-by-file', () => {
it('renders a single diff', async () => {
createComponent({ fileByFileUserPreference: true }, ({ state }) => {
- state.diffs.diffFiles.push({ file_hash: '123' });
+ state.diffs.treeEntries = {
+ 123: { type: 'blob', fileHash: '123' },
+ 312: { type: 'blob', fileHash: '312' },
+ };
state.diffs.diffFiles.push({ file_hash: '312' });
});
@@ -671,7 +675,10 @@ describe('diffs/components/app', () => {
it('sets previous button as disabled', async () => {
createComponent({ fileByFileUserPreference: true }, ({ state }) => {
- state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
+ state.diffs.treeEntries = {
+ 123: { type: 'blob', fileHash: '123' },
+ 312: { type: 'blob', fileHash: '312' },
+ };
});
await nextTick();
@@ -682,7 +689,10 @@ describe('diffs/components/app', () => {
it('sets next button as disabled', async () => {
createComponent({ fileByFileUserPreference: true }, ({ state }) => {
- state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
+ state.diffs.treeEntries = {
+ 123: { type: 'blob', fileHash: '123' },
+ 312: { type: 'blob', fileHash: '312' },
+ };
state.diffs.currentDiffFileId = '312';
});
@@ -694,7 +704,7 @@ describe('diffs/components/app', () => {
it("doesn't display when there's fewer than 2 files", async () => {
createComponent({ fileByFileUserPreference: true }, ({ state }) => {
- state.diffs.diffFiles.push({ file_hash: '123' });
+ state.diffs.treeEntries = { 123: { type: 'blob', fileHash: '123' } };
state.diffs.currentDiffFileId = '123';
});
@@ -711,7 +721,10 @@ describe('diffs/components/app', () => {
'calls navigateToDiffFileIndex with $index when $link is clicked',
async ({ currentDiffFileId, targetFile }) => {
createComponent({ fileByFileUserPreference: true }, ({ state }) => {
- state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
+ state.diffs.treeEntries = {
+ 123: { type: 'blob', fileHash: '123' },
+ 312: { type: 'blob', fileHash: '312' },
+ };
state.diffs.currentDiffFileId = currentDiffFileId;
});
diff --git a/spec/frontend/diffs/components/collapsed_files_warning_spec.js b/spec/frontend/diffs/components/collapsed_files_warning_spec.js
index eca5b536a35..ae40f6c898d 100644
--- a/spec/frontend/diffs/components/collapsed_files_warning_spec.js
+++ b/spec/frontend/diffs/components/collapsed_files_warning_spec.js
@@ -45,10 +45,6 @@ describe('CollapsedFilesWarning', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when there is more than one file', () => {
it.each`
present | dismissed
diff --git a/spec/frontend/diffs/components/commit_item_spec.js b/spec/frontend/diffs/components/commit_item_spec.js
index 08be3fa2745..4b4b6351d3f 100644
--- a/spec/frontend/diffs/components/commit_item_spec.js
+++ b/spec/frontend/diffs/components/commit_item_spec.js
@@ -41,11 +41,6 @@ describe('diffs/components/commit_item', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('default state', () => {
beforeEach(() => {
mountComponent();
diff --git a/spec/frontend/diffs/components/compare_dropdown_layout_spec.js b/spec/frontend/diffs/components/compare_dropdown_layout_spec.js
index 09128b04caa..785ff537777 100644
--- a/spec/frontend/diffs/components/compare_dropdown_layout_spec.js
+++ b/spec/frontend/diffs/components/compare_dropdown_layout_spec.js
@@ -38,11 +38,6 @@ describe('CompareDropdownLayout', () => {
isActive: listItem.classes().includes('is-active'),
}));
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('with versions', () => {
beforeEach(() => {
const versions = [
diff --git a/spec/frontend/diffs/components/compare_versions_spec.js b/spec/frontend/diffs/components/compare_versions_spec.js
index 21f3ee26bf8..23da1a3601b 100644
--- a/spec/frontend/diffs/components/compare_versions_spec.js
+++ b/spec/frontend/diffs/components/compare_versions_spec.js
@@ -58,11 +58,6 @@ describe('CompareVersions', () => {
store.state.diffs.mergeRequestDiffs = diffsMockData;
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('template', () => {
beforeEach(() => {
createWrapper({}, {}, false);
diff --git a/spec/frontend/diffs/components/diff_code_quality_spec.js b/spec/frontend/diffs/components/diff_code_quality_spec.js
index 7bd9afab648..e5ca90eb7c8 100644
--- a/spec/frontend/diffs/components/diff_code_quality_spec.js
+++ b/spec/frontend/diffs/components/diff_code_quality_spec.js
@@ -11,10 +11,6 @@ const findIcon = () => wrapper.findComponent(GlIcon);
const findHeading = () => wrapper.findByTestId(`diff-codequality-findings-heading`);
describe('DiffCodeQuality', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
const createWrapper = (codeQuality, mountFunction = mountExtended) => {
return mountFunction(DiffCodeQuality, {
propsData: {
diff --git a/spec/frontend/diffs/components/diff_content_spec.js b/spec/frontend/diffs/components/diff_content_spec.js
index 0bce6451ce4..3524973278c 100644
--- a/spec/frontend/diffs/components/diff_content_spec.js
+++ b/spec/frontend/diffs/components/diff_content_spec.js
@@ -93,11 +93,6 @@ describe('DiffContent', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('with text based files', () => {
afterEach(() => {
[isParallelViewGetterMock, isInlineViewGetterMock].forEach((m) => m.mockRestore());
diff --git a/spec/frontend/diffs/components/diff_discussion_reply_spec.js b/spec/frontend/diffs/components/diff_discussion_reply_spec.js
index bf4a1a1c1f7..348439d6006 100644
--- a/spec/frontend/diffs/components/diff_discussion_reply_spec.js
+++ b/spec/frontend/diffs/components/diff_discussion_reply_spec.js
@@ -26,10 +26,6 @@ describe('DiffDiscussionReply', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('if user can reply', () => {
beforeEach(() => {
getters = {
diff --git a/spec/frontend/diffs/components/diff_discussions_spec.js b/spec/frontend/diffs/components/diff_discussions_spec.js
index 5092ae6ab6e..73d9f2d6d45 100644
--- a/spec/frontend/diffs/components/diff_discussions_spec.js
+++ b/spec/frontend/diffs/components/diff_discussions_spec.js
@@ -25,10 +25,6 @@ describe('DiffDiscussions', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
it('should have notes list', () => {
createComponent();
diff --git a/spec/frontend/diffs/components/diff_file_header_spec.js b/spec/frontend/diffs/components/diff_file_header_spec.js
index c23eb2f3d24..4515a8e8926 100644
--- a/spec/frontend/diffs/components/diff_file_header_spec.js
+++ b/spec/frontend/diffs/components/diff_file_header_spec.js
@@ -72,8 +72,6 @@ describe('DiffFileHeader component', () => {
diffHasExpandedDiscussionsResultMock,
...Object.values(mockStoreConfig.modules.diffs.actions),
].forEach((mock) => mock.mockReset());
-
- wrapper.destroy();
});
const findHeader = () => wrapper.findComponent({ ref: 'header' });
diff --git a/spec/frontend/diffs/components/diff_file_row_spec.js b/spec/frontend/diffs/components/diff_file_row_spec.js
index c5b76551fcc..66ee4e955b8 100644
--- a/spec/frontend/diffs/components/diff_file_row_spec.js
+++ b/spec/frontend/diffs/components/diff_file_row_spec.js
@@ -13,10 +13,6 @@ describe('Diff File Row component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders file row component', () => {
const sharedProps = {
level: 4,
diff --git a/spec/frontend/diffs/components/diff_file_spec.js b/spec/frontend/diffs/components/diff_file_spec.js
index ccfc36f8f16..93698396450 100644
--- a/spec/frontend/diffs/components/diff_file_spec.js
+++ b/spec/frontend/diffs/components/diff_file_spec.js
@@ -129,8 +129,6 @@ describe('DiffFile', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
axiosMock.restore();
});
@@ -222,21 +220,10 @@ describe('DiffFile', () => {
describe('computed', () => {
describe('showLocalFileReviews', () => {
- let gon;
-
function setLoggedIn(bool) {
window.gon.current_user_id = bool;
}
- beforeAll(() => {
- gon = window.gon;
- window.gon = {};
- });
-
- afterEach(() => {
- window.gon = gon;
- });
-
it.each`
loggedIn | bool
${true} | ${true}
diff --git a/spec/frontend/diffs/components/diff_gutter_avatars_spec.js b/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
index f13988fc11f..5f2b1a81b91 100644
--- a/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
+++ b/spec/frontend/diffs/components/diff_gutter_avatars_spec.js
@@ -21,10 +21,6 @@ describe('DiffGutterAvatars', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when expanded', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/diffs/components/diff_row_spec.js b/spec/frontend/diffs/components/diff_row_spec.js
index a7a95ed2f35..356c7ef925a 100644
--- a/spec/frontend/diffs/components/diff_row_spec.js
+++ b/spec/frontend/diffs/components/diff_row_spec.js
@@ -89,10 +89,6 @@ describe('DiffRow', () => {
};
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
-
- window.gon = {};
showCommentForm.mockReset();
enterdragging.mockReset();
stopdragging.mockReset();
diff --git a/spec/frontend/diffs/components/hidden_files_warning_spec.js b/spec/frontend/diffs/components/hidden_files_warning_spec.js
index bbd4f5faeec..d9359fb3c7b 100644
--- a/spec/frontend/diffs/components/hidden_files_warning_spec.js
+++ b/spec/frontend/diffs/components/hidden_files_warning_spec.js
@@ -23,10 +23,6 @@ describe('HiddenFilesWarning', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('has a correct plain diff URL', () => {
const plainDiffLink = wrapper.findAllComponents(GlButton).at(0);
diff --git a/spec/frontend/diffs/components/image_diff_overlay_spec.js b/spec/frontend/diffs/components/image_diff_overlay_spec.js
index ccf942bdcef..18901781587 100644
--- a/spec/frontend/diffs/components/image_diff_overlay_spec.js
+++ b/spec/frontend/diffs/components/image_diff_overlay_spec.js
@@ -36,10 +36,6 @@ describe('Diffs image diff overlay component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders comment badges', () => {
createComponent();
diff --git a/spec/frontend/diffs/components/merge_conflict_warning_spec.js b/spec/frontend/diffs/components/merge_conflict_warning_spec.js
index 4e47249f5b4..715912b361f 100644
--- a/spec/frontend/diffs/components/merge_conflict_warning_spec.js
+++ b/spec/frontend/diffs/components/merge_conflict_warning_spec.js
@@ -25,10 +25,6 @@ describe('MergeConflictWarning', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
present | resolutionPath
${false} | ${''}
diff --git a/spec/frontend/diffs/components/no_changes_spec.js b/spec/frontend/diffs/components/no_changes_spec.js
index dbfe9770e07..e637b1dd43d 100644
--- a/spec/frontend/diffs/components/no_changes_spec.js
+++ b/spec/frontend/diffs/components/no_changes_spec.js
@@ -34,11 +34,6 @@ describe('Diff no changes empty state', () => {
store.state.diffs.mergeRequestDiffs = diffsMockData;
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findMessage = () => wrapper.find('[data-testid="no-changes-message"]');
it('prevents XSS', () => {
diff --git a/spec/frontend/diffs/components/settings_dropdown_spec.js b/spec/frontend/diffs/components/settings_dropdown_spec.js
index 2ec11ba86fd..3d2bbe43746 100644
--- a/spec/frontend/diffs/components/settings_dropdown_spec.js
+++ b/spec/frontend/diffs/components/settings_dropdown_spec.js
@@ -39,7 +39,6 @@ describe('Diff settings dropdown component', () => {
afterEach(() => {
store.dispatch.mockRestore();
- wrapper.destroy();
});
describe('tree view buttons', () => {
diff --git a/spec/frontend/diffs/components/tree_list_spec.js b/spec/frontend/diffs/components/tree_list_spec.js
index 1656eaf8ba0..87c638d065a 100644
--- a/spec/frontend/diffs/components/tree_list_spec.js
+++ b/spec/frontend/diffs/components/tree_list_spec.js
@@ -1,20 +1,36 @@
-import { shallowMount, mount } from '@vue/test-utils';
+import { shallowMount } 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';
-import FileTree from '~/vue_shared/components/file_tree.vue';
+import DiffFileRow from '~/diffs/components//diff_file_row.vue';
+import { stubComponent } from 'helpers/stub_component';
describe('Diffs tree list component', () => {
let wrapper;
let store;
- const getFileRows = () => wrapper.findAll('.file-row');
+ const getScroller = () => wrapper.findComponent({ name: 'RecycleScroller' });
+ const getFileRow = () => wrapper.findComponent(DiffFileRow);
Vue.use(Vuex);
- const createComponent = (mountFn = mount) => {
- wrapper = mountFn(TreeList, {
+ const createComponent = () => {
+ wrapper = shallowMount(TreeList, {
store,
propsData: { hideFileStats: false },
+ stubs: {
+ // eslint will fail if we import the real component
+ RecycleScroller: stubComponent(
+ {
+ name: 'RecycleScroller',
+ props: {
+ items: null,
+ },
+ },
+ {
+ template: '<div><slot :item="{ tree: [] }"></slot></div>',
+ },
+ ),
+ },
});
};
@@ -80,10 +96,6 @@ describe('Diffs tree list component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
beforeEach(() => {
createComponent();
@@ -101,26 +113,32 @@ describe('Diffs tree list component', () => {
});
describe('search by file extension', () => {
+ it('hides scroller for no matches', async () => {
+ wrapper.find('[data-testid="diff-tree-search"]').setValue('*.md');
+
+ await nextTick();
+
+ expect(getScroller().exists()).toBe(false);
+ expect(wrapper.text()).toContain('No files found');
+ });
+
it.each`
extension | itemSize
- ${'*.md'} | ${0}
- ${'*.js'} | ${1}
- ${'index.js'} | ${1}
- ${'app/*.js'} | ${1}
- ${'*.js, *.rb'} | ${2}
+ ${'*.js'} | ${2}
+ ${'index.js'} | ${2}
+ ${'app/*.js'} | ${2}
+ ${'*.js, *.rb'} | ${3}
`('returns $itemSize item for $extension', async ({ extension, itemSize }) => {
wrapper.find('[data-testid="diff-tree-search"]').setValue(extension);
await nextTick();
- expect(getFileRows()).toHaveLength(itemSize);
+ expect(getScroller().props('items')).toHaveLength(itemSize);
});
});
it('renders tree', () => {
- expect(getFileRows()).toHaveLength(2);
- expect(getFileRows().at(0).html()).toContain('index.js');
- expect(getFileRows().at(1).html()).toContain('app');
+ expect(getScroller().props('items')).toHaveLength(2);
});
it('hides file stats', async () => {
@@ -133,33 +151,16 @@ describe('Diffs tree list component', () => {
it('calls toggleTreeOpen when clicking folder', () => {
jest.spyOn(wrapper.vm.$store, 'dispatch').mockReturnValue(undefined);
- getFileRows().at(1).trigger('click');
+ getFileRow().vm.$emit('toggleTreeOpen', 'app');
expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('diffs/toggleTreeOpen', 'app');
});
- it('calls scrollToFile when clicking blob', () => {
- jest.spyOn(wrapper.vm.$store, 'dispatch').mockReturnValue(undefined);
-
- wrapper.find('.file-row').trigger('click');
-
- expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith('diffs/scrollToFile', {
- path: 'app/index.js',
- });
- });
-
- it('renders as file list when renderTreeList is false', async () => {
- wrapper.vm.$store.state.diffs.renderTreeList = false;
-
- await nextTick();
- expect(getFileRows()).toHaveLength(2);
- });
-
- it('renders file paths when renderTreeList is false', async () => {
+ it('renders when renderTreeList is false', async () => {
wrapper.vm.$store.state.diffs.renderTreeList = false;
await nextTick();
- expect(wrapper.find('.file-row').html()).toContain('index.js');
+ expect(getScroller().props('items')).toHaveLength(3);
});
});
@@ -172,12 +173,10 @@ describe('Diffs tree list component', () => {
});
it('passes the viewedDiffFileIds to the FileTree', async () => {
- createComponent(shallowMount);
+ createComponent();
await nextTick();
- // Have to use $attrs['viewed-files'] because we are passing down an object
- // and attributes('') stringifies values (e.g. [object])...
- expect(wrapper.findComponent(FileTree).vm.$attrs['viewed-files']).toBe(viewedDiffFileIds);
+ expect(wrapper.findComponent(DiffFileRow).props('viewedFiles')).toBe(viewedDiffFileIds);
});
});
});
diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js
index 78765204322..b00076504e3 100644
--- a/spec/frontend/diffs/store/actions_spec.js
+++ b/spec/frontend/diffs/store/actions_spec.js
@@ -13,7 +13,7 @@ import * as diffActions from '~/diffs/store/actions';
import * as types from '~/diffs/store/mutation_types';
import * as utils from '~/diffs/store/utils';
import * as treeWorkerUtils from '~/diffs/utils/tree_worker_utils';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import * as commonUtils from '~/lib/utils/common_utils';
import {
@@ -26,7 +26,7 @@ import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '~/notes/event_hub';
import { diffMetadata } from '../mock_data/diff_metadata';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('DiffsStoreActions', () => {
let mock;
@@ -69,6 +69,7 @@ describe('DiffsStoreActions', () => {
const endpoint = '/diffs/set/endpoint';
const endpointMetadata = '/diffs/set/endpoint/metadata';
const endpointBatch = '/diffs/set/endpoint/batch';
+ const endpointDiffForPath = '/diffs/set/endpoint/path';
const endpointCoverage = '/diffs/set/coverage_reports';
const projectPath = '/root/project';
const dismissEndpoint = '/-/user_callouts';
@@ -83,6 +84,7 @@ describe('DiffsStoreActions', () => {
{
endpoint,
endpointBatch,
+ endpointDiffForPath,
endpointMetadata,
endpointCoverage,
projectPath,
@@ -93,6 +95,7 @@ describe('DiffsStoreActions', () => {
{
endpoint: '',
endpointBatch: '',
+ endpointDiffForPath: '',
endpointMetadata: '',
endpointCoverage: '',
projectPath: '',
@@ -106,6 +109,7 @@ describe('DiffsStoreActions', () => {
endpoint,
endpointMetadata,
endpointBatch,
+ endpointDiffForPath,
endpointCoverage,
projectPath,
dismissEndpoint,
@@ -236,13 +240,17 @@ describe('DiffsStoreActions', () => {
it('should show no warning on any other status code', async () => {
mock.onGet(endpointMetadata).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
- await testAction(
- diffActions.fetchDiffFilesMeta,
- {},
- { endpointMetadata, diffViewType: 'inline', showWhitespace: true },
- [{ type: types.SET_LOADING, payload: true }],
- [],
- );
+ try {
+ await testAction(
+ diffActions.fetchDiffFilesMeta,
+ {},
+ { endpointMetadata, diffViewType: 'inline', showWhitespace: true },
+ [{ type: types.SET_LOADING, payload: true }],
+ [],
+ );
+ } catch (error) {
+ expect(error.response.status).toBe(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+ }
expect(createAlert).not.toHaveBeenCalled();
});
@@ -265,7 +273,7 @@ describe('DiffsStoreActions', () => {
);
});
- it('should show flash on API error', async () => {
+ it('should show alert on API error', async () => {
mock.onGet(endpointCoverage).reply(HTTP_STATUS_BAD_REQUEST);
await testAction(diffActions.fetchCoverageFiles, {}, { endpointCoverage }, [], []);
@@ -389,7 +397,7 @@ describe('DiffsStoreActions', () => {
return testAction(
diffActions.assignDiscussionsToDiff,
[],
- { diffFiles: [] },
+ { diffFiles: [], flatBlobsList: [] },
[],
[{ type: 'setCurrentDiffFileIdFromNote', payload: '123' }],
);
@@ -1007,20 +1015,14 @@ describe('DiffsStoreActions', () => {
describe('setShowWhitespace', () => {
const endpointUpdateUser = 'user/prefs';
let putSpy;
- let gon;
beforeEach(() => {
putSpy = jest.spyOn(axios, 'put');
- gon = window.gon;
mock.onPut(endpointUpdateUser).reply(HTTP_STATUS_OK, {});
jest.spyOn(eventHub, '$emit').mockImplementation();
});
- afterEach(() => {
- window.gon = gon;
- });
-
it('commits SET_SHOW_WHITESPACE', () => {
return testAction(
diffActions.setShowWhitespace,
@@ -1393,39 +1395,38 @@ describe('DiffsStoreActions', () => {
describe('setCurrentDiffFileIdFromNote', () => {
it('commits SET_CURRENT_DIFF_FILE', () => {
const commit = jest.fn();
- const state = { diffFiles: [{ file_hash: '123' }] };
+ const getters = { flatBlobsList: [{ fileHash: '123' }] };
const rootGetters = {
getDiscussion: () => ({ diff_file: { file_hash: '123' } }),
notesById: { 1: { discussion_id: '2' } },
};
- diffActions.setCurrentDiffFileIdFromNote({ commit, state, rootGetters }, '1');
+ diffActions.setCurrentDiffFileIdFromNote({ commit, getters, rootGetters }, '1');
expect(commit).toHaveBeenCalledWith(types.SET_CURRENT_DIFF_FILE, '123');
});
it('does not commit SET_CURRENT_DIFF_FILE when discussion has no diff_file', () => {
const commit = jest.fn();
- const state = { diffFiles: [{ file_hash: '123' }] };
const rootGetters = {
getDiscussion: () => ({ id: '1' }),
notesById: { 1: { discussion_id: '2' } },
};
- diffActions.setCurrentDiffFileIdFromNote({ commit, state, rootGetters }, '1');
+ diffActions.setCurrentDiffFileIdFromNote({ commit, rootGetters }, '1');
expect(commit).not.toHaveBeenCalled();
});
it('does not commit SET_CURRENT_DIFF_FILE when diff file does not exist', () => {
const commit = jest.fn();
- const state = { diffFiles: [{ file_hash: '123' }] };
+ const getters = { flatBlobsList: [{ fileHash: '123' }] };
const rootGetters = {
getDiscussion: () => ({ diff_file: { file_hash: '124' } }),
notesById: { 1: { discussion_id: '2' } },
};
- diffActions.setCurrentDiffFileIdFromNote({ commit, state, rootGetters }, '1');
+ diffActions.setCurrentDiffFileIdFromNote({ commit, getters, rootGetters }, '1');
expect(commit).not.toHaveBeenCalled();
});
@@ -1436,7 +1437,7 @@ describe('DiffsStoreActions', () => {
return testAction(
diffActions.navigateToDiffFileIndex,
0,
- { diffFiles: [{ file_hash: '123' }] },
+ { flatBlobsList: [{ fileHash: '123' }] },
[{ type: types.SET_CURRENT_DIFF_FILE, payload: '123' }],
[],
);
diff --git a/spec/frontend/diffs/store/getters_spec.js b/spec/frontend/diffs/store/getters_spec.js
index 2e3a66d5b01..ed7b6699e2c 100644
--- a/spec/frontend/diffs/store/getters_spec.js
+++ b/spec/frontend/diffs/store/getters_spec.js
@@ -288,6 +288,19 @@ describe('Diffs Module Getters', () => {
});
});
+ describe('isTreePathLoaded', () => {
+ it.each`
+ desc | loaded | path | bool
+ ${'the file exists and has been loaded'} | ${true} | ${'path/tofile'} | ${true}
+ ${'the file exists and has not been loaded'} | ${false} | ${'path/tofile'} | ${false}
+ ${'the file does not exist'} | ${false} | ${'tofile/path'} | ${false}
+ `('returns $bool when $desc', ({ loaded, path, bool }) => {
+ localState.treeEntries['path/tofile'] = { diffLoaded: loaded };
+
+ expect(getters.isTreePathLoaded(localState)(path)).toBe(bool);
+ });
+ });
+
describe('allBlobs', () => {
it('returns an array of blobs', () => {
localState.treeEntries = {
@@ -328,7 +341,11 @@ describe('Diffs Module Getters', () => {
describe('currentDiffIndex', () => {
it('returns index of currently selected diff in diffList', () => {
- localState.diffFiles = [{ file_hash: '111' }, { file_hash: '222' }, { file_hash: '333' }];
+ localState.treeEntries = [
+ { type: 'blob', fileHash: '111' },
+ { type: 'blob', fileHash: '222' },
+ { type: 'blob', fileHash: '333' },
+ ];
localState.currentDiffFileId = '222';
expect(getters.currentDiffIndex(localState)).toEqual(1);
@@ -339,7 +356,11 @@ describe('Diffs Module Getters', () => {
});
it('returns 0 if no diff is selected yet or diff is not found', () => {
- localState.diffFiles = [{ file_hash: '111' }, { file_hash: '222' }, { file_hash: '333' }];
+ localState.treeEntries = [
+ { type: 'blob', fileHash: '111' },
+ { type: 'blob', fileHash: '222' },
+ { type: 'blob', fileHash: '333' },
+ ];
localState.currentDiffFileId = '';
expect(getters.currentDiffIndex(localState)).toEqual(0);
diff --git a/spec/frontend/diffs/store/mutations_spec.js b/spec/frontend/diffs/store/mutations_spec.js
index 031e4fe2be2..ed8d7397bbc 100644
--- a/spec/frontend/diffs/store/mutations_spec.js
+++ b/spec/frontend/diffs/store/mutations_spec.js
@@ -93,15 +93,20 @@ describe('DiffsStoreMutations', () => {
describe('SET_DIFF_DATA_BATCH_DATA', () => {
it('should set diff data batch type properly', () => {
- const state = { diffFiles: [] };
+ const mockFile = getDiffFileMock();
+ const state = {
+ diffFiles: [],
+ treeEntries: { [mockFile.file_path]: { fileHash: mockFile.file_hash } },
+ };
const diffMock = {
- diff_files: [getDiffFileMock()],
+ diff_files: [mockFile],
};
mutations[types.SET_DIFF_DATA_BATCH](state, diffMock);
expect(state.diffFiles[0].renderIt).toEqual(true);
expect(state.diffFiles[0].collapsed).toEqual(false);
+ expect(state.treeEntries[mockFile.file_path].diffLoaded).toBe(true);
});
});
diff --git a/spec/frontend/diffs/store/utils_spec.js b/spec/frontend/diffs/store/utils_spec.js
index b5c44b084d8..4760a8b7166 100644
--- a/spec/frontend/diffs/store/utils_spec.js
+++ b/spec/frontend/diffs/store/utils_spec.js
@@ -892,4 +892,61 @@ describe('DiffsStoreUtils', () => {
expect(files[6].right).toBeNull();
});
});
+
+ describe('isUrlHashNoteLink', () => {
+ it.each`
+ input | bool
+ ${'#note_12345'} | ${true}
+ ${'#12345'} | ${false}
+ ${'note_12345'} | ${true}
+ ${'12345'} | ${false}
+ `('returns $bool for $input', ({ bool, input }) => {
+ expect(utils.isUrlHashNoteLink(input)).toBe(bool);
+ });
+ });
+
+ describe('isUrlHashFileHeader', () => {
+ it.each`
+ input | bool
+ ${'#diff-content-12345'} | ${true}
+ ${'#12345'} | ${false}
+ ${'diff-content-12345'} | ${true}
+ ${'12345'} | ${false}
+ `('returns $bool for $input', ({ bool, input }) => {
+ expect(utils.isUrlHashFileHeader(input)).toBe(bool);
+ });
+ });
+
+ describe('parseUrlHashAsFileHash', () => {
+ it.each`
+ input | currentDiffId | resultId
+ ${'#note_12345'} | ${'1A2B3C'} | ${'1A2B3C'}
+ ${'note_12345'} | ${'1A2B3C'} | ${'1A2B3C'}
+ ${'#note_12345'} | ${undefined} | ${null}
+ ${'note_12345'} | ${undefined} | ${null}
+ ${'#diff-content-12345'} | ${undefined} | ${'12345'}
+ ${'diff-content-12345'} | ${undefined} | ${'12345'}
+ ${'#diff-content-12345'} | ${'98765'} | ${'12345'}
+ ${'diff-content-12345'} | ${'98765'} | ${'12345'}
+ ${'#e334a2a10f036c00151a04cea7938a5d4213a818'} | ${undefined} | ${'e334a2a10f036c00151a04cea7938a5d4213a818'}
+ ${'e334a2a10f036c00151a04cea7938a5d4213a818'} | ${undefined} | ${'e334a2a10f036c00151a04cea7938a5d4213a818'}
+ ${'#Z334a2a10f036c00151a04cea7938a5d4213a818'} | ${undefined} | ${null}
+ ${'Z334a2a10f036c00151a04cea7938a5d4213a818'} | ${undefined} | ${null}
+ `('returns $resultId for $input and $currentDiffId', ({ input, currentDiffId, resultId }) => {
+ expect(utils.parseUrlHashAsFileHash(input, currentDiffId)).toBe(resultId);
+ });
+ });
+
+ describe('markTreeEntriesLoaded', () => {
+ it.each`
+ desc | entries | loaded | outcome
+ ${'marks an existing entry as loaded'} | ${{ abc: {} }} | ${[{ new_path: 'abc' }]} | ${{ abc: { diffLoaded: true } }}
+ ${'does nothing if the new file is not found in the tree entries'} | ${{ abc: {} }} | ${[{ new_path: 'def' }]} | ${{ abc: {} }}
+ ${'leaves entries unmodified if they are not in the loaded files'} | ${{ abc: {}, def: { diffLoaded: true }, ghi: {} }} | ${[{ new_path: 'ghi' }]} | ${{ abc: {}, def: { diffLoaded: true }, ghi: { diffLoaded: true } }}
+ `('$desc', ({ entries, loaded, outcome }) => {
+ expect(utils.markTreeEntriesLoaded({ priorEntries: entries, loadedFiles: loaded })).toEqual(
+ outcome,
+ );
+ });
+ });
});
diff --git a/spec/frontend/diffs/utils/tree_worker_utils_spec.js b/spec/frontend/diffs/utils/tree_worker_utils_spec.js
index 4df5fe75004..b8bd4fcd081 100644
--- a/spec/frontend/diffs/utils/tree_worker_utils_spec.js
+++ b/spec/frontend/diffs/utils/tree_worker_utils_spec.js
@@ -75,8 +75,13 @@ describe('~/diffs/utils/tree_worker_utils', () => {
{
addedLines: 0,
changed: true,
+ diffLoaded: false,
deleted: false,
fileHash: 'test',
+ filePaths: {
+ new: 'app/index.js',
+ old: undefined,
+ },
key: 'app/index.js',
name: 'index.js',
parentPath: 'app/',
@@ -97,8 +102,13 @@ describe('~/diffs/utils/tree_worker_utils', () => {
{
addedLines: 0,
changed: true,
+ diffLoaded: false,
deleted: false,
fileHash: 'test',
+ filePaths: {
+ new: 'app/test/index.js',
+ old: undefined,
+ },
key: 'app/test/index.js',
name: 'index.js',
parentPath: 'app/test/',
@@ -112,8 +122,13 @@ describe('~/diffs/utils/tree_worker_utils', () => {
{
addedLines: 0,
changed: true,
+ diffLoaded: false,
deleted: false,
fileHash: 'test',
+ filePaths: {
+ new: 'app/test/filepathneedstruncating.js',
+ old: undefined,
+ },
key: 'app/test/filepathneedstruncating.js',
name: 'filepathneedstruncating.js',
parentPath: 'app/test/',
@@ -138,8 +153,13 @@ describe('~/diffs/utils/tree_worker_utils', () => {
{
addedLines: 42,
changed: true,
+ diffLoaded: false,
deleted: false,
fileHash: 'test',
+ filePaths: {
+ new: 'constructor/test/aFile.js',
+ old: undefined,
+ },
key: 'constructor/test/aFile.js',
name: 'aFile.js',
parentPath: 'constructor/test/',
@@ -160,10 +180,15 @@ describe('~/diffs/utils/tree_worker_utils', () => {
name: 'submodule @ abcdef123',
type: 'blob',
changed: true,
+ diffLoaded: false,
tempFile: true,
submodule: true,
deleted: false,
fileHash: 'test',
+ filePaths: {
+ new: 'submodule @ abcdef123',
+ old: undefined,
+ },
addedLines: 1,
removedLines: 0,
tree: [],
@@ -175,10 +200,15 @@ describe('~/diffs/utils/tree_worker_utils', () => {
name: 'package.json',
type: 'blob',
changed: true,
+ diffLoaded: false,
tempFile: false,
submodule: undefined,
deleted: true,
fileHash: 'test',
+ filePaths: {
+ new: 'package.json',
+ old: undefined,
+ },
addedLines: 0,
removedLines: 0,
tree: [],
diff --git a/spec/frontend/drawio/content_editor_facade_spec.js b/spec/frontend/drawio/content_editor_facade_spec.js
new file mode 100644
index 00000000000..673968bac9f
--- /dev/null
+++ b/spec/frontend/drawio/content_editor_facade_spec.js
@@ -0,0 +1,138 @@
+import AxiosMockAdapter from 'axios-mock-adapter';
+import { create } from '~/drawio/content_editor_facade';
+import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
+import DrawioDiagram from '~/content_editor/extensions/drawio_diagram';
+import axios from '~/lib/utils/axios_utils';
+import { PROJECT_WIKI_ATTACHMENT_DRAWIO_DIAGRAM_HTML } from '../content_editor/test_constants';
+import { createTestEditor } from '../content_editor/test_utils';
+
+describe('drawio/contentEditorFacade', () => {
+ let tiptapEditor;
+ let axiosMock;
+ let contentEditorFacade;
+ let assetResolver;
+ const imageURL = '/group1/project1/-/wikis/test-file.drawio.svg';
+ const diagramSvg = '<svg></svg>';
+ const contentType = 'image/svg+xml';
+ const filename = 'test-file.drawio.svg';
+ const uploadsPath = '/uploads';
+ const canonicalSrc = '/new-diagram.drawio.svg';
+ const src = `/uploads${canonicalSrc}`;
+
+ beforeEach(() => {
+ assetResolver = {
+ resolveUrl: jest.fn(),
+ };
+ tiptapEditor = createTestEditor({ extensions: [DrawioDiagram] });
+ contentEditorFacade = create({
+ tiptapEditor,
+ drawioNodeName: DrawioDiagram.name,
+ uploadsPath,
+ assetResolver,
+ });
+ });
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ });
+
+ afterEach(() => {
+ axiosMock.restore();
+ tiptapEditor.destroy();
+ });
+
+ describe('getDiagram', () => {
+ describe('when there is a selected diagram', () => {
+ beforeEach(() => {
+ tiptapEditor
+ .chain()
+ .setContent(PROJECT_WIKI_ATTACHMENT_DRAWIO_DIAGRAM_HTML)
+ .setNodeSelection(1)
+ .run();
+ axiosMock
+ .onGet(imageURL)
+ .reply(HTTP_STATUS_OK, diagramSvg, { 'content-type': contentType });
+ });
+
+ it('returns diagram information', async () => {
+ const diagram = await contentEditorFacade.getDiagram();
+
+ expect(diagram).toEqual({
+ diagramURL: imageURL,
+ filename,
+ diagramSvg,
+ contentType,
+ });
+ });
+ });
+
+ describe('when there is not a selected diagram', () => {
+ beforeEach(() => {
+ tiptapEditor.chain().setContent('<p>text</p>').setNodeSelection(1).run();
+ });
+
+ it('returns null', async () => {
+ const diagram = await contentEditorFacade.getDiagram();
+
+ expect(diagram).toBe(null);
+ });
+ });
+ });
+
+ describe('updateDiagram', () => {
+ beforeEach(() => {
+ tiptapEditor
+ .chain()
+ .setContent(PROJECT_WIKI_ATTACHMENT_DRAWIO_DIAGRAM_HTML)
+ .setNodeSelection(1)
+ .run();
+
+ assetResolver.resolveUrl.mockReturnValueOnce(src);
+ contentEditorFacade.updateDiagram({ uploadResults: { file_path: canonicalSrc } });
+ });
+
+ it('updates selected diagram diagram node src and canonicalSrc', () => {
+ tiptapEditor.commands.setNodeSelection(1);
+ expect(tiptapEditor.state.selection.node.attrs).toMatchObject({
+ src,
+ canonicalSrc,
+ });
+ });
+ });
+
+ describe('insertDiagram', () => {
+ beforeEach(() => {
+ tiptapEditor.chain().setContent('<p></p>').run();
+
+ assetResolver.resolveUrl.mockReturnValueOnce(src);
+ contentEditorFacade.insertDiagram({ uploadResults: { file_path: canonicalSrc } });
+ });
+
+ it('inserts a new draw.io diagram in the document', () => {
+ tiptapEditor.commands.setNodeSelection(1);
+ expect(tiptapEditor.state.selection.node.attrs).toMatchObject({
+ src,
+ canonicalSrc,
+ });
+ });
+ });
+
+ describe('uploadDiagram', () => {
+ it('sends a post request to the uploadsPath containing the diagram svg', async () => {
+ const link = { markdown: '![](diagram.drawio.svg)' };
+ const blob = new Blob([diagramSvg], { type: 'image/svg+xml' });
+ const formData = new FormData();
+
+ formData.append('file', blob, filename);
+
+ axiosMock.onPost(uploadsPath, formData).reply(HTTP_STATUS_OK, {
+ data: {
+ link,
+ },
+ });
+
+ const response = await contentEditorFacade.uploadDiagram({ diagramSvg, filename });
+
+ expect(response).not.toBe(link);
+ });
+ });
+});
diff --git a/spec/frontend/drawio/drawio_editor_spec.js b/spec/frontend/drawio/drawio_editor_spec.js
new file mode 100644
index 00000000000..5ef26c04204
--- /dev/null
+++ b/spec/frontend/drawio/drawio_editor_spec.js
@@ -0,0 +1,479 @@
+import { launchDrawioEditor } from '~/drawio/drawio_editor';
+import {
+ DRAWIO_EDITOR_URL,
+ DRAWIO_FRAME_ID,
+ DIAGRAM_BACKGROUND_COLOR,
+ DRAWIO_IFRAME_TIMEOUT,
+ DIAGRAM_MAX_SIZE,
+} from '~/drawio/constants';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
+
+jest.mock('~/alert');
+
+jest.useFakeTimers();
+
+describe('drawio/drawio_editor', () => {
+ let editorFacade;
+ let drawioIFrameReceivedMessages;
+ const diagramURL = `${window.location.origin}/uploads/diagram.drawio.svg`;
+ const testSvg = '<svg></svg>';
+ const testEncodedSvg = `data:image/svg+xml;base64,${btoa(testSvg)}`;
+ const filename = 'diagram.drawio.svg';
+
+ const findDrawioIframe = () => document.getElementById(DRAWIO_FRAME_ID);
+ const waitForDrawioIFrameMessage = ({ messageNumber = 1 } = {}) =>
+ new Promise((resolve) => {
+ let messageCounter = 0;
+ const iframe = findDrawioIframe();
+
+ iframe?.contentWindow.addEventListener('message', (event) => {
+ drawioIFrameReceivedMessages.push(event);
+
+ messageCounter += 1;
+
+ if (messageCounter === messageNumber) {
+ resolve();
+ }
+ });
+ });
+ const expectDrawioIframeMessage = ({ expectation, messageNumber = 1 }) => {
+ expect(drawioIFrameReceivedMessages).toHaveLength(messageNumber);
+ expect(JSON.parse(drawioIFrameReceivedMessages[messageNumber - 1].data)).toEqual(expectation);
+ };
+ const postMessageToParentWindow = (data) => {
+ const event = new Event('message');
+
+ Object.setPrototypeOf(event, {
+ source: findDrawioIframe().contentWindow,
+ data: JSON.stringify(data),
+ });
+
+ window.dispatchEvent(event);
+ };
+
+ beforeEach(() => {
+ editorFacade = {
+ getDiagram: jest.fn(),
+ uploadDiagram: jest.fn(),
+ insertDiagram: jest.fn(),
+ updateDiagram: jest.fn(),
+ };
+ drawioIFrameReceivedMessages = [];
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ findDrawioIframe()?.remove();
+ });
+
+ describe('initializing', () => {
+ beforeEach(() => {
+ launchDrawioEditor({ editorFacade });
+ });
+
+ it('creates the drawio editor iframe and attaches it to the body', () => {
+ expect(findDrawioIframe().getAttribute('src')).toBe(DRAWIO_EDITOR_URL);
+ });
+
+ it('sets drawio-editor classname to the iframe', () => {
+ expect(findDrawioIframe().classList).toContain('drawio-editor');
+ });
+ });
+
+ describe(`when parent window does not receive configure event after ${DRAWIO_IFRAME_TIMEOUT} ms`, () => {
+ beforeEach(() => {
+ launchDrawioEditor({ editorFacade });
+ });
+
+ it('disposes draw.io iframe', () => {
+ expect(findDrawioIframe()).not.toBe(null);
+ jest.runAllTimers();
+ expect(findDrawioIframe()).toBe(null);
+ });
+
+ it('displays an alert indicating that the draw.io editor could not be loaded', () => {
+ jest.runAllTimers();
+
+ expect(createAlert).toHaveBeenCalledWith({
+ message: 'The diagrams.net editor could not be loaded.',
+ });
+ });
+ });
+
+ describe('when parent window receives configure event', () => {
+ beforeEach(async () => {
+ launchDrawioEditor({ editorFacade });
+ postMessageToParentWindow({ event: 'configure' });
+
+ await waitForDrawioIFrameMessage();
+ });
+
+ it('sends configure action to the draw.io iframe', async () => {
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'configure',
+ config: {
+ darkColor: '#202020',
+ settingsName: 'gitlab',
+ },
+ colorSchemeMeta: false,
+ },
+ });
+ });
+
+ it('does not remove the iframe after the load error timeouts run', async () => {
+ jest.runAllTimers();
+
+ expect(findDrawioIframe()).not.toBe(null);
+ });
+ });
+
+ describe('when parent window receives init event', () => {
+ describe('when there isn’t a diagram selected', () => {
+ beforeEach(() => {
+ editorFacade.getDiagram.mockResolvedValueOnce(null);
+
+ launchDrawioEditor({ editorFacade });
+
+ postMessageToParentWindow({ event: 'init' });
+ });
+
+ it('sends load action to the draw.io iframe with empty svg and title', async () => {
+ await waitForDrawioIFrameMessage();
+
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'load',
+ xml: null,
+ border: 8,
+ background: DIAGRAM_BACKGROUND_COLOR,
+ dark: false,
+ title: null,
+ },
+ });
+ });
+ });
+
+ describe('when there is a diagram selected', () => {
+ const diagramSvg = '<svg></svg>';
+
+ beforeEach(() => {
+ editorFacade.getDiagram.mockResolvedValueOnce({
+ diagramURL,
+ diagramSvg,
+ filename,
+ contentType: 'image/svg+xml',
+ });
+
+ launchDrawioEditor({ editorFacade });
+ postMessageToParentWindow({ event: 'init' });
+ });
+
+ it('sends load action to the draw.io iframe with the selected diagram svg and filename', async () => {
+ await waitForDrawioIFrameMessage();
+
+ // Step 5: The draw.io editor will send the downloaded diagram to the iframe
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'load',
+ xml: diagramSvg,
+ border: 8,
+ background: DIAGRAM_BACKGROUND_COLOR,
+ dark: false,
+ title: filename,
+ },
+ });
+ });
+
+ it('sets the drawio iframe as visible and resets cursor', async () => {
+ await waitForDrawioIFrameMessage();
+
+ expect(findDrawioIframe().style.visibility).toBe('visible');
+ expect(findDrawioIframe().style.cursor).toBe('');
+ });
+
+ it('scrolls window to the top', async () => {
+ await waitForDrawioIFrameMessage();
+
+ expect(window.scrollX).toBe(0);
+ });
+ });
+
+ describe.each`
+ description | errorMessage | diagram
+ ${'when there is an image selected that is not an svg file'} | ${'The selected image is not a valid SVG diagram'} | ${{
+ diagramURL,
+ contentType: 'image/png',
+ filename: 'image.png',
+}}
+ ${'when the selected image is not an asset upload'} | ${'The selected image is not an asset uploaded in the application'} | ${{
+ diagramSvg: '<svg></svg>',
+ filename,
+ contentType: 'image/svg+xml',
+ diagramURL: 'https://example.com/image.drawio.svg',
+}}
+ ${'when the selected image is too large'} | ${'The selected image is too large.'} | ${{
+ diagramSvg: 'x'.repeat(DIAGRAM_MAX_SIZE + 1),
+ filename,
+ contentType: 'image/svg+xml',
+ diagramURL,
+}}
+ `('$description', ({ errorMessage, diagram }) => {
+ beforeEach(() => {
+ editorFacade.getDiagram.mockResolvedValueOnce(diagram);
+
+ launchDrawioEditor({ editorFacade });
+
+ postMessageToParentWindow({ event: 'init' });
+ });
+
+ it('displays an error alert indicating that the image is not a diagram', async () => {
+ expect(createAlert).toHaveBeenCalledWith({
+ message: errorMessage,
+ error: expect.any(Error),
+ });
+ });
+
+ it('disposes the draw.io diagram iframe', () => {
+ expect(findDrawioIframe()).toBe(null);
+ });
+ });
+
+ describe('when loading a diagram fails', () => {
+ beforeEach(() => {
+ editorFacade.getDiagram.mockRejectedValueOnce(new Error());
+
+ launchDrawioEditor({ editorFacade });
+
+ postMessageToParentWindow({ event: 'init' });
+ });
+
+ it('displays an error alert indicating the failure', async () => {
+ expect(createAlert).toHaveBeenCalledWith({
+ message: 'Cannot load the diagram into the diagrams.net editor',
+ error: expect.any(Error),
+ });
+ });
+
+ it('disposes the draw.io diagram iframe', () => {
+ expect(findDrawioIframe()).toBe(null);
+ });
+ });
+ });
+
+ describe('when parent window receives prompt event', () => {
+ describe('when the filename is empty', () => {
+ beforeEach(() => {
+ launchDrawioEditor({ editorFacade });
+
+ postMessageToParentWindow({ event: 'prompt', value: '' });
+ });
+
+ it('sends prompt action to the draw.io iframe requesting a filename', async () => {
+ await waitForDrawioIFrameMessage({ messageNumber: 1 });
+
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'prompt',
+ titleKey: 'filename',
+ okKey: 'save',
+ defaultValue: 'diagram.drawio.svg',
+ },
+ messageNumber: 1,
+ });
+ });
+
+ it('sends dialog action to the draw.io iframe indicating that the filename cannot be empty', async () => {
+ await waitForDrawioIFrameMessage({ messageNumber: 2 });
+
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'dialog',
+ titleKey: 'error',
+ messageKey: 'filenameShort',
+ buttonKey: 'ok',
+ },
+ messageNumber: 2,
+ });
+ });
+ });
+
+ describe('when the event data is not empty', () => {
+ beforeEach(async () => {
+ launchDrawioEditor({ editorFacade });
+ postMessageToParentWindow({ event: 'prompt', value: 'diagram.drawio.svg' });
+
+ await waitForDrawioIFrameMessage();
+ });
+
+ it('starts the saving file process', () => {
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'spinner',
+ show: true,
+ messageKey: 'saving',
+ },
+ });
+ });
+ });
+ });
+
+ describe('when parent receives export event', () => {
+ beforeEach(() => {
+ editorFacade.uploadDiagram.mockResolvedValueOnce({});
+ });
+
+ it('reloads diagram in the draw.io editor', async () => {
+ launchDrawioEditor({ editorFacade });
+ postMessageToParentWindow({ event: 'export', data: testEncodedSvg });
+
+ await waitForDrawioIFrameMessage();
+
+ expectDrawioIframeMessage({
+ expectation: expect.objectContaining({
+ action: 'load',
+ xml: expect.stringContaining(testSvg),
+ }),
+ });
+ });
+
+ it('marks the diagram as modified in the draw.io editor', async () => {
+ launchDrawioEditor({ editorFacade });
+ postMessageToParentWindow({ event: 'export', data: testEncodedSvg });
+
+ await waitForDrawioIFrameMessage({ messageNumber: 2 });
+
+ expectDrawioIframeMessage({
+ expectation: expect.objectContaining({
+ action: 'status',
+ modified: true,
+ }),
+ messageNumber: 2,
+ });
+ });
+
+ describe('when the diagram filename is set', () => {
+ const TEST_FILENAME = 'diagram.drawio.svg';
+
+ beforeEach(() => {
+ launchDrawioEditor({ editorFacade, filename: TEST_FILENAME });
+ });
+
+ it('displays loading spinner in the draw.io editor', async () => {
+ postMessageToParentWindow({ event: 'export', data: testEncodedSvg });
+
+ await waitForDrawioIFrameMessage({ messageNumber: 3 });
+
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'spinner',
+ show: true,
+ messageKey: 'saving',
+ },
+ messageNumber: 3,
+ });
+ });
+
+ it('uploads exported diagram', async () => {
+ postMessageToParentWindow({ event: 'export', data: testEncodedSvg });
+
+ await waitForDrawioIFrameMessage({ messageNumber: 3 });
+
+ expect(editorFacade.uploadDiagram).toHaveBeenCalledWith({
+ filename: TEST_FILENAME,
+ diagramSvg: expect.stringContaining(testSvg),
+ });
+ });
+
+ describe('when uploading the exported diagram succeeds', () => {
+ it('displays an alert indicating that the diagram was uploaded successfully', async () => {
+ postMessageToParentWindow({ event: 'export', data: testEncodedSvg });
+
+ await waitForDrawioIFrameMessage({ messageNumber: 3 });
+
+ expect(createAlert).toHaveBeenCalledWith({
+ message: expect.any(String),
+ variant: VARIANT_SUCCESS,
+ fadeTransition: true,
+ });
+ });
+
+ it('disposes iframe', () => {
+ jest.runAllTimers();
+
+ expect(findDrawioIframe()).toBe(null);
+ });
+ });
+
+ describe('when uploading the exported diagram fails', () => {
+ const uploadError = new Error();
+
+ beforeEach(() => {
+ editorFacade.uploadDiagram.mockReset();
+ editorFacade.uploadDiagram.mockRejectedValue(uploadError);
+
+ postMessageToParentWindow({ event: 'export', data: testEncodedSvg });
+ });
+
+ it('hides loading indicator in the draw.io editor', async () => {
+ await waitForDrawioIFrameMessage({ messageNumber: 4 });
+
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'spinner',
+ show: false,
+ },
+ messageNumber: 4,
+ });
+ });
+
+ it('displays an error dialog in the draw.io editor', async () => {
+ await waitForDrawioIFrameMessage({ messageNumber: 5 });
+
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'dialog',
+ titleKey: 'error',
+ modified: true,
+ buttonKey: 'close',
+ messageKey: 'errorSavingFile',
+ },
+ messageNumber: 5,
+ });
+ });
+ });
+ });
+
+ describe('when diagram filename is not set', () => {
+ it('sends prompt action to the draw.io iframe', async () => {
+ launchDrawioEditor({ editorFacade });
+ postMessageToParentWindow({ event: 'export', data: testEncodedSvg });
+
+ await waitForDrawioIFrameMessage({ messageNumber: 3 });
+
+ expect(drawioIFrameReceivedMessages[2].data).toEqual(
+ JSON.stringify({
+ action: 'prompt',
+ titleKey: 'filename',
+ okKey: 'save',
+ defaultValue: 'diagram.drawio.svg',
+ }),
+ );
+ });
+ });
+ });
+
+ describe('when parent window receives exit event', () => {
+ beforeEach(() => {
+ launchDrawioEditor({ editorFacade });
+ });
+
+ it('disposes the the draw.io iframe', () => {
+ expect(findDrawioIframe()).not.toBe(null);
+
+ postMessageToParentWindow({ event: 'exit' });
+
+ expect(findDrawioIframe()).toBe(null);
+ });
+ });
+});
diff --git a/spec/frontend/drawio/markdown_field_editor_facade_spec.js b/spec/frontend/drawio/markdown_field_editor_facade_spec.js
new file mode 100644
index 00000000000..e3eafc63839
--- /dev/null
+++ b/spec/frontend/drawio/markdown_field_editor_facade_spec.js
@@ -0,0 +1,148 @@
+import AxiosMockAdapter from 'axios-mock-adapter';
+import { create } from '~/drawio/markdown_field_editor_facade';
+import * as textMarkdown from '~/lib/utils/text_markdown';
+import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
+import axios from '~/lib/utils/axios_utils';
+
+jest.mock('~/lib/utils/text_markdown');
+
+describe('drawio/textareaMarkdownEditor', () => {
+ let textArea;
+ let textareaMarkdownEditor;
+ let axiosMock;
+
+ const markdownPreviewPath = '/markdown/preview';
+ const imageURL = '/assets/image.png';
+ const diagramMarkdown = '![](image.png)';
+ const diagramSvg = '<svg></svg>';
+ const contentType = 'image/svg+xml';
+ const filename = 'image.png';
+ const newDiagramMarkdown = '![](newdiagram.svg)';
+ const uploadsPath = '/uploads';
+
+ beforeEach(() => {
+ textArea = document.createElement('textarea');
+ textareaMarkdownEditor = create({ textArea, markdownPreviewPath, uploadsPath });
+
+ document.body.appendChild(textArea);
+ });
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ });
+
+ afterEach(() => {
+ axiosMock.restore();
+ textArea.remove();
+ });
+
+ describe('getDiagram', () => {
+ describe('when there is a selected diagram', () => {
+ beforeEach(() => {
+ textMarkdown.resolveSelectedImage.mockReturnValueOnce({
+ imageURL,
+ imageMarkdown: diagramMarkdown,
+ filename,
+ });
+ axiosMock
+ .onGet(imageURL)
+ .reply(HTTP_STATUS_OK, diagramSvg, { 'content-type': contentType });
+ });
+
+ it('returns diagram information', async () => {
+ const diagram = await textareaMarkdownEditor.getDiagram();
+
+ expect(textMarkdown.resolveSelectedImage).toHaveBeenCalledWith(
+ textArea,
+ markdownPreviewPath,
+ );
+
+ expect(diagram).toEqual({
+ diagramURL: imageURL,
+ diagramMarkdown,
+ filename,
+ diagramSvg,
+ contentType,
+ });
+ });
+ });
+
+ describe('when there is not a selected diagram', () => {
+ beforeEach(() => {
+ textMarkdown.resolveSelectedImage.mockReturnValueOnce(null);
+ });
+
+ it('returns null', async () => {
+ const diagram = await textareaMarkdownEditor.getDiagram();
+
+ expect(textMarkdown.resolveSelectedImage).toHaveBeenCalledWith(
+ textArea,
+ markdownPreviewPath,
+ );
+
+ expect(diagram).toBe(null);
+ });
+ });
+ });
+
+ describe('updateDiagram', () => {
+ beforeEach(() => {
+ jest.spyOn(textArea, 'focus');
+ jest.spyOn(textArea, 'dispatchEvent');
+
+ textArea.value = `diagram ${diagramMarkdown}`;
+
+ textareaMarkdownEditor.updateDiagram({
+ diagramMarkdown,
+ uploadResults: { link: { markdown: newDiagramMarkdown } },
+ });
+ });
+
+ it('focuses the textarea', () => {
+ expect(textArea.focus).toHaveBeenCalled();
+ });
+
+ it('replaces previous diagram markdown with new diagram markdown', () => {
+ expect(textArea.value).toBe(`diagram ${newDiagramMarkdown}`);
+ });
+
+ it('dispatches input event in the textarea', () => {
+ expect(textArea.dispatchEvent).toHaveBeenCalledWith(new Event('input'));
+ });
+ });
+
+ describe('insertDiagram', () => {
+ it('inserts markdown text and replaces any selected markdown in the textarea', () => {
+ textArea.value = `diagram ${diagramMarkdown}`;
+ textArea.setSelectionRange(0, 8);
+
+ textareaMarkdownEditor.insertDiagram({
+ uploadResults: { link: { markdown: newDiagramMarkdown } },
+ });
+
+ expect(textMarkdown.insertMarkdownText).toHaveBeenCalledWith({
+ textArea,
+ text: textArea.value,
+ tag: newDiagramMarkdown,
+ selected: textArea.value.substring(0, 8),
+ });
+ });
+ });
+
+ describe('uploadDiagram', () => {
+ it('sends a post request to the uploadsPath containing the diagram svg', async () => {
+ const link = { markdown: '![](diagram.drawio.svg)' };
+ const blob = new Blob([diagramSvg], { type: 'image/svg+xml' });
+ const formData = new FormData();
+
+ formData.append('file', blob, filename);
+
+ axiosMock.onPost(uploadsPath, formData).reply(HTTP_STATUS_OK, {
+ link,
+ });
+
+ const response = await textareaMarkdownEditor.uploadDiagram({ diagramSvg, filename });
+
+ expect(response).toEqual({ link });
+ });
+ });
+});
diff --git a/spec/frontend/editor/components/helpers.js b/spec/frontend/editor/components/helpers.js
index 12f90390c18..5cc66dd2ae0 100644
--- a/spec/frontend/editor/components/helpers.js
+++ b/spec/frontend/editor/components/helpers.js
@@ -1,4 +1,3 @@
-import { EDITOR_TOOLBAR_RIGHT_GROUP } from '~/editor/constants';
import { apolloProvider } from '~/editor/components/source_editor_toolbar_graphql';
import getToolbarItemsQuery from '~/editor/graphql/get_items.query.graphql';
@@ -9,7 +8,7 @@ export const buildButton = (id = 'foo-bar-btn', options = {}) => {
label: options.label || 'Foo Bar Button',
icon: options.icon || 'check',
selected: options.selected || false,
- group: options.group || EDITOR_TOOLBAR_RIGHT_GROUP,
+ group: options.group,
onClick: options.onClick || (() => {}),
category: options.category || 'primary',
selectedLabel: options.selectedLabel || 'smth',
diff --git a/spec/frontend/editor/components/source_editor_toolbar_button_spec.js b/spec/frontend/editor/components/source_editor_toolbar_button_spec.js
index ff377494312..79692ab4557 100644
--- a/spec/frontend/editor/components/source_editor_toolbar_button_spec.js
+++ b/spec/frontend/editor/components/source_editor_toolbar_button_spec.js
@@ -21,11 +21,6 @@ describe('Source Editor Toolbar button', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('default', () => {
const defaultProps = {
category: 'primary',
diff --git a/spec/frontend/editor/components/source_editor_toolbar_spec.js b/spec/frontend/editor/components/source_editor_toolbar_spec.js
index bead39ca744..f737340a317 100644
--- a/spec/frontend/editor/components/source_editor_toolbar_spec.js
+++ b/spec/frontend/editor/components/source_editor_toolbar_spec.js
@@ -5,7 +5,7 @@ import { shallowMount } from '@vue/test-utils';
import createMockApollo from 'helpers/mock_apollo_helper';
import SourceEditorToolbar from '~/editor/components/source_editor_toolbar.vue';
import SourceEditorToolbarButton from '~/editor/components/source_editor_toolbar_button.vue';
-import { EDITOR_TOOLBAR_LEFT_GROUP, EDITOR_TOOLBAR_RIGHT_GROUP } from '~/editor/constants';
+import { EDITOR_TOOLBAR_BUTTON_GROUPS } from '~/editor/constants';
import getToolbarItemsQuery from '~/editor/graphql/get_items.query.graphql';
import { buildButton } from './helpers';
@@ -40,24 +40,24 @@ describe('Source Editor Toolbar', () => {
};
afterEach(() => {
- wrapper.destroy();
mockApollo = null;
});
describe('groups', () => {
it.each`
- group | expectedGroup
- ${EDITOR_TOOLBAR_LEFT_GROUP} | ${EDITOR_TOOLBAR_LEFT_GROUP}
- ${EDITOR_TOOLBAR_RIGHT_GROUP} | ${EDITOR_TOOLBAR_RIGHT_GROUP}
- ${undefined} | ${EDITOR_TOOLBAR_RIGHT_GROUP}
- ${'non-existing'} | ${EDITOR_TOOLBAR_RIGHT_GROUP}
+ group | expectedGroup
+ ${EDITOR_TOOLBAR_BUTTON_GROUPS.file} | ${EDITOR_TOOLBAR_BUTTON_GROUPS.file}
+ ${EDITOR_TOOLBAR_BUTTON_GROUPS.edit} | ${EDITOR_TOOLBAR_BUTTON_GROUPS.edit}
+ ${EDITOR_TOOLBAR_BUTTON_GROUPS.settings} | ${EDITOR_TOOLBAR_BUTTON_GROUPS.settings}
+ ${undefined} | ${EDITOR_TOOLBAR_BUTTON_GROUPS.settings}
+ ${'non-existing'} | ${EDITOR_TOOLBAR_BUTTON_GROUPS.settings}
`('puts item with group="$group" into $expectedGroup group', ({ group, expectedGroup }) => {
const item = buildButton('first', {
group,
});
createComponentWithApollo([item]);
expect(findButtons()).toHaveLength(1);
- [EDITOR_TOOLBAR_RIGHT_GROUP, EDITOR_TOOLBAR_LEFT_GROUP].forEach((g) => {
+ Object.keys(EDITOR_TOOLBAR_BUTTON_GROUPS).forEach((g) => {
if (g === expectedGroup) {
expect(wrapper.vm.getGroupItems(g)).toEqual([expect.objectContaining({ id: 'first' })]);
} else {
@@ -70,7 +70,7 @@ describe('Source Editor Toolbar', () => {
describe('buttons update', () => {
it('properly updates buttons on Apollo cache update', async () => {
const item = buildButton('first', {
- group: EDITOR_TOOLBAR_RIGHT_GROUP,
+ group: EDITOR_TOOLBAR_BUTTON_GROUPS.edit,
});
createComponentWithApollo();
@@ -95,12 +95,15 @@ describe('Source Editor Toolbar', () => {
describe('click handler', () => {
it('emits the "click" event when a button is clicked', () => {
const item1 = buildButton('first', {
- group: EDITOR_TOOLBAR_LEFT_GROUP,
+ group: EDITOR_TOOLBAR_BUTTON_GROUPS.file,
});
const item2 = buildButton('second', {
- group: EDITOR_TOOLBAR_RIGHT_GROUP,
+ group: EDITOR_TOOLBAR_BUTTON_GROUPS.edit,
});
- createComponentWithApollo([item1, item2]);
+ const item3 = buildButton('third', {
+ group: EDITOR_TOOLBAR_BUTTON_GROUPS.settings,
+ });
+ createComponentWithApollo([item1, item2, item3]);
jest.spyOn(wrapper.vm, '$emit');
expect(wrapper.vm.$emit).not.toHaveBeenCalled();
@@ -110,7 +113,10 @@ describe('Source Editor Toolbar', () => {
findButtons().at(1).vm.$emit('click');
expect(wrapper.vm.$emit).toHaveBeenCalledWith('click', item2);
- expect(wrapper.vm.$emit.mock.calls).toHaveLength(2);
+ findButtons().at(2).vm.$emit('click');
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('click', item3);
+
+ expect(wrapper.vm.$emit.mock.calls).toHaveLength(3);
});
});
});
diff --git a/spec/frontend/editor/schema/ci/ci_schema_spec.js b/spec/frontend/editor/schema/ci/ci_schema_spec.js
index c822a0bfeaf..87208ec7aa8 100644
--- a/spec/frontend/editor/schema/ci/ci_schema_spec.js
+++ b/spec/frontend/editor/schema/ci/ci_schema_spec.js
@@ -33,6 +33,7 @@ import JobWhenYaml from './yaml_tests/positive_tests/job_when.yml';
import IdTokensYaml from './yaml_tests/positive_tests/id_tokens.yml';
import HooksYaml from './yaml_tests/positive_tests/hooks.yml';
import SecretsYaml from './yaml_tests/positive_tests/secrets.yml';
+import ServicesYaml from './yaml_tests/positive_tests/services.yml';
// YAML NEGATIVE TEST
import ArtifactsNegativeYaml from './yaml_tests/negative_tests/artifacts.yml';
@@ -52,6 +53,7 @@ import VariablesWrongSyntaxUsageExpand from './yaml_tests/negative_tests/variabl
import IdTokensNegativeYaml from './yaml_tests/negative_tests/id_tokens.yml';
import HooksNegative from './yaml_tests/negative_tests/hooks.yml';
import SecretsNegativeYaml from './yaml_tests/negative_tests/secrets.yml';
+import ServicesNegativeYaml from './yaml_tests/negative_tests/services.yml';
const ajv = new Ajv({
strictTypes: false,
@@ -89,6 +91,7 @@ describe('positive tests', () => {
VariablesYaml,
ProjectPathYaml,
IdTokensYaml,
+ ServicesYaml,
SecretsYaml,
}),
)('schema validates %s', (_, input) => {
@@ -113,10 +116,12 @@ describe('negative tests', () => {
// YAML
ArtifactsNegativeYaml,
CacheKeyNeative,
+ HooksNegative,
IdTokensNegativeYaml,
IncludeNegativeYaml,
JobWhenNegativeYaml,
RulesNegativeYaml,
+ TriggerNegative,
VariablesInvalidOptionsYaml,
VariablesInvalidSyntaxDescYaml,
VariablesWrongSyntaxUsageExpand,
@@ -126,8 +131,7 @@ describe('negative tests', () => {
ProjectPathIncludeNoSlashYaml,
ProjectPathIncludeTailSlashYaml,
SecretsNegativeYaml,
- TriggerNegative,
- HooksNegative,
+ ServicesNegativeYaml,
}),
)('schema validates %s', (_, input) => {
// We construct a new "JSON" from each main key that is inside a
diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/services.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/services.yml
new file mode 100644
index 00000000000..6761a603a0a
--- /dev/null
+++ b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/services.yml
@@ -0,0 +1,38 @@
+empty_services:
+ services:
+
+without_name:
+ services:
+ - alias: db-postgres
+ entrypoint: ["/usr/local/bin/db-postgres"]
+ command: ["start"]
+
+invalid_entrypoint:
+ services:
+ - name: my-postgres:11.7
+ alias: db-postgres
+ entrypoint: "/usr/local/bin/db-postgres" # must be array
+
+empty_entrypoint:
+ services:
+ - name: my-postgres:11.7
+ alias: db-postgres
+ entrypoint: []
+
+invalid_command:
+ services:
+ - name: my-postgres:11.7
+ alias: db-postgres
+ command: "start" # must be array
+
+empty_command:
+ services:
+ - name: my-postgres:11.7
+ alias: db-postgres
+ command: []
+
+empty_pull_policy:
+ script: echo "Multiple pull policies."
+ services:
+ - name: postgres:11.6
+ pull_policy: []
diff --git a/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/services.yml b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/services.yml
new file mode 100644
index 00000000000..8a0f59d1dfd
--- /dev/null
+++ b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/services.yml
@@ -0,0 +1,31 @@
+valid_services_list:
+ services:
+ - php:7
+ - node:latest
+ - golang:1.10
+
+valid_services_object:
+ services:
+ - name: my-postgres:11.7
+ alias: db-postgres
+ entrypoint: ["/usr/local/bin/db-postgres"]
+ command: ["start"]
+
+services_with_variables:
+ services:
+ - name: bitnami/nginx
+ alias: nginx
+ variables:
+ NGINX_HTTP_PORT_NUMBER: ${NGINX_HTTP_PORT_NUMBER}
+
+pull_policy_string:
+ script: echo "A single pull policy."
+ services:
+ - name: postgres:11.6
+ pull_policy: if-not-present
+
+pull_policy_array:
+ script: echo "Multiple pull policies."
+ services:
+ - name: postgres:11.6
+ pull_policy: [always, if-not-present]
diff --git a/spec/frontend/editor/source_editor_ci_schema_ext_spec.js b/spec/frontend/editor/source_editor_ci_schema_ext_spec.js
index 21f8979f1a9..e515285601b 100644
--- a/spec/frontend/editor/source_editor_ci_schema_ext_spec.js
+++ b/spec/frontend/editor/source_editor_ci_schema_ext_spec.js
@@ -17,7 +17,6 @@ describe('~/editor/editor_ci_config_ext', () => {
let editor;
let instance;
let editorEl;
- let originalGitlabUrl;
const createMockEditor = ({ blobPath = defaultBlobPath } = {}) => {
setHTMLFixture('<div id="editor"></div>');
@@ -31,16 +30,8 @@ describe('~/editor/editor_ci_config_ext', () => {
instance.use({ definition: CiSchemaExtension });
};
- beforeAll(() => {
- originalGitlabUrl = gon.gitlab_url;
- gon.gitlab_url = TEST_HOST;
- });
-
- afterAll(() => {
- gon.gitlab_url = originalGitlabUrl;
- });
-
beforeEach(() => {
+ gon.gitlab_url = TEST_HOST;
createMockEditor();
});
diff --git a/spec/frontend/editor/source_editor_extension_base_spec.js b/spec/frontend/editor/source_editor_extension_base_spec.js
index eab39ccaba1..b1b8173188c 100644
--- a/spec/frontend/editor/source_editor_extension_base_spec.js
+++ b/spec/frontend/editor/source_editor_extension_base_spec.js
@@ -7,6 +7,7 @@ import {
EDITOR_TYPE_DIFF,
EXTENSION_BASE_LINE_LINK_ANCHOR_CLASS,
EXTENSION_BASE_LINE_NUMBERS_CLASS,
+ EXTENSION_SOFTWRAP_ID,
} from '~/editor/constants';
import { SourceEditorExtension } from '~/editor/extensions/source_editor_extension_base';
import EditorInstance from '~/editor/source_editor_instance';
@@ -35,8 +36,18 @@ describe('The basis for an Source Editor extension', () => {
},
};
};
- const createInstance = (baseInstance = {}) => {
- return new EditorInstance(baseInstance);
+ const baseInstance = {
+ getOption: jest.fn(),
+ };
+
+ const createInstance = (base = baseInstance) => {
+ return new EditorInstance(base);
+ };
+
+ const toolbar = {
+ addItems: jest.fn(),
+ updateItem: jest.fn(),
+ removeItems: jest.fn(),
};
beforeEach(() => {
@@ -49,6 +60,66 @@ describe('The basis for an Source Editor extension', () => {
resetHTMLFixture();
});
+ describe('onSetup callback', () => {
+ let instance;
+ beforeEach(() => {
+ instance = createInstance();
+
+ instance.toolbar = toolbar;
+ });
+
+ it('adds correct buttons to the toolbar', () => {
+ instance.use({ definition: SourceEditorExtension });
+ expect(instance.toolbar.addItems).toHaveBeenCalledWith([
+ expect.objectContaining({
+ id: EXTENSION_SOFTWRAP_ID,
+ }),
+ ]);
+ });
+
+ it('does not fail if toolbar is not available', () => {
+ instance.toolbar = null;
+ expect(() => instance.use({ definition: SourceEditorExtension })).not.toThrow();
+ });
+
+ it.each`
+ optionValue | expectSelected
+ ${'on'} | ${true}
+ ${'off'} | ${false}
+ ${'foo'} | ${false}
+ ${undefined} | ${false}
+ ${null} | ${false}
+ `(
+ 'correctly sets the initial state of the button when wordWrap option is "$optionValue"',
+ ({ optionValue, expectSelected }) => {
+ instance.getOption.mockReturnValue(optionValue);
+ instance.use({ definition: SourceEditorExtension });
+ expect(instance.toolbar.addItems).toHaveBeenCalledWith([
+ expect.objectContaining({
+ selected: expectSelected,
+ }),
+ ]);
+ },
+ );
+ });
+
+ describe('onBeforeUnuse', () => {
+ let instance;
+ let extension;
+
+ beforeEach(() => {
+ instance = createInstance();
+
+ instance.toolbar = toolbar;
+ extension = instance.use({ definition: SourceEditorExtension });
+ });
+ it('removes the registered buttons from the toolbar', () => {
+ expect(instance.toolbar.removeItems).not.toHaveBeenCalled();
+ instance.unuse(extension);
+ expect(instance.toolbar.removeItems).toHaveBeenCalledWith([EXTENSION_SOFTWRAP_ID]);
+ });
+ });
+
describe('onUse callback', () => {
it('initializes the line highlighting', () => {
const instance = createInstance();
@@ -66,6 +137,7 @@ describe('The basis for an Source Editor extension', () => {
'$description the line linking for $instanceType instance',
({ instanceType, shouldBeCalled }) => {
const instance = createInstance({
+ ...baseInstance,
getEditorType: jest.fn().mockReturnValue(instanceType),
onMouseMove: jest.fn(),
onMouseDown: jest.fn(),
@@ -82,10 +154,44 @@ describe('The basis for an Source Editor extension', () => {
);
});
+ describe('toggleSoftwrap', () => {
+ let instance;
+
+ beforeEach(() => {
+ instance = createInstance();
+
+ instance.toolbar = toolbar;
+ instance.use({ definition: SourceEditorExtension });
+ });
+
+ it.each`
+ currentWordWrap | newWordWrap | expectSelected
+ ${'on'} | ${'off'} | ${false}
+ ${'off'} | ${'on'} | ${true}
+ ${'foo'} | ${'on'} | ${true}
+ ${undefined} | ${'on'} | ${true}
+ ${null} | ${'on'} | ${true}
+ `(
+ 'correctly updates wordWrap option in editor and the state of the button when currentWordWrap is "$currentWordWrap"',
+ ({ currentWordWrap, newWordWrap, expectSelected }) => {
+ instance.getOption.mockReturnValue(currentWordWrap);
+ instance.updateOptions = jest.fn();
+ instance.toggleSoftwrap();
+ expect(instance.updateOptions).toHaveBeenCalledWith({
+ wordWrap: newWordWrap,
+ });
+ expect(instance.toolbar.updateItem).toHaveBeenCalledWith(EXTENSION_SOFTWRAP_ID, {
+ selected: expectSelected,
+ });
+ },
+ );
+ });
+
describe('highlightLines', () => {
const revealSpy = jest.fn();
const decorationsSpy = jest.fn();
const instance = createInstance({
+ ...baseInstance,
revealLineInCenter: revealSpy,
deltaDecorations: decorationsSpy,
});
@@ -174,6 +280,7 @@ describe('The basis for an Source Editor extension', () => {
beforeEach(() => {
instance = createInstance({
+ ...baseInstance,
deltaDecorations: decorationsSpy,
lineDecorations,
});
@@ -188,6 +295,7 @@ describe('The basis for an Source Editor extension', () => {
describe('setupLineLinking', () => {
const instance = {
+ ...baseInstance,
onMouseMove: jest.fn(),
onMouseDown: jest.fn(),
deltaDecorations: jest.fn(),
diff --git a/spec/frontend/editor/source_editor_markdown_ext_spec.js b/spec/frontend/editor/source_editor_markdown_ext_spec.js
index 33e4b4bfc8e..b226ef03b33 100644
--- a/spec/frontend/editor/source_editor_markdown_ext_spec.js
+++ b/spec/frontend/editor/source_editor_markdown_ext_spec.js
@@ -17,6 +17,7 @@ describe('Markdown Extension for Source Editor', () => {
const thirdLine = 'string with some **markup**';
const text = `${firstLine}\n${secondLine}\n${thirdLine}`;
const markdownPath = 'foo.md';
+ let extensions;
const setSelection = (startLineNumber = 1, startColumn = 1, endLineNumber = 1, endColumn = 1) => {
const selection = new Range(startLineNumber, startColumn, endLineNumber, endColumn);
@@ -38,7 +39,10 @@ describe('Markdown Extension for Source Editor', () => {
blobPath: markdownPath,
blobContent: text,
});
- instance.use([{ definition: ToolbarExtension }, { definition: EditorMarkdownExtension }]);
+ extensions = instance.use([
+ { definition: ToolbarExtension },
+ { definition: EditorMarkdownExtension },
+ ]);
});
afterEach(() => {
@@ -59,6 +63,25 @@ describe('Markdown Extension for Source Editor', () => {
});
});
+ describe('markdown keystrokes', () => {
+ it('registers all keystrokes as actions', () => {
+ EXTENSION_MARKDOWN_BUTTONS.forEach((button) => {
+ if (button.data.mdShortcuts) {
+ expect(instance.getAction(button.id)).toBeDefined();
+ }
+ });
+ });
+
+ it('disposes all keystrokes on unuse', () => {
+ instance.unuse(extensions[1]);
+ EXTENSION_MARKDOWN_BUTTONS.forEach((button) => {
+ if (button.data.mdShortcuts) {
+ expect(instance.getAction(button.id)).toBeNull();
+ }
+ });
+ });
+ });
+
describe('getSelectedText', () => {
it('does not fail if there is no selection and returns the empty string', () => {
jest.spyOn(instance, 'getSelection');
diff --git a/spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js b/spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js
index c42ac28c498..895eb87833d 100644
--- a/spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js
+++ b/spec/frontend/editor/source_editor_markdown_livepreview_ext_spec.js
@@ -12,14 +12,14 @@ import {
} from '~/editor/constants';
import { EditorMarkdownPreviewExtension } from '~/editor/extensions/source_editor_markdown_livepreview_ext';
import SourceEditor from '~/editor/source_editor';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import syntaxHighlight from '~/syntax_highlight';
import { spyOnApi } from './helpers';
jest.mock('~/syntax_highlight');
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Markdown Live Preview Extension for Source Editor', () => {
let editor;
diff --git a/spec/frontend/editor/source_editor_webide_ext_spec.js b/spec/frontend/editor/source_editor_webide_ext_spec.js
index f418eab668a..7e4079c17f7 100644
--- a/spec/frontend/editor/source_editor_webide_ext_spec.js
+++ b/spec/frontend/editor/source_editor_webide_ext_spec.js
@@ -13,7 +13,6 @@ describe('Source Editor Web IDE Extension', () => {
editorEl = document.getElementById('editor');
editor = new SourceEditor();
});
- afterEach(() => {});
describe('onSetup', () => {
it.each`
diff --git a/spec/frontend/editor/utils_spec.js b/spec/frontend/editor/utils_spec.js
index e561cad1086..13b8a9804b0 100644
--- a/spec/frontend/editor/utils_spec.js
+++ b/spec/frontend/editor/utils_spec.js
@@ -54,19 +54,17 @@ describe('Source Editor utils', () => {
describe('getBlobLanguage', () => {
it.each`
- path | expectedLanguage
- ${'foo.js'} | ${'javascript'}
- ${'foo.js.rb'} | ${'ruby'}
- ${'foo.bar'} | ${'plaintext'}
- ${undefined} | ${'plaintext'}
- `(
- 'sets the $expectedThemeName theme when $themeName is set in the user preference',
- ({ path, expectedLanguage }) => {
- const language = utils.getBlobLanguage(path);
+ path | expectedLanguage
+ ${'foo.js'} | ${'javascript'}
+ ${'foo.js.rb'} | ${'ruby'}
+ ${'foo.bar'} | ${'plaintext'}
+ ${undefined} | ${'plaintext'}
+ ${'foo/bar/foo.js'} | ${'javascript'}
+ `(`returns '$expectedLanguage' for '$path' path`, ({ path, expectedLanguage }) => {
+ const language = utils.getBlobLanguage(path);
- expect(language).toEqual(expectedLanguage);
- },
- );
+ expect(language).toEqual(expectedLanguage);
+ });
});
describe('setupCodeSnipet', () => {
diff --git a/spec/frontend/emoji/components/category_spec.js b/spec/frontend/emoji/components/category_spec.js
index 90816f28d5b..272c1a09a69 100644
--- a/spec/frontend/emoji/components/category_spec.js
+++ b/spec/frontend/emoji/components/category_spec.js
@@ -9,11 +9,12 @@ function factory(propsData = {}) {
wrapper = shallowMount(Category, { propsData });
}
-describe('Emoji category component', () => {
- afterEach(() => {
- wrapper.destroy();
- });
+const triggerGlIntersectionObserver = () => {
+ wrapper.findComponent(GlIntersectionObserver).vm.$emit('appear');
+ return nextTick();
+};
+describe('Emoji category component', () => {
beforeEach(() => {
factory({
category: 'Activity',
@@ -26,25 +27,19 @@ describe('Emoji category component', () => {
});
it('renders group', async () => {
- // 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({ renderGroup: true });
+ await triggerGlIntersectionObserver();
expect(wrapper.findComponent(EmojiGroup).attributes('rendergroup')).toBe('true');
});
it('renders group on appear', async () => {
- wrapper.findComponent(GlIntersectionObserver).vm.$emit('appear');
-
- await nextTick();
+ await triggerGlIntersectionObserver();
expect(wrapper.findComponent(EmojiGroup).attributes('rendergroup')).toBe('true');
});
it('emits appear event on appear', async () => {
- wrapper.findComponent(GlIntersectionObserver).vm.$emit('appear');
-
- await nextTick();
+ await triggerGlIntersectionObserver();
expect(wrapper.emitted().appear[0]).toEqual(['Activity']);
});
diff --git a/spec/frontend/emoji/components/emoji_group_spec.js b/spec/frontend/emoji/components/emoji_group_spec.js
index 1aca2fbb8fc..75397ce25ff 100644
--- a/spec/frontend/emoji/components/emoji_group_spec.js
+++ b/spec/frontend/emoji/components/emoji_group_spec.js
@@ -15,10 +15,6 @@ function factory(propsData = {}) {
}
describe('Emoji group component', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it('does not render any buttons', () => {
factory({
emojis: [],
diff --git a/spec/frontend/emoji/components/emoji_list_spec.js b/spec/frontend/emoji/components/emoji_list_spec.js
index a72ba614d9f..f6f6062f8e8 100644
--- a/spec/frontend/emoji/components/emoji_list_spec.js
+++ b/spec/frontend/emoji/components/emoji_list_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import EmojiList from '~/emoji/components/emoji_list.vue';
+import waitForPromises from 'helpers/wait_for_promises';
jest.mock('~/emoji', () => ({
initEmojiMap: jest.fn(() => Promise.resolve()),
@@ -14,7 +14,8 @@ jest.mock('~/emoji', () => ({
}));
let wrapper;
-async function factory(render, propsData = { searchValue: '' }) {
+
+function factory(propsData = { searchValue: '' }) {
wrapper = extendedWrapper(
shallowMount(EmojiList, {
propsData,
@@ -23,35 +24,23 @@ async function factory(render, propsData = { searchValue: '' }) {
},
}),
);
-
- // Wait for categories to be set
- await nextTick();
-
- if (render) {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ render: true });
-
- // Wait for component to render
- await nextTick();
- }
}
const findDefaultSlot = () => wrapper.findByTestId('default-slot');
describe('Emoji list component', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it('does not render until render is set', async () => {
- await factory(false);
+ factory();
expect(findDefaultSlot().exists()).toBe(false);
+ await waitForPromises();
+ expect(findDefaultSlot().exists()).toBe(true);
});
it('renders with none filtered list', async () => {
- await factory(true);
+ factory();
+
+ await waitForPromises();
expect(JSON.parse(findDefaultSlot().text())).toEqual({
activity: {
@@ -63,7 +52,9 @@ describe('Emoji list component', () => {
});
it('renders filtered list of emojis', async () => {
- await factory(true, { searchValue: 'smile' });
+ factory({ searchValue: 'smile' });
+
+ await waitForPromises();
expect(JSON.parse(findDefaultSlot().text())).toEqual({
search: {
diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js
index 82e3b50aeb8..7b160c48501 100644
--- a/spec/frontend/environment.js
+++ b/spec/frontend/environment.js
@@ -8,6 +8,7 @@ const {
setGlobalDateToRealDate,
} = require('./__helpers__/fake_date/fake_date');
const { TEST_HOST } = require('./__helpers__/test_constants');
+const { createGon } = require('./__helpers__/gon_helper');
const ROOT_PATH = path.resolve(__dirname, '../..');
@@ -40,11 +41,12 @@ class CustomEnvironment extends TestEnvironment {
});
const { IS_EE } = projectConfig.testEnvironmentOptions;
- this.global.gon = {
- ee: IS_EE,
- };
+
this.global.IS_EE = IS_EE;
+ // Set up global `gon` object
+ this.global.gon = createGon(IS_EE);
+
// Set up global `gl` object
this.global.gl = {};
diff --git a/spec/frontend/environments/canary_ingress_spec.js b/spec/frontend/environments/canary_ingress_spec.js
index 340740e6499..17ecd93361f 100644
--- a/spec/frontend/environments/canary_ingress_spec.js
+++ b/spec/frontend/environments/canary_ingress_spec.js
@@ -23,7 +23,7 @@ describe('/environments/components/canary_ingress.vue', () => {
...props,
},
directives: {
- GlModal: createMockDirective(),
+ GlModal: createMockDirective('gl-modal'),
},
...options,
});
@@ -37,8 +37,6 @@ describe('/environments/components/canary_ingress.vue', () => {
if (wrapper) {
wrapper.destroy();
}
-
- wrapper = null;
});
describe('stable weight', () => {
diff --git a/spec/frontend/environments/canary_update_modal_spec.js b/spec/frontend/environments/canary_update_modal_spec.js
index 31b1770da59..a101ed4e00a 100644
--- a/spec/frontend/environments/canary_update_modal_spec.js
+++ b/spec/frontend/environments/canary_update_modal_spec.js
@@ -34,8 +34,6 @@ describe('/environments/components/canary_update_modal.vue', () => {
if (wrapper) {
wrapper.destroy();
}
-
- wrapper = null;
});
beforeEach(() => {
@@ -47,7 +45,7 @@ describe('/environments/components/canary_update_modal.vue', () => {
modalId: 'confirm-canary-change',
actionPrimary: {
text: 'Change ratio',
- attributes: [{ variant: 'confirm' }],
+ attributes: { variant: 'confirm' },
},
actionCancel: { text: 'Cancel' },
});
diff --git a/spec/frontend/environments/delete_environment_modal_spec.js b/spec/frontend/environments/delete_environment_modal_spec.js
index cc18bf754eb..96f6ce52a9c 100644
--- a/spec/frontend/environments/delete_environment_modal_spec.js
+++ b/spec/frontend/environments/delete_environment_modal_spec.js
@@ -6,10 +6,10 @@ import { s__, sprintf } from '~/locale';
import DeleteEnvironmentModal from '~/environments/components/delete_environment_modal.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { resolvedEnvironment } from './graphql/mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(VueApollo);
describe('~/environments/components/delete_environment_modal.vue', () => {
@@ -67,7 +67,7 @@ describe('~/environments/components/delete_environment_modal.vue', () => {
);
});
- it('should flash a message on error', async () => {
+ it('should alert a message on error', async () => {
createComponent({ apolloProvider: mockApollo });
deleteResolver.mockRejectedValue();
diff --git a/spec/frontend/environments/edit_environment_spec.js b/spec/frontend/environments/edit_environment_spec.js
index fb1a8b8c00a..34f338fabe6 100644
--- a/spec/frontend/environments/edit_environment_spec.js
+++ b/spec/frontend/environments/edit_environment_spec.js
@@ -3,13 +3,13 @@ import MockAdapter from 'axios-mock-adapter';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import EditEnvironment from '~/environments/components/edit_environment.vue';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { visitUrl } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility');
-jest.mock('~/flash');
+jest.mock('~/alert');
const DEFAULT_OPTS = {
provide: {
@@ -37,7 +37,6 @@ describe('~/environments/components/edit.vue', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
const findNameInput = () => wrapper.findByLabelText('Name');
diff --git a/spec/frontend/environments/empty_state_spec.js b/spec/frontend/environments/empty_state_spec.js
index 02cf2dc3c68..d067c4c80e0 100644
--- a/spec/frontend/environments/empty_state_spec.js
+++ b/spec/frontend/environments/empty_state_spec.js
@@ -29,10 +29,6 @@ describe('~/environments/components/empty_state.vue', () => {
provide: { newEnvironmentPath: NEW_PATH },
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('shows an empty state for available environments', () => {
wrapper = createWrapper();
diff --git a/spec/frontend/environments/enable_review_app_modal_spec.js b/spec/frontend/environments/enable_review_app_modal_spec.js
index 7939bd600dc..ee728775980 100644
--- a/spec/frontend/environments/enable_review_app_modal_spec.js
+++ b/spec/frontend/environments/enable_review_app_modal_spec.js
@@ -18,10 +18,6 @@ describe('Enable Review App Modal', () => {
const findInstructionAt = (i) => wrapper.findAll('ol li').at(i);
const findCopyString = () => wrapper.find(`#${EXPECTED_COPY_PRE_ID}`);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('renders the modal', () => {
beforeEach(() => {
wrapper = extendedWrapper(
diff --git a/spec/frontend/environments/environment_actions_spec.js b/spec/frontend/environments/environment_actions_spec.js
index 48483152f7a..3c9b4144e45 100644
--- a/spec/frontend/environments/environment_actions_spec.js
+++ b/spec/frontend/environments/environment_actions_spec.js
@@ -36,7 +36,7 @@ describe('EnvironmentActions Component', () => {
wrapper = mountFn(EnvironmentActions, {
propsData: { actions: [], ...props },
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
...options,
});
@@ -52,7 +52,6 @@ describe('EnvironmentActions Component', () => {
};
afterEach(() => {
- wrapper.destroy();
confirmAction.mockReset();
});
diff --git a/spec/frontend/environments/environment_folder_spec.js b/spec/frontend/environments/environment_folder_spec.js
index a37515bc3f7..279ff32a13d 100644
--- a/spec/frontend/environments/environment_folder_spec.js
+++ b/spec/frontend/environments/environment_folder_spec.js
@@ -35,7 +35,7 @@ describe('~/environments/components/environments_folder.vue', () => {
...propsData,
},
stubs: { transition: stubTransition() },
- provide: { helpPagePath: '/help' },
+ provide: { helpPagePath: '/help', projectId: '1' },
});
beforeEach(async () => {
diff --git a/spec/frontend/environments/environment_form_spec.js b/spec/frontend/environments/environment_form_spec.js
index b9b34bee80f..50e4e637aa3 100644
--- a/spec/frontend/environments/environment_form_spec.js
+++ b/spec/frontend/environments/environment_form_spec.js
@@ -15,19 +15,16 @@ const PROVIDE = { protectedEnvironmentSettingsPath: '/projects/not_real/settings
describe('~/environments/components/form.vue', () => {
let wrapper;
- const createWrapper = (propsData = {}) =>
+ const createWrapper = (propsData = {}, options = {}) =>
mountExtended(EnvironmentForm, {
provide: PROVIDE,
+ ...options,
propsData: {
...DEFAULT_PROPS,
...propsData,
},
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
beforeEach(() => {
wrapper = createWrapper();
@@ -105,6 +102,7 @@ describe('~/environments/components/form.vue', () => {
wrapper = createWrapper({ loading: true });
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
+
describe('when a new environment is being created', () => {
beforeEach(() => {
wrapper = createWrapper({
@@ -133,6 +131,18 @@ describe('~/environments/components/form.vue', () => {
});
});
+ describe('when no protected environment link is provided', () => {
+ beforeEach(() => {
+ wrapper = createWrapper({
+ provide: {},
+ });
+ });
+
+ it('does not show protected environment documentation', () => {
+ expect(wrapper.findByRole('link', { name: 'Protected environments' }).exists()).toBe(false);
+ });
+ });
+
describe('when an existing environment is being edited', () => {
beforeEach(() => {
wrapper = createWrapper({
diff --git a/spec/frontend/environments/environment_item_spec.js b/spec/frontend/environments/environment_item_spec.js
index dd909cf4473..59e94dfd662 100644
--- a/spec/frontend/environments/environment_item_spec.js
+++ b/spec/frontend/environments/environment_item_spec.js
@@ -56,10 +56,6 @@ describe('Environment item', () => {
findUpcomingDeployment().findComponent(GlAvatarLink);
const findUpcomingDeploymentAvatar = () => findUpcomingDeployment().findComponent(GlAvatar);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when item is not folder', () => {
it('should render environment name', () => {
expect(wrapper.find('.environment-name').text()).toContain(environment.name);
@@ -390,10 +386,6 @@ describe('Environment item', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render folder icon and name', () => {
expect(wrapper.find('.folder-name').text()).toContain(folder.name);
expect(wrapper.find('.folder-icon')).toBeDefined();
diff --git a/spec/frontend/environments/environment_pin_spec.js b/spec/frontend/environments/environment_pin_spec.js
index 170036b5b00..2f38dea2833 100644
--- a/spec/frontend/environments/environment_pin_spec.js
+++ b/spec/frontend/environments/environment_pin_spec.js
@@ -31,10 +31,6 @@ describe('Pin Component', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render the component with descriptive text', () => {
expect(wrapper.text()).toBe('Prevent auto-stopping');
});
@@ -64,10 +60,6 @@ describe('Pin Component', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render the component with descriptive text', () => {
expect(wrapper.text()).toBe('Prevent auto-stopping');
});
diff --git a/spec/frontend/environments/environment_table_spec.js b/spec/frontend/environments/environment_table_spec.js
index a86cfdd56ba..652b0f807fe 100644
--- a/spec/frontend/environments/environment_table_spec.js
+++ b/spec/frontend/environments/environment_table_spec.js
@@ -34,10 +34,6 @@ describe('Environment table', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('Should render a table', async () => {
const mockItem = {
name: 'review',
diff --git a/spec/frontend/environments/environments_app_spec.js b/spec/frontend/environments/environments_app_spec.js
index 986ecca4e84..a843f801da5 100644
--- a/spec/frontend/environments/environments_app_spec.js
+++ b/spec/frontend/environments/environments_app_spec.js
@@ -96,10 +96,6 @@ describe('~/environments/components/environments_app.vue', () => {
paginationMock = jest.fn();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should request available environments if the scope is invalid', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
diff --git a/spec/frontend/environments/environments_detail_header_spec.js b/spec/frontend/environments/environments_detail_header_spec.js
index 1f233c05fbf..8574743919f 100644
--- a/spec/frontend/environments/environments_detail_header_spec.js
+++ b/spec/frontend/environments/environments_detail_header_spec.js
@@ -47,7 +47,7 @@ describe('Environments detail header component', () => {
TimeAgo,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
propsData: {
canAdminEnvironment: false,
@@ -59,10 +59,6 @@ describe('Environments detail header component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default state with minimal access', () => {
beforeEach(() => {
createWrapper({ props: { environment: createEnvironment({ externalUrl: null }) } });
diff --git a/spec/frontend/environments/environments_folder_view_spec.js b/spec/frontend/environments/environments_folder_view_spec.js
index a87060f83d8..75fb3a31120 100644
--- a/spec/frontend/environments/environments_folder_view_spec.js
+++ b/spec/frontend/environments/environments_folder_view_spec.js
@@ -24,7 +24,6 @@ describe('Environments Folder View', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
describe('successful request', () => {
diff --git a/spec/frontend/environments/graphql/mock_data.js b/spec/frontend/environments/graphql/mock_data.js
index 5ea0be41614..b5435990042 100644
--- a/spec/frontend/environments/graphql/mock_data.js
+++ b/spec/frontend/environments/graphql/mock_data.js
@@ -798,3 +798,9 @@ export const resolvedDeploymentDetails = {
},
},
};
+
+export const agent = {
+ project: 'agent-project',
+ id: '1',
+ name: 'agent-name',
+};
diff --git a/spec/frontend/environments/kubernetes_agent_info_spec.js b/spec/frontend/environments/kubernetes_agent_info_spec.js
new file mode 100644
index 00000000000..4a6e2a7373b
--- /dev/null
+++ b/spec/frontend/environments/kubernetes_agent_info_spec.js
@@ -0,0 +1,126 @@
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlIcon, GlLink, GlSprintf, GlLoadingIcon, GlAlert } from '@gitlab/ui';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import KubernetesAgentInfo from '~/environments/components/kubernetes_agent_info.vue';
+import { TOKEN_STATUS_ACTIVE } from '~/clusters/agents/constants';
+import { AGENT_STATUSES, ACTIVE_CONNECTION_TIME } from '~/clusters_list/constants';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import getK8sClusterAgentQuery from '~/environments/graphql/queries/k8s_cluster_agent.query.graphql';
+
+Vue.use(VueApollo);
+
+const propsData = {
+ agentName: 'my-agent',
+ agentId: '1',
+ agentProjectPath: 'path/to/agent-config-project',
+};
+
+const mockClusterAgent = {
+ id: '1',
+ name: 'token-1',
+ webPath: 'path/to/agent-page',
+};
+
+const connectedTimeNow = new Date();
+const connectedTimeInactive = new Date(connectedTimeNow.getTime() - ACTIVE_CONNECTION_TIME);
+
+describe('~/environments/components/kubernetes_agent_info.vue', () => {
+ let wrapper;
+ let agentQueryResponse;
+
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findAgentLink = () => wrapper.findComponent(GlLink);
+ const findAgentStatus = () => wrapper.findByTestId('agent-status');
+ const findAgentStatusIcon = () => findAgentStatus().findComponent(GlIcon);
+ const findAgentLastUsedDate = () => wrapper.findByTestId('agent-last-used-date');
+ const findAlert = () => wrapper.findComponent(GlAlert);
+
+ const createWrapper = ({ tokens = [], queryResponse = null } = {}) => {
+ const clusterAgent = { ...mockClusterAgent, tokens: { nodes: tokens } };
+
+ agentQueryResponse =
+ queryResponse ||
+ jest.fn().mockResolvedValue({ data: { project: { id: 'project-1', clusterAgent } } });
+ const apolloProvider = createMockApollo([[getK8sClusterAgentQuery, agentQueryResponse]]);
+
+ wrapper = extendedWrapper(
+ shallowMount(KubernetesAgentInfo, {
+ apolloProvider,
+ propsData,
+ stubs: { TimeAgoTooltip, GlSprintf },
+ }),
+ );
+ };
+
+ describe('default', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('shows loading icon while fetching the agent details', async () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+ await waitForPromises();
+ expect(findLoadingIcon().exists()).toBe(false);
+ });
+
+ it('sends expected params', async () => {
+ await waitForPromises();
+
+ const variables = {
+ agentName: propsData.agentName,
+ projectPath: propsData.agentProjectPath,
+ tokenStatus: TOKEN_STATUS_ACTIVE,
+ };
+
+ expect(agentQueryResponse).toHaveBeenCalledWith(variables);
+ });
+
+ it('renders the agent name with the link', async () => {
+ await waitForPromises();
+
+ expect(findAgentLink().attributes('href')).toBe(mockClusterAgent.webPath);
+ expect(findAgentLink().text()).toContain(mockClusterAgent.id);
+ });
+ });
+
+ describe.each`
+ lastUsedAt | status | lastUsedText
+ ${null} | ${'unused'} | ${KubernetesAgentInfo.i18n.neverConnectedText}
+ ${connectedTimeNow} | ${'active'} | ${'just now'}
+ ${connectedTimeInactive} | ${'inactive'} | ${'8 minutes ago'}
+ `('when agent connection status is "$status"', ({ lastUsedAt, status, lastUsedText }) => {
+ beforeEach(async () => {
+ const tokens = [{ id: 'token-id', lastUsedAt }];
+ createWrapper({ tokens });
+ await waitForPromises();
+ });
+
+ it('displays correct status text', () => {
+ expect(findAgentStatus().text()).toBe(AGENT_STATUSES[status].name);
+ });
+
+ it('displays correct status icon', () => {
+ expect(findAgentStatusIcon().props('name')).toBe(AGENT_STATUSES[status].icon);
+ expect(findAgentStatusIcon().attributes('class')).toBe(AGENT_STATUSES[status].class);
+ });
+
+ it('displays correct last used date status', () => {
+ expect(findAgentLastUsedDate().text()).toBe(lastUsedText);
+ });
+ });
+
+ describe('when the agent query has errored', () => {
+ beforeEach(() => {
+ createWrapper({ clusterAgent: null, queryResponse: jest.fn().mockRejectedValue() });
+ return waitForPromises();
+ });
+
+ it('displays an alert message', () => {
+ expect(findAlert().text()).toBe(KubernetesAgentInfo.i18n.loadingError);
+ });
+ });
+});
diff --git a/spec/frontend/environments/kubernetes_overview_spec.js b/spec/frontend/environments/kubernetes_overview_spec.js
new file mode 100644
index 00000000000..8673c657760
--- /dev/null
+++ b/spec/frontend/environments/kubernetes_overview_spec.js
@@ -0,0 +1,84 @@
+import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import { GlCollapse, GlButton } from '@gitlab/ui';
+import KubernetesOverview from '~/environments/components/kubernetes_overview.vue';
+import KubernetesAgentInfo from '~/environments/components/kubernetes_agent_info.vue';
+
+const agent = {
+ project: 'agent-project',
+ id: '1',
+ name: 'agent-name',
+};
+
+const propsData = {
+ agentId: agent.id,
+ agentName: agent.name,
+ agentProjectPath: agent.project,
+};
+
+describe('~/environments/components/kubernetes_overview.vue', () => {
+ let wrapper;
+
+ const findCollapse = () => wrapper.findComponent(GlCollapse);
+ const findCollapseButton = () => wrapper.findComponent(GlButton);
+ const findAgentInfo = () => wrapper.findComponent(KubernetesAgentInfo);
+
+ const createWrapper = () => {
+ wrapper = shallowMount(KubernetesOverview, {
+ propsData,
+ });
+ };
+
+ const toggleCollapse = async () => {
+ findCollapseButton().vm.$emit('click');
+ await nextTick();
+ };
+
+ describe('default', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('renders the kubernetes overview title', () => {
+ expect(wrapper.text()).toBe(KubernetesOverview.i18n.sectionTitle);
+ });
+ });
+
+ describe('collapse', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('is collapsed by default', () => {
+ expect(findCollapse().props('visible')).toBeUndefined();
+ expect(findCollapseButton().attributes('aria-label')).toBe(KubernetesOverview.i18n.expand);
+ expect(findCollapseButton().props('icon')).toBe('chevron-right');
+ });
+
+ it("doesn't render components when the collapse is not visible", () => {
+ expect(findAgentInfo().exists()).toBe(false);
+ });
+
+ it('opens on click', async () => {
+ findCollapseButton().vm.$emit('click');
+ await nextTick();
+
+ expect(findCollapse().attributes('visible')).toBe('true');
+ expect(findCollapseButton().attributes('aria-label')).toBe(KubernetesOverview.i18n.collapse);
+ expect(findCollapseButton().props('icon')).toBe('chevron-down');
+ });
+ });
+
+ describe('when section is expanded', () => {
+ it('renders kubernetes agent info', async () => {
+ createWrapper();
+ await toggleCollapse();
+
+ expect(findAgentInfo().props()).toEqual({
+ agentName: agent.name,
+ agentId: agent.id,
+ agentProjectPath: agent.project,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/environments/new_environment_item_spec.js b/spec/frontend/environments/new_environment_item_spec.js
index 76cd09cfb4e..c04ff896794 100644
--- a/spec/frontend/environments/new_environment_item_spec.js
+++ b/spec/frontend/environments/new_environment_item_spec.js
@@ -9,7 +9,8 @@ import { __, s__, sprintf } from '~/locale';
import EnvironmentItem from '~/environments/components/new_environment_item.vue';
import Deployment from '~/environments/components/deployment.vue';
import DeployBoardWrapper from '~/environments/components/deploy_board_wrapper.vue';
-import { resolvedEnvironment, rolloutStatus } from './graphql/mock_data';
+import KubernetesOverview from '~/environments/components/kubernetes_overview.vue';
+import { resolvedEnvironment, rolloutStatus, agent } from './graphql/mock_data';
Vue.use(VueApollo);
@@ -20,15 +21,16 @@ describe('~/environments/components/new_environment_item.vue', () => {
return createMockApollo();
};
- const createWrapper = ({ propsData = {}, apolloProvider } = {}) =>
+ const createWrapper = ({ propsData = {}, provideData = {}, apolloProvider } = {}) =>
mountExtended(EnvironmentItem, {
apolloProvider,
propsData: { environment: resolvedEnvironment, ...propsData },
- provide: { helpPagePath: '/help', projectId: '1', projectPath: '/1' },
+ provide: { helpPagePath: '/help', projectId: '1', projectPath: '/1', ...provideData },
stubs: { transition: stubTransition() },
});
const findDeployment = () => wrapper.findComponent(Deployment);
+ const findKubernetesOverview = () => wrapper.findComponent(KubernetesOverview);
const expandCollapsedSection = async () => {
const button = wrapper.findByRole('button', { name: __('Expand') });
@@ -37,10 +39,6 @@ describe('~/environments/components/new_environment_item.vue', () => {
return button;
};
- afterEach(() => {
- wrapper?.destroy();
- });
-
it('displays the name when not in a folder', () => {
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
@@ -157,7 +155,7 @@ describe('~/environments/components/new_environment_item.vue', () => {
});
describe('stop', () => {
- it('shows a buton to stop the environment if the environment is available', () => {
+ it('shows a button to stop the environment if the environment is available', () => {
wrapper = createWrapper({ apolloProvider: createApolloProvider() });
const stop = wrapper.findByRole('button', { name: s__('Environments|Stop environment') });
@@ -165,7 +163,7 @@ describe('~/environments/components/new_environment_item.vue', () => {
expect(stop.exists()).toBe(true);
});
- it('does not show a buton to stop the environment if the environment is stopped', () => {
+ it('does not show a button to stop the environment if the environment is stopped', () => {
wrapper = createWrapper({
propsData: { environment: { ...resolvedEnvironment, canStop: false } },
apolloProvider: createApolloProvider(),
@@ -515,4 +513,71 @@ describe('~/environments/components/new_environment_item.vue', () => {
expect(deployBoard.exists()).toBe(false);
});
});
+
+ describe('kubernetes overview', () => {
+ const environmentWithAgent = {
+ ...resolvedEnvironment,
+ agent,
+ };
+
+ it('should render if the feature flag is enabled and the environment has an agent object with the required data specified', () => {
+ wrapper = createWrapper({
+ propsData: { environment: environmentWithAgent },
+ provideData: {
+ glFeatures: {
+ kasUserAccessProject: true,
+ },
+ },
+ apolloProvider: createApolloProvider(),
+ });
+
+ expandCollapsedSection();
+
+ expect(findKubernetesOverview().props()).toMatchObject({
+ agentProjectPath: agent.project,
+ agentName: agent.name,
+ agentId: agent.id,
+ });
+ });
+
+ it('should not render if the feature flag is not enabled', () => {
+ wrapper = createWrapper({
+ propsData: { environment: environmentWithAgent },
+ apolloProvider: createApolloProvider(),
+ });
+
+ expandCollapsedSection();
+
+ expect(findKubernetesOverview().exists()).toBe(false);
+ });
+
+ it('should not render if the environment has no agent object', () => {
+ wrapper = createWrapper({
+ apolloProvider: createApolloProvider(),
+ });
+
+ expandCollapsedSection();
+
+ expect(findKubernetesOverview().exists()).toBe(false);
+ });
+
+ it('should not render if the environment has an agent object without agent id specified', () => {
+ const environment = {
+ ...resolvedEnvironment,
+ agent: {
+ project: agent.project,
+ name: agent.name,
+ },
+ };
+
+ wrapper = createWrapper({
+ propsData: { environment },
+ apolloProvider: createApolloProvider(),
+ });
+
+ expandCollapsedSection();
+
+ expect(findKubernetesOverview().exists()).toBe(false);
+ });
+ });
});
diff --git a/spec/frontend/environments/new_environment_spec.js b/spec/frontend/environments/new_environment_spec.js
index a8cc05b297b..743f4ad6786 100644
--- a/spec/frontend/environments/new_environment_spec.js
+++ b/spec/frontend/environments/new_environment_spec.js
@@ -3,13 +3,13 @@ import MockAdapter from 'axios-mock-adapter';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import NewEnvironment from '~/environments/components/new_environment.vue';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { visitUrl } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility');
-jest.mock('~/flash');
+jest.mock('~/alert');
const DEFAULT_OPTS = {
provide: {
@@ -41,7 +41,6 @@ describe('~/environments/components/new.vue', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
const showsLoading = () => wrapper.findComponent(GlLoadingIcon).exists();
diff --git a/spec/frontend/environments/stop_stale_environments_modal_spec.js b/spec/frontend/environments/stop_stale_environments_modal_spec.js
index a2ab4f707b5..ddf6670db12 100644
--- a/spec/frontend/environments/stop_stale_environments_modal_spec.js
+++ b/spec/frontend/environments/stop_stale_environments_modal_spec.js
@@ -18,7 +18,6 @@ describe('~/environments/components/stop_stale_environments_modal.vue', () => {
let wrapper;
let mock;
let before;
- let originalGon;
const createWrapper = (opts = {}) =>
shallowMount(StopStaleEnvironmentsModal, {
@@ -28,8 +27,7 @@ describe('~/environments/components/stop_stale_environments_modal.vue', () => {
});
beforeEach(() => {
- originalGon = window.gon;
- window.gon = { api_version: 'v4' };
+ window.gon.api_version = 'v4';
mock = new MockAdapter(axios);
jest.spyOn(axios, 'post');
@@ -39,9 +37,7 @@ describe('~/environments/components/stop_stale_environments_modal.vue', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
jest.resetAllMocks();
- window.gon = originalGon;
});
it('sets the correct min and max dates', async () => {
diff --git a/spec/frontend/error_tracking/components/error_details_spec.js b/spec/frontend/error_tracking/components/error_details_spec.js
index 9d6e46be8c4..3bfade12d27 100644
--- a/spec/frontend/error_tracking/components/error_details_spec.js
+++ b/spec/frontend/error_tracking/components/error_details_spec.js
@@ -18,11 +18,11 @@ import {
trackErrorDetailsViewsOptions,
trackErrorStatusUpdateOptions,
} from '~/error_tracking/utils';
-import { createAlert, VARIANT_WARNING } from '~/flash';
+import { createAlert, VARIANT_WARNING } from '~/alert';
import { __ } from '~/locale';
import Tracking from '~/tracking';
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(Vuex);
@@ -148,7 +148,7 @@ describe('ErrorDetails', () => {
expect(mocks.$apollo.queries.error.stopPolling).not.toHaveBeenCalled();
});
- it('when timeout is hit and no apollo result, stops loading and shows flash', async () => {
+ it('when timeout is hit and no apollo result, stops loading and shows alert', async () => {
Date.now.mockReturnValue(endTime + 1);
wrapper.vm.onNoApolloResult();
diff --git a/spec/frontend/error_tracking/store/actions_spec.js b/spec/frontend/error_tracking/store/actions_spec.js
index 3ec43010d80..44db4780ba9 100644
--- a/spec/frontend/error_tracking/store/actions_spec.js
+++ b/spec/frontend/error_tracking/store/actions_spec.js
@@ -2,12 +2,12 @@ import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/error_tracking/store/actions';
import * as types from '~/error_tracking/store/mutation_types';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { visitUrl } from '~/lib/utils/url_utility';
-jest.mock('~/flash.js');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility');
let mock;
diff --git a/spec/frontend/error_tracking/store/details/actions_spec.js b/spec/frontend/error_tracking/store/details/actions_spec.js
index 383d8aaeb20..0aeb8b19a9e 100644
--- a/spec/frontend/error_tracking/store/details/actions_spec.js
+++ b/spec/frontend/error_tracking/store/details/actions_spec.js
@@ -2,7 +2,7 @@ import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/error_tracking/store/details/actions';
import * as types from '~/error_tracking/store/details/mutation_types';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import {
HTTP_STATUS_BAD_REQUEST,
@@ -14,7 +14,7 @@ import Poll from '~/lib/utils/poll';
let mockedAdapter;
let mockedRestart;
-jest.mock('~/flash.js');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility');
describe('Sentry error details store actions', () => {
@@ -48,7 +48,7 @@ describe('Sentry error details store actions', () => {
);
});
- it('should show flash on API error', async () => {
+ it('should show alert on API error', async () => {
mockedAdapter.onGet().reply(HTTP_STATUS_BAD_REQUEST);
await testAction(
diff --git a/spec/frontend/error_tracking/store/list/actions_spec.js b/spec/frontend/error_tracking/store/list/actions_spec.js
index 590983bd93d..24a26476455 100644
--- a/spec/frontend/error_tracking/store/list/actions_spec.js
+++ b/spec/frontend/error_tracking/store/list/actions_spec.js
@@ -2,11 +2,11 @@ import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/error_tracking/store/list/actions';
import * as types from '~/error_tracking/store/list/mutation_types';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
-jest.mock('~/flash.js');
+jest.mock('~/alert');
describe('error tracking actions', () => {
let mock;
@@ -38,7 +38,7 @@ describe('error tracking actions', () => {
);
});
- it('should show flash on API error', async () => {
+ it('should show alert on API error', async () => {
mock.onGet().reply(HTTP_STATUS_BAD_REQUEST);
await testAction(
diff --git a/spec/frontend/experimentation/components/gitlab_experiment_spec.js b/spec/frontend/experimentation/components/gitlab_experiment_spec.js
index f52ebf0f3c4..73db4b9503c 100644
--- a/spec/frontend/experimentation/components/gitlab_experiment_spec.js
+++ b/spec/frontend/experimentation/components/gitlab_experiment_spec.js
@@ -9,7 +9,6 @@ const defaultSlots = {
};
describe('ExperimentComponent', () => {
- const oldGon = window.gon;
let wrapper;
const createComponent = (propsData = defaultProps, slots = defaultSlots) => {
@@ -20,12 +19,6 @@ describe('ExperimentComponent', () => {
window.gon = { experiment: { experiment_name: { variant: expectedVariant } } };
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- window.gon = oldGon;
- });
-
describe('when variant and experiment is set', () => {
it('renders control when it is the active variant', () => {
mockVariant('control');
diff --git a/spec/frontend/experimentation/utils_spec.js b/spec/frontend/experimentation/utils_spec.js
index 0d663fd055e..6d9c9dfe65a 100644
--- a/spec/frontend/experimentation/utils_spec.js
+++ b/spec/frontend/experimentation/utils_spec.js
@@ -10,18 +10,15 @@ describe('experiment Utilities', () => {
const ABC_KEY = 'abc';
const DEF_KEY = 'def';
- let origGon;
let origGl;
beforeEach(() => {
- origGon = window.gon;
origGl = window.gl;
window.gon.experiment = {};
window.gl.experiments = {};
});
afterEach(() => {
- window.gon = origGon;
window.gl = origGl;
});
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 c1051a14a08..b06e0340991 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
@@ -42,7 +42,6 @@ describe('Configure Feature Flags Modal', () => {
wrapper.findAllComponents(GlAlert).filter((c) => c.props('variant') === 'danger');
describe('idle', () => {
- afterEach(() => wrapper.destroy());
beforeEach(factory);
it('should have Primary and Secondary actions', () => {
@@ -51,7 +50,7 @@ describe('Configure Feature Flags Modal', () => {
});
it('should default disable the primary action', () => {
- const [{ disabled }] = findSecondaryAction().attributes;
+ const { disabled } = findSecondaryAction().attributes;
expect(disabled).toBe(true);
});
@@ -112,19 +111,17 @@ describe('Configure Feature Flags Modal', () => {
});
describe('verified', () => {
- afterEach(() => wrapper.destroy());
beforeEach(factory);
it('should enable the secondary action', async () => {
findProjectNameInput().vm.$emit('input', provide.projectName);
await nextTick();
- const [{ disabled }] = findSecondaryAction().attributes;
+ const { disabled } = findSecondaryAction().attributes;
expect(disabled).toBe(false);
});
});
describe('cannot rotate token', () => {
- afterEach(() => wrapper.destroy());
beforeEach(factory.bind(null, { canUserRotateToken: false }));
it('should not display the primary action', () => {
@@ -141,7 +138,6 @@ describe('Configure Feature Flags Modal', () => {
});
describe('has rotate error', () => {
- afterEach(() => wrapper.destroy());
beforeEach(() => {
factory({ hasRotateError: true });
});
@@ -153,7 +149,6 @@ describe('Configure Feature Flags Modal', () => {
});
describe('is rotating', () => {
- afterEach(() => wrapper.destroy());
beforeEach(factory.bind(null, { isRotating: true }));
it('should disable the project name input', async () => {
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 cf4605e21ea..c26fd80865d 100644
--- a/spec/frontend/feature_flags/components/edit_feature_flag_spec.js
+++ b/spec/frontend/feature_flags/components/edit_feature_flag_spec.js
@@ -53,7 +53,6 @@ describe('Edit feature flag form', () => {
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
});
diff --git a/spec/frontend/feature_flags/components/empty_state_spec.js b/spec/frontend/feature_flags/components/empty_state_spec.js
index e3cc6f703c4..d983332f7c1 100644
--- a/spec/frontend/feature_flags/components/empty_state_spec.js
+++ b/spec/frontend/feature_flags/components/empty_state_spec.js
@@ -48,8 +48,6 @@ describe('feature_flags/components/feature_flags_tab.vue', () => {
if (wrapper?.destroy) {
wrapper.destroy();
}
-
- wrapper = null;
});
describe('alerts', () => {
diff --git a/spec/frontend/feature_flags/components/environments_dropdown_spec.js b/spec/frontend/feature_flags/components/environments_dropdown_spec.js
index a4738fed37e..9fc0119a6c8 100644
--- a/spec/frontend/feature_flags/components/environments_dropdown_spec.js
+++ b/spec/frontend/feature_flags/components/environments_dropdown_spec.js
@@ -27,7 +27,6 @@ describe('Feature flags > Environments dropdown', () => {
const findDropdownMenu = () => wrapper.find('.dropdown-menu');
afterEach(() => {
- wrapper.destroy();
mock.restore();
});
diff --git a/spec/frontend/feature_flags/components/feature_flags_spec.js b/spec/frontend/feature_flags/components/feature_flags_spec.js
index e80f9c559c4..23e86d0eb2f 100644
--- a/spec/frontend/feature_flags/components/feature_flags_spec.js
+++ b/spec/frontend/feature_flags/components/feature_flags_spec.js
@@ -65,8 +65,6 @@ describe('Feature flags', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
- wrapper = null;
});
describe('when limit exceeded', () => {
diff --git a/spec/frontend/feature_flags/components/form_spec.js b/spec/frontend/feature_flags/components/form_spec.js
index 7dd7c709c94..f66e25698e6 100644
--- a/spec/frontend/feature_flags/components/form_spec.js
+++ b/spec/frontend/feature_flags/components/form_spec.js
@@ -42,10 +42,6 @@ describe('feature flag form', () => {
Api.fetchFeatureFlagUserLists.mockResolvedValue({ data: [] });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render provided submitText', () => {
factory(requiredProps);
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 300d0e47082..46c9118cbd9 100644
--- a/spec/frontend/feature_flags/components/new_feature_flag_spec.js
+++ b/spec/frontend/feature_flags/components/new_feature_flag_spec.js
@@ -46,10 +46,6 @@ describe('New feature flag form', () => {
factory();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with error', () => {
it('should render the error', async () => {
store.dispatch('receiveCreateFeatureFlagError', { message: ['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 70a9156b5a9..5feaf094701 100644
--- a/spec/frontend/feature_flags/components/strategies/flexible_rollout_spec.js
+++ b/spec/frontend/feature_flags/components/strategies/flexible_rollout_spec.js
@@ -24,8 +24,6 @@ describe('feature_flags/components/strategies/flexible_rollout.vue', () => {
if (wrapper?.destroy) {
wrapper.destroy();
}
-
- wrapper = null;
});
describe('with valid percentage', () => {
diff --git a/spec/frontend/feature_flags/components/strategies/parameter_form_group_spec.js b/spec/frontend/feature_flags/components/strategies/parameter_form_group_spec.js
index 23ad0d3a08d..365f1e534b5 100644
--- a/spec/frontend/feature_flags/components/strategies/parameter_form_group_spec.js
+++ b/spec/frontend/feature_flags/components/strategies/parameter_form_group_spec.js
@@ -28,8 +28,6 @@ describe('~/feature_flags/strategies/parameter_form_group.vue', () => {
if (wrapper?.destroy) {
wrapper.destroy();
}
-
- wrapper = null;
});
it('should display the default slot', () => {
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 cb422a018f9..b20061c12a2 100644
--- a/spec/frontend/feature_flags/components/strategies/percent_rollout_spec.js
+++ b/spec/frontend/feature_flags/components/strategies/percent_rollout_spec.js
@@ -22,8 +22,6 @@ describe('~/feature_flags/components/strategies/percent_rollout.vue', () => {
if (wrapper?.destroy) {
wrapper.destroy();
}
-
- wrapper = null;
});
describe('with valid percentage', () => {
diff --git a/spec/frontend/feature_flags/components/strategies/users_with_id_spec.js b/spec/frontend/feature_flags/components/strategies/users_with_id_spec.js
index 0a72714c22a..ae489f3a6e6 100644
--- a/spec/frontend/feature_flags/components/strategies/users_with_id_spec.js
+++ b/spec/frontend/feature_flags/components/strategies/users_with_id_spec.js
@@ -22,8 +22,6 @@ describe('~/feature_flags/components/users_with_id.vue', () => {
if (wrapper?.destroy) {
wrapper.destroy();
}
-
- wrapper = null;
});
it('should display the current value of the parameters', () => {
diff --git a/spec/frontend/feature_flags/components/strategy_parameters_spec.js b/spec/frontend/feature_flags/components/strategy_parameters_spec.js
index d0f1f7d0e2a..cd8270f1801 100644
--- a/spec/frontend/feature_flags/components/strategy_parameters_spec.js
+++ b/spec/frontend/feature_flags/components/strategy_parameters_spec.js
@@ -32,8 +32,6 @@ describe('~/feature_flags/components/strategy_parameters.vue', () => {
if (wrapper?.destroy) {
wrapper.destroy();
}
-
- wrapper = null;
});
describe.each`
diff --git a/spec/frontend/feature_highlight/feature_highlight_helper_spec.js b/spec/frontend/feature_highlight/feature_highlight_helper_spec.js
index 4d5cb26810e..4609bfc23d7 100644
--- a/spec/frontend/feature_highlight/feature_highlight_helper_spec.js
+++ b/spec/frontend/feature_highlight/feature_highlight_helper_spec.js
@@ -1,10 +1,10 @@
import MockAdapter from 'axios-mock-adapter';
import { dismiss } from '~/feature_highlight/feature_highlight_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_CREATED, HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('feature highlight helper', () => {
describe('dismiss', () => {
@@ -26,7 +26,7 @@ describe('feature highlight helper', () => {
await expect(dismiss(endpoint, highlightId)).resolves.toEqual(expect.anything());
});
- it('triggers flash when dismiss request fails', async () => {
+ it('triggers an alert when dismiss request fails', async () => {
mockAxios
.onPost(endpoint, { feature_name: highlightId })
.replyOnce(HTTP_STATUS_INTERNAL_SERVER_ERROR);
diff --git a/spec/frontend/feature_highlight/feature_highlight_popover_spec.js b/spec/frontend/feature_highlight/feature_highlight_popover_spec.js
index 650f9eb1bbc..66ea22cece3 100644
--- a/spec/frontend/feature_highlight/feature_highlight_popover_spec.js
+++ b/spec/frontend/feature_highlight/feature_highlight_popover_spec.js
@@ -29,11 +29,6 @@ describe('feature_highlight/feature_highlight_popover', () => {
buildWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders popover target', () => {
expect(findPopoverTarget().exists()).toBe(true);
});
diff --git a/spec/frontend/filtered_search/components/recent_searches_dropdown_content_spec.js b/spec/frontend/filtered_search/components/recent_searches_dropdown_content_spec.js
index ebed477fa2f..5f0e928e1fe 100644
--- a/spec/frontend/filtered_search/components/recent_searches_dropdown_content_spec.js
+++ b/spec/frontend/filtered_search/components/recent_searches_dropdown_content_spec.js
@@ -22,11 +22,6 @@ describe('Recent Searches Dropdown Content', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when local storage is not available', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/filtered_search/dropdown_user_spec.js b/spec/frontend/filtered_search/dropdown_user_spec.js
index 26f12673f68..02ef813883f 100644
--- a/spec/frontend/filtered_search/dropdown_user_spec.js
+++ b/spec/frontend/filtered_search/dropdown_user_spec.js
@@ -68,10 +68,6 @@ describe('Dropdown User', () => {
'/gitlab_directory/-/autocomplete/users.json',
);
});
-
- afterEach(() => {
- window.gon = {};
- });
});
describe('hideCurrentUser', () => {
diff --git a/spec/frontend/filtered_search/filtered_search_manager_spec.js b/spec/frontend/filtered_search/filtered_search_manager_spec.js
index 26af7af701b..8c16ff100eb 100644
--- a/spec/frontend/filtered_search/filtered_search_manager_spec.js
+++ b/spec/frontend/filtered_search/filtered_search_manager_spec.js
@@ -8,11 +8,11 @@ import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered
import RecentSearchesRoot from '~/filtered_search/recent_searches_root';
import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { BACKSPACE_KEY_CODE, DELETE_KEY_CODE } from '~/lib/utils/keycodes';
import { visitUrl, getParameterByName } from '~/lib/utils/url_utility';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
getParameterByName: jest.fn(),
diff --git a/spec/frontend/filtered_search/visual_token_value_spec.js b/spec/frontend/filtered_search/visual_token_value_spec.js
index d3fa8fae9ab..138a4e183a9 100644
--- a/spec/frontend/filtered_search/visual_token_value_spec.js
+++ b/spec/frontend/filtered_search/visual_token_value_spec.js
@@ -5,11 +5,11 @@ import FilteredSearchSpecHelper from 'helpers/filtered_search_spec_helper';
import { TEST_HOST } from 'helpers/test_constants';
import DropdownUtils from '~/filtered_search/dropdown_utils';
import VisualTokenValue from '~/filtered_search/visual_token_value';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import AjaxCache from '~/lib/utils/ajax_cache';
import UsersCache from '~/lib/utils/users_cache';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Filtered Search Visual Tokens', () => {
const findElements = (tokenElement) => {
diff --git a/spec/frontend/fixtures/abuse_reports.rb b/spec/frontend/fixtures/abuse_reports.rb
index d8c8737b125..ad0fb9be8dc 100644
--- a/spec/frontend/fixtures/abuse_reports.rb
+++ b/spec/frontend/fixtures/abuse_reports.rb
@@ -14,6 +14,8 @@ RSpec.describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :co
render_views
before do
+ stub_feature_flags(abuse_reports_list: false)
+
sign_in(admin)
enable_admin_mode!(admin)
end
diff --git a/spec/frontend/fixtures/api_deploy_keys.rb b/spec/frontend/fixtures/api_deploy_keys.rb
index 5ffc726f086..8c926296817 100644
--- a/spec/frontend/fixtures/api_deploy_keys.rb
+++ b/spec/frontend/fixtures/api_deploy_keys.rb
@@ -7,6 +7,7 @@ RSpec.describe API::DeployKeys, '(JavaScript fixtures)', type: :request do
include JavaScriptFixturesHelpers
let_it_be(:admin) { create(:admin) }
+ let_it_be(:path) { "/deploy_keys" }
let_it_be(:project) { create(:project) }
let_it_be(:project2) { create(:project) }
let_it_be(:deploy_key) { create(:deploy_key, public: true) }
@@ -17,8 +18,10 @@ RSpec.describe API::DeployKeys, '(JavaScript fixtures)', type: :request do
let_it_be(:deploy_keys_project3) { create(:deploy_keys_project, :write_access, project: project, deploy_key: deploy_key2) }
let_it_be(:deploy_keys_project4) { create(:deploy_keys_project, :write_access, project: project2, deploy_key: deploy_key2) }
+ it_behaves_like 'GET request permissions for admin mode'
+
it 'api/deploy_keys/index.json' do
- get api("/deploy_keys", admin)
+ get api("/deploy_keys", admin, admin_mode: true)
expect(response).to be_successful
end
diff --git a/spec/frontend/fixtures/jobs.rb b/spec/frontend/fixtures/jobs.rb
index 6d452bf1bff..3583beb83c2 100644
--- a/spec/frontend/fixtures/jobs.rb
+++ b/spec/frontend/fixtures/jobs.rb
@@ -93,4 +93,26 @@ RSpec.describe 'Jobs (JavaScript fixtures)' do
expect_graphql_errors_to_be_empty
end
end
+
+ describe 'get_jobs_count.query.graphql', type: :request do
+ let!(:build) { create(:ci_build, :success, name: 'build', pipeline: pipeline) }
+ let!(:cancelable) { create(:ci_build, :cancelable, name: 'cancelable', pipeline: pipeline) }
+ let!(:failed) { create(:ci_build, :failed, name: 'failed', pipeline: pipeline) }
+
+ fixtures_path = 'graphql/jobs/'
+ get_jobs_count_query = 'get_jobs_count.query.graphql'
+ full_path = 'frontend-fixtures/builds-project'
+
+ let_it_be(:query) do
+ get_graphql_query_as_string("jobs/components/table/graphql/queries/#{get_jobs_count_query}")
+ end
+
+ it "#{fixtures_path}#{get_jobs_count_query}.json" do
+ post_graphql(query, current_user: user, variables: {
+ fullPath: full_path
+ })
+
+ expect_graphql_errors_to_be_empty
+ end
+ end
end
diff --git a/spec/frontend/fixtures/merge_requests.rb b/spec/frontend/fixtures/merge_requests.rb
index 7ee89ca3694..b6f6d149756 100644
--- a/spec/frontend/fixtures/merge_requests.rb
+++ b/spec/frontend/fixtures/merge_requests.rb
@@ -151,7 +151,7 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type:
context 'merge request with no approvals' do
base_input_path = 'vue_merge_request_widget/components/approvals/queries/'
base_output_path = 'graphql/merge_requests/approvals/'
- query_name = 'approved_by.query.graphql'
+ query_name = 'approvals.query.graphql'
it "#{base_output_path}#{query_name}_no_approvals.json" do
query = get_graphql_query_as_string("#{base_input_path}#{query_name}", ee: Gitlab.ee?)
@@ -165,7 +165,7 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type:
context 'merge request approved by current user' do
base_input_path = 'vue_merge_request_widget/components/approvals/queries/'
base_output_path = 'graphql/merge_requests/approvals/'
- query_name = 'approved_by.query.graphql'
+ query_name = 'approvals.query.graphql'
it "#{base_output_path}#{query_name}.json" do
merge_request.approved_by_users << user
@@ -181,7 +181,7 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type:
context 'merge request approved by multiple users' do
base_input_path = 'vue_merge_request_widget/components/approvals/queries/'
base_output_path = 'graphql/merge_requests/approvals/'
- query_name = 'approved_by.query.graphql'
+ query_name = 'approvals.query.graphql'
it "#{base_output_path}#{query_name}_multiple_users.json" do
merge_request.approved_by_users << user
diff --git a/spec/frontend/fixtures/runner.rb b/spec/frontend/fixtures/runner.rb
index f60e4991292..1581bc58289 100644
--- a/spec/frontend/fixtures/runner.rb
+++ b/spec/frontend/fixtures/runner.rb
@@ -145,6 +145,40 @@ RSpec.describe 'Runner (JavaScript fixtures)' do
expect_graphql_errors_to_be_empty
end
end
+
+ describe 'runner_for_registration.query.graphql', :freeze_time, type: :request do
+ runner_for_registration_query = 'register/runner_for_registration.query.graphql'
+
+ let_it_be(:query) do
+ get_graphql_query_as_string("#{query_path}#{runner_for_registration_query}")
+ end
+
+ it "#{fixtures_path}#{runner_for_registration_query}.json" do
+ post_graphql(query, current_user: admin, variables: {
+ id: runner.to_global_id.to_s
+ })
+
+ expect_graphql_errors_to_be_empty
+ end
+ end
+
+ describe 'runner_create.mutation.graphql', type: :request do
+ runner_create_mutation = 'new/runner_create.mutation.graphql'
+
+ let_it_be(:query) do
+ get_graphql_query_as_string("#{query_path}#{runner_create_mutation}")
+ end
+
+ it "#{fixtures_path}#{runner_create_mutation}.json" do
+ post_graphql(query, current_user: admin, variables: {
+ input: {
+ description: 'My dummy runner'
+ }
+ })
+
+ expect_graphql_errors_to_be_empty
+ end
+ end
end
describe 'as group owner', GraphQL::Query do
diff --git a/spec/frontend/fixtures/saved_replies.rb b/spec/frontend/fixtures/saved_replies.rb
index c80ba06bca1..613e4a1b447 100644
--- a/spec/frontend/fixtures/saved_replies.rb
+++ b/spec/frontend/fixtures/saved_replies.rb
@@ -43,4 +43,32 @@ RSpec.describe GraphQL::Query, type: :request, feature_category: :user_profile d
expect_graphql_errors_to_be_empty
end
end
+
+ context 'when user creates saved reply' do
+ base_input_path = 'saved_replies/queries/'
+ base_output_path = 'graphql/saved_replies/'
+ query_name = 'create_saved_reply.mutation.graphql'
+
+ it "#{base_output_path}#{query_name}.json" do
+ query = get_graphql_query_as_string("#{base_input_path}#{query_name}")
+
+ post_graphql(query, current_user: current_user, variables: { name: "Test", content: "Test content" })
+
+ expect_graphql_errors_to_be_empty
+ end
+ end
+
+ context 'when user creates saved reply and it errors' do
+ base_input_path = 'saved_replies/queries/'
+ base_output_path = 'graphql/saved_replies/'
+ query_name = 'create_saved_reply.mutation.graphql'
+
+ it "#{base_output_path}create_saved_reply_with_errors.mutation.graphql.json" do
+ query = get_graphql_query_as_string("#{base_input_path}#{query_name}")
+
+ post_graphql(query, current_user: current_user, variables: { name: nil, content: nil })
+
+ expect(flattened_errors).not_to be_empty
+ end
+ end
end
diff --git a/spec/frontend/fixtures/startup_css.rb b/spec/frontend/fixtures/startup_css.rb
index bd2d63a1827..18a4aa58c00 100644
--- a/spec/frontend/fixtures/startup_css.rb
+++ b/spec/frontend/fixtures/startup_css.rb
@@ -16,7 +16,6 @@ RSpec.describe 'Startup CSS fixtures', type: :controller do
before do
# We want vNext badge to be included and com/canary don't remove/hide any other elements.
# This is why we're turning com and canary on by default for now.
- allow(Gitlab).to receive(:com?).and_return(true)
allow(Gitlab).to receive(:canary?).and_return(true)
sign_in(user)
end
@@ -55,13 +54,28 @@ RSpec.describe 'Startup CSS fixtures', type: :controller do
expect(response).to be_successful
end
+
+ # This Feature Flag is off by default
+ # This ensures that the correct css is generated for super sidebar
+ # When the feature flag is off, the general startup will capture it
+ it "startup_css/project-#{type}-super-sidebar.html" do
+ stub_feature_flags(super_sidebar_nav: true)
+ user.update!(use_new_navigation: true)
+
+ get :show, params: {
+ namespace_id: project.namespace.to_param,
+ id: project
+ }
+
+ expect(response).to be_successful
+ end
end
- describe ProjectsController, '(Startup CSS fixtures)', type: :controller do
+ describe ProjectsController, '(Startup CSS fixtures)', :saas, type: :controller do
it_behaves_like 'startup css project fixtures', 'general'
end
- describe ProjectsController, '(Startup CSS fixtures)', type: :controller do
+ describe ProjectsController, '(Startup CSS fixtures)', :saas, type: :controller do
before do
user.update!(theme_id: 11)
end
diff --git a/spec/frontend/fixtures/u2f.rb b/spec/frontend/fixtures/u2f.rb
deleted file mode 100644
index 96820c9ae80..00000000000
--- a/spec/frontend/fixtures/u2f.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.context 'U2F' do
- include JavaScriptFixturesHelpers
-
- let(:user) { create(:user, :two_factor_via_u2f, otp_secret: 'otpsecret:coolkids') }
-
- before do
- stub_feature_flags(webauthn: false)
- end
-
- describe SessionsController, '(JavaScript fixtures)', type: :controller do
- include DeviseHelpers
-
- render_views
-
- before do
- set_devise_mapping(context: @request)
- end
-
- it 'u2f/authenticate.html' do
- allow(controller).to receive(:find_user).and_return(user)
-
- post :create, params: { user: { login: user.username, password: user.password } }
-
- expect(response).to be_successful
- end
- end
-
- describe Profiles::TwoFactorAuthsController, '(JavaScript fixtures)', type: :controller do
- render_views
-
- before do
- sign_in(user)
- allow_next_instance_of(Profiles::TwoFactorAuthsController) do |instance|
- allow(instance).to receive(:build_qr_code).and_return('qrcode:blackandwhitesquares')
- end
- end
-
- it 'u2f/register.html' do
- get :show
-
- expect(response).to be_successful
- end
- end
-end
diff --git a/spec/frontend/fixtures/users.rb b/spec/frontend/fixtures/users.rb
new file mode 100644
index 00000000000..6271aa87b9a
--- /dev/null
+++ b/spec/frontend/fixtures/users.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Users (GraphQL fixtures)', feature_category: :user_profile do
+ describe GraphQL::Query, type: :request do
+ include ApiHelpers
+ include GraphqlHelpers
+ include JavaScriptFixturesHelpers
+
+ let_it_be(:user) { create(:user) }
+
+ context 'for user achievements' do
+ let_it_be(:group) { create(:group, :public) }
+ let_it_be(:achievement1) { create(:achievement, namespace: group) }
+ let_it_be(:achievement2) { create(:achievement, namespace: group) }
+ let_it_be(:achievement3) { create(:achievement, namespace: group) }
+ let_it_be(:achievement_with_avatar_and_description) do
+ create(:achievement,
+ namespace: group,
+ description: 'Description',
+ avatar: File.new(Rails.root.join('db/fixtures/development/rocket.jpg'), 'r'))
+ end
+
+ let(:user_achievements_query_path) { 'profile/components/graphql/get_user_achievements.query.graphql' }
+ let(:query) { get_graphql_query_as_string(user_achievements_query_path) }
+
+ before_all do
+ group.add_guest(user)
+ end
+
+ it "graphql/get_user_achievements_empty_response.json" do
+ post_graphql(query, current_user: user, variables: { id: user.to_global_id })
+
+ expect_graphql_errors_to_be_empty
+ end
+
+ it "graphql/get_user_achievements_with_avatar_and_description_response.json" do
+ create(:user_achievement, user: user, achievement: achievement_with_avatar_and_description)
+
+ post_graphql(query, current_user: user, variables: { id: user.to_global_id })
+
+ expect_graphql_errors_to_be_empty
+ end
+
+ it "graphql/get_user_achievements_without_avatar_or_description_response.json" do
+ create(:user_achievement, user: user, achievement: achievement1)
+
+ post_graphql(query, current_user: user, variables: { id: user.to_global_id })
+
+ expect_graphql_errors_to_be_empty
+ end
+
+ it "graphql/get_user_achievements_long_response.json" do
+ [achievement1, achievement2, achievement3, achievement_with_avatar_and_description].each do |achievement|
+ create(:user_achievement, user: user, achievement: achievement)
+ end
+
+ post_graphql(query, current_user: user, variables: { id: user.to_global_id })
+
+ expect_graphql_errors_to_be_empty
+ end
+ end
+ end
+end
diff --git a/spec/frontend/fixtures/webauthn.rb b/spec/frontend/fixtures/webauthn.rb
index c6e9b41b584..ed6180118f0 100644
--- a/spec/frontend/fixtures/webauthn.rb
+++ b/spec/frontend/fixtures/webauthn.rb
@@ -32,6 +32,7 @@ RSpec.context 'WebAuthn' do
allow_next_instance_of(Profiles::TwoFactorAuthsController) do |instance|
allow(instance).to receive(:build_qr_code).and_return('qrcode:blackandwhitesquares')
end
+ stub_feature_flags(webauthn_without_totp: false)
end
it 'webauthn/register.html' do
diff --git a/spec/frontend/frequent_items/components/app_spec.js b/spec/frontend/frequent_items/components/app_spec.js
index e1890555de0..4f5788dcb77 100644
--- a/spec/frontend/frequent_items/components/app_spec.js
+++ b/spec/frontend/frequent_items/components/app_spec.js
@@ -69,7 +69,6 @@ describe('Frequent Items App Component', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
describe('default', () => {
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 c54a2a1d039..7c8592fdf0c 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
@@ -59,8 +59,6 @@ describe('FrequentItemsListItemComponent', () => {
afterEach(() => {
unmockTracking();
- wrapper.destroy();
- wrapper = null;
});
describe('computed', () => {
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 d024925f62b..87f8e131b77 100644
--- a/spec/frontend/frequent_items/components/frequent_items_list_spec.js
+++ b/spec/frontend/frequent_items/components/frequent_items_list_spec.js
@@ -29,10 +29,6 @@ describe('FrequentItemsListComponent', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('computed', () => {
describe('isListEmpty', () => {
it('should return `true` or `false` representing whether if `items` is empty or not with projects', async () => {
diff --git a/spec/frontend/gitlab_pages/new/pages/pages_pipeline_wizard_spec.js b/spec/frontend/gitlab_pages/new/pages/pages_pipeline_wizard_spec.js
index 685b5144a95..b1adc3f794a 100644
--- a/spec/frontend/gitlab_pages/new/pages/pages_pipeline_wizard_spec.js
+++ b/spec/frontend/gitlab_pages/new/pages/pages_pipeline_wizard_spec.js
@@ -50,10 +50,6 @@ describe('PagesPipelineWizard', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('shows the pipeline wizard', () => {
expect(findPipelineWizardWrapper().exists()).toBe(true);
});
diff --git a/spec/frontend/gitlab_version_check/components/gitlab_version_check_badge_spec.js b/spec/frontend/gitlab_version_check/components/gitlab_version_check_badge_spec.js
index 949bcf71ff5..e87f7e950cd 100644
--- a/spec/frontend/gitlab_version_check/components/gitlab_version_check_badge_spec.js
+++ b/spec/frontend/gitlab_version_check/components/gitlab_version_check_badge_spec.js
@@ -25,7 +25,6 @@ describe('GitlabVersionCheckBadge', () => {
afterEach(() => {
unmockTracking();
- wrapper.destroy();
});
const findGlBadgeClickWrapper = () => wrapper.findByTestId('badge-click-wrapper');
diff --git a/spec/frontend/google_cloud/components/google_cloud_menu_spec.js b/spec/frontend/google_cloud/components/google_cloud_menu_spec.js
index 4809ea37045..a0c988830ed 100644
--- a/spec/frontend/google_cloud/components/google_cloud_menu_spec.js
+++ b/spec/frontend/google_cloud/components/google_cloud_menu_spec.js
@@ -15,10 +15,6 @@ describe('google_cloud/components/google_cloud_menu', () => {
wrapper = mountExtended(GoogleCloudMenu, { propsData: props });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('contains active configuration link', () => {
const link = wrapper.findByTestId('configurationLink');
expect(link.text()).toBe(GoogleCloudMenu.i18n.configuration.title);
diff --git a/spec/frontend/google_cloud/components/incubation_banner_spec.js b/spec/frontend/google_cloud/components/incubation_banner_spec.js
index 09a4d92dca2..92bc39bdff9 100644
--- a/spec/frontend/google_cloud/components/incubation_banner_spec.js
+++ b/spec/frontend/google_cloud/components/incubation_banner_spec.js
@@ -15,10 +15,6 @@ describe('google_cloud/components/incubation_banner', () => {
wrapper = mount(IncubationBanner);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('contains alert', () => {
expect(findAlert().exists()).toBe(true);
});
diff --git a/spec/frontend/google_cloud/components/revoke_oauth_spec.js b/spec/frontend/google_cloud/components/revoke_oauth_spec.js
index faaec07fc35..2b39bb9ca74 100644
--- a/spec/frontend/google_cloud/components/revoke_oauth_spec.js
+++ b/spec/frontend/google_cloud/components/revoke_oauth_spec.js
@@ -20,10 +20,6 @@ describe('google_cloud/components/revoke_oauth', () => {
wrapper = shallowMount(RevokeOauth, { propsData });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('contains title', () => {
const title = findTitle();
expect(title.text()).toContain('Revoke authorizations');
diff --git a/spec/frontend/google_cloud/configuration/panel_spec.js b/spec/frontend/google_cloud/configuration/panel_spec.js
index 79eb4cb4918..dd85b4c90a7 100644
--- a/spec/frontend/google_cloud/configuration/panel_spec.js
+++ b/spec/frontend/google_cloud/configuration/panel_spec.js
@@ -25,10 +25,6 @@ describe('google_cloud/configuration/panel', () => {
wrapper = shallowMountExtended(Panel, { propsData: props });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('contains incubation banner', () => {
const target = wrapper.findComponent(IncubationBanner);
expect(target.exists()).toBe(true);
diff --git a/spec/frontend/google_cloud/databases/cloudsql/create_instance_form_spec.js b/spec/frontend/google_cloud/databases/cloudsql/create_instance_form_spec.js
index 48e4b0ca1ad..6e2d3147a54 100644
--- a/spec/frontend/google_cloud/databases/cloudsql/create_instance_form_spec.js
+++ b/spec/frontend/google_cloud/databases/cloudsql/create_instance_form_spec.js
@@ -25,10 +25,6 @@ describe('google_cloud/databases/cloudsql/create_instance_form', () => {
wrapper = shallowMountExtended(InstanceForm, { propsData, stubs: { GlFormCheckbox } });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('contains header', () => {
expect(findHeader().exists()).toBe(true);
});
diff --git a/spec/frontend/google_cloud/databases/cloudsql/instance_table_spec.js b/spec/frontend/google_cloud/databases/cloudsql/instance_table_spec.js
index a5736d0a524..a2ee75f9fbf 100644
--- a/spec/frontend/google_cloud/databases/cloudsql/instance_table_spec.js
+++ b/spec/frontend/google_cloud/databases/cloudsql/instance_table_spec.js
@@ -8,10 +8,6 @@ describe('google_cloud/databases/cloudsql/instance_table', () => {
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findTable = () => wrapper.findComponent(GlTable);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when there are no instances', () => {
beforeEach(() => {
const propsData = {
diff --git a/spec/frontend/google_cloud/databases/panel_spec.js b/spec/frontend/google_cloud/databases/panel_spec.js
index e6a0d74f348..779258bbdbb 100644
--- a/spec/frontend/google_cloud/databases/panel_spec.js
+++ b/spec/frontend/google_cloud/databases/panel_spec.js
@@ -23,10 +23,6 @@ describe('google_cloud/databases/panel', () => {
wrapper = shallowMountExtended(Panel, { propsData: props });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('contains incubation banner', () => {
const target = wrapper.findComponent(IncubationBanner);
expect(target.exists()).toBe(true);
diff --git a/spec/frontend/google_cloud/databases/service_table_spec.js b/spec/frontend/google_cloud/databases/service_table_spec.js
index 4a622e544e1..4594e1758ad 100644
--- a/spec/frontend/google_cloud/databases/service_table_spec.js
+++ b/spec/frontend/google_cloud/databases/service_table_spec.js
@@ -19,10 +19,6 @@ describe('google_cloud/databases/service_table', () => {
wrapper = mountExtended(ServiceTable, { propsData });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should contain a table', () => {
expect(findTable().exists()).toBe(true);
});
diff --git a/spec/frontend/google_cloud/deployments/panel_spec.js b/spec/frontend/google_cloud/deployments/panel_spec.js
index 729db1707a7..0748d8f9377 100644
--- a/spec/frontend/google_cloud/deployments/panel_spec.js
+++ b/spec/frontend/google_cloud/deployments/panel_spec.js
@@ -19,10 +19,6 @@ describe('google_cloud/deployments/panel', () => {
wrapper = shallowMountExtended(Panel, { propsData: props });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('contains incubation banner', () => {
const target = wrapper.findComponent(IncubationBanner);
expect(target.exists()).toBe(true);
diff --git a/spec/frontend/google_cloud/deployments/service_table_spec.js b/spec/frontend/google_cloud/deployments/service_table_spec.js
index 8faad64e313..49220a6007e 100644
--- a/spec/frontend/google_cloud/deployments/service_table_spec.js
+++ b/spec/frontend/google_cloud/deployments/service_table_spec.js
@@ -18,10 +18,6 @@ describe('google_cloud/deployments/service_table', () => {
wrapper = mount(DeploymentsServiceTable, { propsData });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should contain a table', () => {
expect(findTable().exists()).toBe(true);
});
diff --git a/spec/frontend/google_cloud/gcp_regions/form_spec.js b/spec/frontend/google_cloud/gcp_regions/form_spec.js
index 1030e9c8a18..be37ff092f0 100644
--- a/spec/frontend/google_cloud/gcp_regions/form_spec.js
+++ b/spec/frontend/google_cloud/gcp_regions/form_spec.js
@@ -16,10 +16,6 @@ describe('google_cloud/gcp_regions/form', () => {
wrapper = shallowMount(GcpRegionsForm, { propsData });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('contains header', () => {
expect(findHeader().exists()).toBe(true);
});
diff --git a/spec/frontend/google_cloud/gcp_regions/list_spec.js b/spec/frontend/google_cloud/gcp_regions/list_spec.js
index 6d8c389e5a1..74a54b93183 100644
--- a/spec/frontend/google_cloud/gcp_regions/list_spec.js
+++ b/spec/frontend/google_cloud/gcp_regions/list_spec.js
@@ -18,10 +18,6 @@ describe('google_cloud/gcp_regions/list', () => {
wrapper = mount(GcpRegionsList, { propsData });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('shows the empty state component', () => {
expect(findEmptyState().exists()).toBe(true);
});
diff --git a/spec/frontend/google_cloud/service_accounts/form_spec.js b/spec/frontend/google_cloud/service_accounts/form_spec.js
index 8be481774fa..c86c8876b15 100644
--- a/spec/frontend/google_cloud/service_accounts/form_spec.js
+++ b/spec/frontend/google_cloud/service_accounts/form_spec.js
@@ -17,10 +17,6 @@ describe('google_cloud/service_accounts/form', () => {
wrapper = shallowMount(ServiceAccountsForm, { propsData, stubs: { GlFormCheckbox } });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('contains header', () => {
expect(findHeader().exists()).toBe(true);
});
diff --git a/spec/frontend/google_cloud/service_accounts/list_spec.js b/spec/frontend/google_cloud/service_accounts/list_spec.js
index c2bd2005b5d..ae5776081d7 100644
--- a/spec/frontend/google_cloud/service_accounts/list_spec.js
+++ b/spec/frontend/google_cloud/service_accounts/list_spec.js
@@ -18,10 +18,6 @@ describe('google_cloud/service_accounts/list', () => {
wrapper = mount(ServiceAccountsList, { propsData });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('shows the empty state component', () => {
expect(findEmptyState().exists()).toBe(true);
});
diff --git a/spec/frontend/grafana_integration/components/grafana_integration_spec.js b/spec/frontend/grafana_integration/components/grafana_integration_spec.js
index 021a3aa41ed..9cb27670c98 100644
--- a/spec/frontend/grafana_integration/components/grafana_integration_spec.js
+++ b/spec/frontend/grafana_integration/components/grafana_integration_spec.js
@@ -3,14 +3,14 @@ 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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import GrafanaIntegration from '~/grafana_integration/components/grafana_integration.vue';
import { createStore } from '~/grafana_integration/store';
import axios from '~/lib/utils/axios_utils';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility');
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('grafana integration component', () => {
let wrapper;
@@ -103,7 +103,7 @@ describe('grafana integration component', () => {
expect(refreshCurrentPage).toHaveBeenCalled();
});
- it('creates flash banner on error', async () => {
+ it('creates alert banner on error', async () => {
const message = 'mockErrorMessage';
axios.patch.mockRejectedValue({ response: { data: { 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 85475c749b0..e92493315f7 100644
--- a/spec/frontend/group_settings/components/shared_runners_form_spec.js
+++ b/spec/frontend/group_settings/components/shared_runners_form_spec.js
@@ -45,9 +45,6 @@ describe('group_settings/components/shared_runners_form', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
-
updateGroup.mockReset();
});
diff --git a/spec/frontend/groups/components/app_spec.js b/spec/frontend/groups/components/app_spec.js
index 4e6ddd89a55..98868de8475 100644
--- a/spec/frontend/groups/components/app_spec.js
+++ b/spec/frontend/groups/components/app_spec.js
@@ -3,7 +3,7 @@ import AxiosMockAdapter from 'axios-mock-adapter';
import Vue, { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import appComponent from '~/groups/components/app.vue';
import groupFolderComponent from '~/groups/components/group_folder.vue';
import groupItemComponent from '~/groups/components/group_item.vue';
@@ -34,7 +34,7 @@ import {
const $toast = {
show: jest.fn(),
};
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('AppComponent', () => {
let wrapper;
@@ -65,11 +65,6 @@ describe('AppComponent', () => {
vm = wrapper.vm;
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
beforeEach(async () => {
mock = new AxiosMockAdapter(axios);
mock.onGet('/dashboard/groups.json').reply(HTTP_STATUS_OK, mockGroups);
@@ -117,7 +112,7 @@ describe('AppComponent', () => {
});
});
- it('should show flash error when request fails', () => {
+ it('should show alert error when request fails', () => {
mock.onGet('/dashboard/groups.json').reply(HTTP_STATUS_BAD_REQUEST);
jest.spyOn(window, 'scrollTo').mockImplementation(() => {});
@@ -325,7 +320,7 @@ describe('AppComponent', () => {
});
});
- it('should show error flash message if request failed to leave group', () => {
+ it('should show error alert message if request failed to leave group', () => {
const message = 'An error occurred. Please try again.';
jest
.spyOn(vm.service, 'leaveGroup')
@@ -342,7 +337,7 @@ describe('AppComponent', () => {
});
});
- it('should show appropriate error flash message if request forbids to leave group', () => {
+ it('should show appropriate error alert message if request forbids to leave group', () => {
const message = 'Failed to leave the group. Please make sure you are not the only owner.';
jest.spyOn(vm.service, 'leaveGroup').mockRejectedValue({ status: HTTP_STATUS_FORBIDDEN });
jest.spyOn(vm.store, 'removeGroup');
diff --git a/spec/frontend/groups/components/empty_states/subgroups_and_projects_empty_state_spec.js b/spec/frontend/groups/components/empty_states/subgroups_and_projects_empty_state_spec.js
index 75edc602fbf..dc4271b98ee 100644
--- a/spec/frontend/groups/components/empty_states/subgroups_and_projects_empty_state_spec.js
+++ b/spec/frontend/groups/components/empty_states/subgroups_and_projects_empty_state_spec.js
@@ -24,10 +24,6 @@ const createComponent = ({ provide = {} } = {}) => {
});
};
-afterEach(() => {
- wrapper.destroy();
-});
-
const findNewSubgroupLink = () =>
wrapper.findByRole('link', {
name: new RegExp(SubgroupsAndProjectsEmptyState.i18n.withLinks.subgroup.title),
diff --git a/spec/frontend/groups/components/group_folder_spec.js b/spec/frontend/groups/components/group_folder_spec.js
index f223333360d..da31fb02f69 100644
--- a/spec/frontend/groups/components/group_folder_spec.js
+++ b/spec/frontend/groups/components/group_folder_spec.js
@@ -20,10 +20,6 @@ describe('GroupFolder component', () => {
},
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('does not render more children stats link when children count of group is under limit', () => {
wrapper = createComponent();
diff --git a/spec/frontend/groups/components/group_item_spec.js b/spec/frontend/groups/components/group_item_spec.js
index 4570aa33a6c..663dd341a58 100644
--- a/spec/frontend/groups/components/group_item_spec.js
+++ b/spec/frontend/groups/components/group_item_spec.js
@@ -37,10 +37,6 @@ describe('GroupItemComponent', () => {
return waitForPromises();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const withMicrodata = (group) => ({
...group,
microdata: getGroupItemMicrodata(group),
diff --git a/spec/frontend/groups/components/group_name_and_path_spec.js b/spec/frontend/groups/components/group_name_and_path_spec.js
index 9965b608f27..0a18e657c94 100644
--- a/spec/frontend/groups/components/group_name_and_path_spec.js
+++ b/spec/frontend/groups/components/group_name_and_path_spec.js
@@ -7,11 +7,11 @@ import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
import GroupNameAndPath from '~/groups/components/group_name_and_path.vue';
import { getGroupPathAvailability } from '~/rest_api';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { helpPagePath } from '~/helpers/help_page_helper';
import searchGroupsWhereUserCanCreateSubgroups from '~/groups/queries/search_groups_where_user_can_create_subgroups.query.graphql';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/rest_api', () => ({
getGroupPathAvailability: jest.fn(),
}));
diff --git a/spec/frontend/groups/components/groups_spec.js b/spec/frontend/groups/components/groups_spec.js
index cae29a8f15a..9ee785d688a 100644
--- a/spec/frontend/groups/components/groups_spec.js
+++ b/spec/frontend/groups/components/groups_spec.js
@@ -37,10 +37,6 @@ describe('GroupsComponent', () => {
Vue.component('GroupItem', GroupItemComponent);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('methods', () => {
describe('change', () => {
it('should emit `fetchPage` event when page is changed via pagination', () => {
diff --git a/spec/frontend/groups/components/invite_members_banner_spec.js b/spec/frontend/groups/components/invite_members_banner_spec.js
index 4a385cb00ee..c4bc35dcd57 100644
--- a/spec/frontend/groups/components/invite_members_banner_spec.js
+++ b/spec/frontend/groups/components/invite_members_banner_spec.js
@@ -42,8 +42,6 @@ describe('InviteMembersBanner', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
mockAxios.restore();
unmockTracking();
});
@@ -59,7 +57,6 @@ describe('InviteMembersBanner', () => {
});
const trackCategory = undefined;
- const buttonClickEvent = 'invite_members_banner_button_clicked';
it('sends the displayEvent when the banner is displayed', () => {
const displayEvent = 'invite_members_banner_displayed';
@@ -80,12 +77,6 @@ describe('InviteMembersBanner', () => {
source: 'invite_members_banner',
});
});
-
- it('sends the buttonClickEvent with correct trackCategory and trackLabel', () => {
- expect(trackingSpy).toHaveBeenCalledWith(trackCategory, buttonClickEvent, {
- label: provide.trackLabel,
- });
- });
});
it('sends the dismissEvent when the banner is dismissed', () => {
diff --git a/spec/frontend/groups/components/item_actions_spec.js b/spec/frontend/groups/components/item_actions_spec.js
index 3ceb038dd3c..fac6fb77709 100644
--- a/spec/frontend/groups/components/item_actions_spec.js
+++ b/spec/frontend/groups/components/item_actions_spec.js
@@ -18,11 +18,6 @@ describe('ItemActions', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findEditGroupBtn = () => wrapper.findByTestId(`edit-group-${mockParentGroupItem.id}-btn`);
const findLeaveGroupBtn = () => wrapper.findByTestId(`leave-group-${mockParentGroupItem.id}-btn`);
const findRemoveGroupBtn = () =>
diff --git a/spec/frontend/groups/components/new_top_level_group_alert_spec.js b/spec/frontend/groups/components/new_top_level_group_alert_spec.js
index db9a5c7b16b..060663747e4 100644
--- a/spec/frontend/groups/components/new_top_level_group_alert_spec.js
+++ b/spec/frontend/groups/components/new_top_level_group_alert_spec.js
@@ -30,10 +30,6 @@ describe('NewTopLevelGroupAlert', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when the component is created', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/groups/components/overview_tabs_spec.js b/spec/frontend/groups/components/overview_tabs_spec.js
index d1ae2c4be17..906609c97f9 100644
--- a/spec/frontend/groups/components/overview_tabs_spec.js
+++ b/spec/frontend/groups/components/overview_tabs_spec.js
@@ -76,7 +76,6 @@ describe('OverviewTabs', () => {
});
afterEach(() => {
- wrapper.destroy();
axiosMock.restore();
});
diff --git a/spec/frontend/groups/components/transfer_group_form_spec.js b/spec/frontend/groups/components/transfer_group_form_spec.js
index 0065820f78f..fd0c3907e04 100644
--- a/spec/frontend/groups/components/transfer_group_form_spec.js
+++ b/spec/frontend/groups/components/transfer_group_form_spec.js
@@ -48,10 +48,6 @@ describe('Transfer group form', () => {
const findTransferLocations = () => wrapper.findComponent(TransferLocations);
const findHiddenInput = () => wrapper.find('[name="new_parent_group_id"]');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/groups_projects/components/transfer_locations_spec.js b/spec/frontend/groups_projects/components/transfer_locations_spec.js
index 77c0966ba1e..86913bb4c09 100644
--- a/spec/frontend/groups_projects/components/transfer_locations_spec.js
+++ b/spec/frontend/groups_projects/components/transfer_locations_spec.js
@@ -109,10 +109,6 @@ describe('TransferLocations', () => {
const intersectionObserverEmitAppear = () => findIntersectionObserver().vm.$emit('appear');
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when `GlDropdown` is opened', () => {
it('shows loading icon', async () => {
getTransferLocations.mockReturnValueOnce(new Promise(() => {}));
diff --git a/spec/frontend/header_search/components/app_spec.js b/spec/frontend/header_search/components/app_spec.js
index d6263c663d2..8e84c672d90 100644
--- a/spec/frontend/header_search/components/app_spec.js
+++ b/spec/frontend/header_search/components/app_spec.js
@@ -80,10 +80,6 @@ describe('HeaderSearchApp', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findHeaderSearchForm = () => wrapper.findByTestId('header-search-form');
const findHeaderSearchInput = () => wrapper.findComponent(GlSearchBoxByType);
const findScopeToken = () => wrapper.findComponent(GlToken);
@@ -187,10 +183,10 @@ describe('HeaderSearchApp', () => {
describe.each`
username | showDropdown | expectedDesc
- ${null} | ${false} | ${HeaderSearchApp.i18n.searchInputDescribeByNoDropdown}
- ${null} | ${true} | ${HeaderSearchApp.i18n.searchInputDescribeByNoDropdown}
- ${MOCK_USERNAME} | ${false} | ${HeaderSearchApp.i18n.searchInputDescribeByWithDropdown}
- ${MOCK_USERNAME} | ${true} | ${HeaderSearchApp.i18n.searchInputDescribeByWithDropdown}
+ ${null} | ${false} | ${HeaderSearchApp.i18n.SEARCH_INPUT_DESCRIBE_BY_NO_DROPDOWN}
+ ${null} | ${true} | ${HeaderSearchApp.i18n.SEARCH_INPUT_DESCRIBE_BY_NO_DROPDOWN}
+ ${MOCK_USERNAME} | ${false} | ${HeaderSearchApp.i18n.SEARCH_INPUT_DESCRIBE_BY_WITH_DROPDOWN}
+ ${MOCK_USERNAME} | ${true} | ${HeaderSearchApp.i18n.SEARCH_INPUT_DESCRIBE_BY_WITH_DROPDOWN}
`('Search Input Description', ({ username, showDropdown, expectedDesc }) => {
describe(`current_username is ${username} and showDropdown is ${showDropdown}`, () => {
beforeEach(() => {
@@ -212,7 +208,7 @@ describe('HeaderSearchApp', () => {
${MOCK_USERNAME} | ${true} | ${''} | ${false} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${`${MOCK_DEFAULT_SEARCH_OPTIONS.length} default results provided. Use the up and down arrow keys to navigate search results list.`}
${MOCK_USERNAME} | ${true} | ${''} | ${true} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${`${MOCK_DEFAULT_SEARCH_OPTIONS.length} default results provided. Use the up and down arrow keys to navigate search results list.`}
${MOCK_USERNAME} | ${true} | ${MOCK_SEARCH} | ${false} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${`Results updated. ${MOCK_SCOPED_SEARCH_OPTIONS.length} results available. Use the up and down arrow keys to navigate search results list, or ENTER to submit.`}
- ${MOCK_USERNAME} | ${true} | ${MOCK_SEARCH} | ${true} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${HeaderSearchApp.i18n.searchResultsLoading}
+ ${MOCK_USERNAME} | ${true} | ${MOCK_SEARCH} | ${true} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${HeaderSearchApp.i18n.SEARCH_RESULTS_LOADING}
`(
'Search Results Description',
({ username, showDropdown, search, loading, searchOptions, expectedDesc }) => {
@@ -354,8 +350,8 @@ describe('HeaderSearchApp', () => {
describe('events', () => {
beforeEach(() => {
- createComponent();
window.gon.current_username = MOCK_USERNAME;
+ createComponent();
});
describe('Header Search Input', () => {
@@ -463,8 +459,8 @@ describe('HeaderSearchApp', () => {
${2} | ${'test1'}
`('currentFocusedOption', ({ MOCK_INDEX, search }) => {
beforeEach(() => {
- createComponent({ search });
window.gon.current_username = MOCK_USERNAME;
+ createComponent({ search });
findHeaderSearchInput().vm.$emit('click');
});
@@ -504,8 +500,8 @@ describe('HeaderSearchApp', () => {
const MOCK_INDEX = 1;
beforeEach(() => {
- createComponent();
window.gon.current_username = MOCK_USERNAME;
+ createComponent();
findHeaderSearchInput().vm.$emit('click');
});
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 7952661e2d2..e77a9231b7a 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
@@ -3,15 +3,14 @@ import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import HeaderSearchAutocompleteItems from '~/header_search/components/header_search_autocomplete_items.vue';
+import { LARGE_AVATAR_PX, SMALL_AVATAR_PX } from '~/header_search/constants';
import {
- GROUPS_CATEGORY,
- LARGE_AVATAR_PX,
PROJECTS_CATEGORY,
- SMALL_AVATAR_PX,
+ GROUPS_CATEGORY,
ISSUES_CATEGORY,
MERGE_REQUEST_CATEGORY,
RECENT_EPICS_CATEGORY,
-} from '~/header_search/constants';
+} from '~/vue_shared/global_search/constants';
import {
MOCK_GROUPED_AUTOCOMPLETE_OPTIONS,
MOCK_SORTED_AUTOCOMPLETE_OPTIONS,
@@ -46,10 +45,6 @@ describe('HeaderSearchAutocompleteItems', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findGlDropdownDividers = () => wrapper.findAllComponents(GlDropdownDivider);
const findFirstDropdownItem = () => findDropdownItems().at(0);
diff --git a/spec/frontend/header_search/components/header_search_default_items_spec.js b/spec/frontend/header_search/components/header_search_default_items_spec.js
index abcacc487df..3768862d83e 100644
--- a/spec/frontend/header_search/components/header_search_default_items_spec.js
+++ b/spec/frontend/header_search/components/header_search_default_items_spec.js
@@ -29,10 +29,6 @@ describe('HeaderSearchDefaultItems', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findDropdownHeader = () => wrapper.findComponent(GlDropdownSectionHeader);
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findFirstDropdownItem = () => findDropdownItems().at(0);
diff --git a/spec/frontend/header_search/components/header_search_scoped_items_spec.js b/spec/frontend/header_search/components/header_search_scoped_items_spec.js
index 2db9f71d702..51d67198f04 100644
--- a/spec/frontend/header_search/components/header_search_scoped_items_spec.js
+++ b/spec/frontend/header_search/components/header_search_scoped_items_spec.js
@@ -5,7 +5,8 @@ import Vuex from 'vuex';
import { trimText } from 'helpers/text_helper';
import HeaderSearchScopedItems from '~/header_search/components/header_search_scoped_items.vue';
import { truncate } from '~/lib/utils/text_utility';
-import { MSG_IN_ALL_GITLAB, SCOPE_TOKEN_MAX_LENGTH } from '~/header_search/constants';
+import { SCOPE_TOKEN_MAX_LENGTH } from '~/header_search/constants';
+import { MSG_IN_ALL_GITLAB } from '~/vue_shared/global_search/constants';
import {
MOCK_SEARCH,
MOCK_SCOPED_SEARCH_OPTIONS,
@@ -38,10 +39,6 @@ describe('HeaderSearchScopedItems', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findFirstDropdownItem = () => findDropdownItems().at(0);
const findDropdownItemTitles = () => findDropdownItems().wrappers.map((w) => trimText(w.text()));
diff --git a/spec/frontend/header_search/mock_data.js b/spec/frontend/header_search/mock_data.js
index 3a8624ad9dd..2218c81efc3 100644
--- a/spec/frontend/header_search/mock_data.js
+++ b/spec/frontend/header_search/mock_data.js
@@ -1,16 +1,14 @@
+import { ICON_PROJECT, ICON_GROUP, ICON_SUBGROUP } from '~/header_search/constants';
import {
+ PROJECTS_CATEGORY,
+ GROUPS_CATEGORY,
MSG_ISSUES_ASSIGNED_TO_ME,
MSG_ISSUES_IVE_CREATED,
MSG_MR_ASSIGNED_TO_ME,
MSG_MR_IM_REVIEWER,
MSG_MR_IVE_CREATED,
MSG_IN_ALL_GITLAB,
- PROJECTS_CATEGORY,
- ICON_PROJECT,
- GROUPS_CATEGORY,
- ICON_GROUP,
- ICON_SUBGROUP,
-} from '~/header_search/constants';
+} from '~/vue_shared/global_search/constants';
export const MOCK_USERNAME = 'anyone';
diff --git a/spec/frontend/header_search/store/actions_spec.js b/spec/frontend/header_search/store/actions_spec.js
index bd93b0edadf..95a619ebeca 100644
--- a/spec/frontend/header_search/store/actions_spec.js
+++ b/spec/frontend/header_search/store/actions_spec.js
@@ -16,7 +16,7 @@ import {
MOCK_ISSUE_PATH,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Header Search Store Actions', () => {
let state;
diff --git a/spec/frontend/header_search/store/getters_spec.js b/spec/frontend/header_search/store/getters_spec.js
index a1d9481b5cc..7a7a00178f1 100644
--- a/spec/frontend/header_search/store/getters_spec.js
+++ b/spec/frontend/header_search/store/getters_spec.js
@@ -241,6 +241,13 @@ describe('Header Search Store Getters', () => {
MOCK_DEFAULT_SEARCH_OPTIONS,
);
});
+
+ it('returns the correct array if issues path is false', () => {
+ mockGetters.scopedIssuesPath = undefined;
+ expect(getters.defaultSearchOptions(state, mockGetters)).toStrictEqual(
+ MOCK_DEFAULT_SEARCH_OPTIONS.slice(2, MOCK_DEFAULT_SEARCH_OPTIONS.length),
+ );
+ });
});
describe('scopedSearchOptions', () => {
diff --git a/spec/frontend/helpers/startup_css_helper_spec.js b/spec/frontend/helpers/startup_css_helper_spec.js
index 05161437c22..28c742386cc 100644
--- a/spec/frontend/helpers/startup_css_helper_spec.js
+++ b/spec/frontend/helpers/startup_css_helper_spec.js
@@ -21,17 +21,10 @@ describe('waitForCSSLoaded', () => {
});
describe('when gon features is not provided', () => {
- let originalGon;
-
beforeEach(() => {
- originalGon = window.gon;
window.gon = null;
});
- afterEach(() => {
- window.gon = originalGon;
- });
-
it('should invoke the action right away', async () => {
const events = waitForCSSLoaded(mockedCallback);
await events;
diff --git a/spec/frontend/ide/components/activity_bar_spec.js b/spec/frontend/ide/components/activity_bar_spec.js
index a97e883a8bf..ff04f9a84f1 100644
--- a/spec/frontend/ide/components/activity_bar_spec.js
+++ b/spec/frontend/ide/components/activity_bar_spec.js
@@ -22,10 +22,6 @@ describe('IDE ActivityBar component', () => {
wrapper = shallowMount(ActivityBar, { store });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('updateActivityBarView', () => {
beforeEach(() => {
mountComponent();
diff --git a/spec/frontend/ide/components/branches/item_spec.js b/spec/frontend/ide/components/branches/item_spec.js
index 3dbd1210916..4cae146cbd2 100644
--- a/spec/frontend/ide/components/branches/item_spec.js
+++ b/spec/frontend/ide/components/branches/item_spec.js
@@ -34,10 +34,6 @@ describe('IDE branch item', () => {
router = createRouter(store);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('if not active', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/ide/components/branches/search_list_spec.js b/spec/frontend/ide/components/branches/search_list_spec.js
index bbde45d700f..eeab26f7559 100644
--- a/spec/frontend/ide/components/branches/search_list_spec.js
+++ b/spec/frontend/ide/components/branches/search_list_spec.js
@@ -35,11 +35,6 @@ describe('IDE branches search list', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('calls fetch on mounted', () => {
createComponent();
expect(fetchBranchesMock).toHaveBeenCalled();
diff --git a/spec/frontend/ide/components/cannot_push_code_alert_spec.js b/spec/frontend/ide/components/cannot_push_code_alert_spec.js
index ff659ecdf3f..d4db2246008 100644
--- a/spec/frontend/ide/components/cannot_push_code_alert_spec.js
+++ b/spec/frontend/ide/components/cannot_push_code_alert_spec.js
@@ -10,10 +10,6 @@ const TEST_BUTTON_TEXT = 'Fork text';
describe('ide/components/cannot_push_code_alert', () => {
let wrapper;
- afterEach(() => {
- wrapper.destroy();
- });
-
const createComponent = (props = {}) => {
wrapper = shallowMount(CannotPushCodeAlert, {
propsData: {
diff --git a/spec/frontend/ide/components/commit_sidebar/actions_spec.js b/spec/frontend/ide/components/commit_sidebar/actions_spec.js
index dc103fec5d0..019469cbf87 100644
--- a/spec/frontend/ide/components/commit_sidebar/actions_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/actions_spec.js
@@ -46,10 +46,6 @@ describe('IDE commit sidebar actions', () => {
jest.spyOn(store, 'dispatch').mockImplementation(() => {});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findText = () => wrapper.text();
const findRadios = () => wrapper.findAll('input[type="radio"]');
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 f6d5833edee..ce43e648b43 100644
--- a/spec/frontend/ide/components/commit_sidebar/editor_header_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/editor_header_spec.js
@@ -1,7 +1,9 @@
-import { mount } from '@vue/test-utils';
+import { GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import EditorHeader from '~/ide/components/commit_sidebar/editor_header.vue';
+import { stubComponent } from 'helpers/stub_component';
import { createStore } from '~/ide/stores';
import { file } from '../../helpers';
@@ -12,9 +14,10 @@ const TEST_FILE_PATH = 'test/file/path';
describe('IDE commit editor header', () => {
let wrapper;
let store;
+ const showMock = jest.fn();
const createComponent = (fileProps = {}) => {
- wrapper = mount(EditorHeader, {
+ wrapper = shallowMount(EditorHeader, {
store,
propsData: {
activeFile: {
@@ -23,22 +26,17 @@ describe('IDE commit editor header', () => {
...fileProps,
},
},
+ stubs: {
+ GlModal: stubComponent(GlModal, {
+ methods: { show: showMock },
+ }),
+ },
});
};
const findDiscardModal = () => wrapper.findComponent({ ref: 'discardModal' });
const findDiscardButton = () => wrapper.findComponent({ ref: 'discardButton' });
- beforeEach(() => {
- store = createStore();
- jest.spyOn(store, 'dispatch').mockImplementation();
- });
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it.each`
fileProps | shouldExist
${{ staged: false, changed: false }} | ${false}
@@ -52,20 +50,19 @@ describe('IDE commit editor header', () => {
});
describe('discard button', () => {
- beforeEach(() => {
+ it('opens a dialog confirming discard', () => {
createComponent();
+ findDiscardButton().vm.$emit('click');
- const modal = findDiscardModal();
- jest.spyOn(modal.vm, 'show');
-
- findDiscardButton().trigger('click');
- });
-
- it('opens a dialog confirming discard', () => {
- expect(findDiscardModal().vm.show).toHaveBeenCalled();
+ expect(showMock).toHaveBeenCalled();
});
it('calls discardFileChanges if dialog result is confirmed', () => {
+ store = createStore();
+ jest.spyOn(store, 'dispatch').mockImplementation();
+
+ createComponent();
+
expect(store.dispatch).not.toHaveBeenCalled();
findDiscardModal().vm.$emit('primary');
diff --git a/spec/frontend/ide/components/commit_sidebar/empty_state_spec.js b/spec/frontend/ide/components/commit_sidebar/empty_state_spec.js
index 7c48c0e6f95..4a6aafe42ae 100644
--- a/spec/frontend/ide/components/commit_sidebar/empty_state_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/empty_state_spec.js
@@ -11,10 +11,6 @@ describe('IDE commit panel EmptyState component', () => {
wrapper = shallowMount(EmptyState, { store });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders no changes text when last commit message is empty', () => {
expect(wrapper.find('h4').text()).toBe('No changes');
});
diff --git a/spec/frontend/ide/components/commit_sidebar/form_spec.js b/spec/frontend/ide/components/commit_sidebar/form_spec.js
index a8ee81afa0b..0c0998c037a 100644
--- a/spec/frontend/ide/components/commit_sidebar/form_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/form_spec.js
@@ -26,7 +26,7 @@ describe('IDE commit form', () => {
wrapper = shallowMount(CommitForm, {
store,
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
stubs: {
GlModal: stubComponent(GlModal),
@@ -73,10 +73,6 @@ describe('IDE commit form', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
// Notes:
// - When there are no changes, there is no commit button so there's nothing to test :)
describe.each`
diff --git a/spec/frontend/ide/components/commit_sidebar/list_item_spec.js b/spec/frontend/ide/components/commit_sidebar/list_item_spec.js
index c9571d39acb..c2a33c0d71e 100644
--- a/spec/frontend/ide/components/commit_sidebar/list_item_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/list_item_spec.js
@@ -36,10 +36,6 @@ describe('Multi-file editor commit sidebar list item', () => {
findPathEl = wrapper.find('.multi-file-commit-list-path');
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findPathText = () => trimText(findPathEl.text());
it('renders file path', () => {
diff --git a/spec/frontend/ide/components/commit_sidebar/list_spec.js b/spec/frontend/ide/components/commit_sidebar/list_spec.js
index 4406d14d990..6b9ba939a87 100644
--- a/spec/frontend/ide/components/commit_sidebar/list_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/list_spec.js
@@ -19,10 +19,6 @@ describe('Multi-file editor commit sidebar list', () => {
},
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with a list of files', () => {
beforeEach(async () => {
const f = file('file name');
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 c2ef29c1059..3403a7b8ad9 100644
--- a/spec/frontend/ide/components/commit_sidebar/message_field_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/message_field_spec.js
@@ -15,10 +15,6 @@ describe('IDE commit message field', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findMessage = () => wrapper.find('textarea');
const findHighlights = () => wrapper.findAll('.highlights span');
const findMarks = () => wrapper.findAll('mark');
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 2a455c9d7c1..adc9a0f1421 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
@@ -33,15 +33,11 @@ describe('NewMergeRequestOption component', () => {
},
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when the `shouldHideNewMrOption` getter returns false', () => {
beforeEach(() => {
createComponent();
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 a3fa03a4aa5..cdf14056523 100644
--- a/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js
@@ -19,15 +19,11 @@ describe('IDE commit sidebar radio group', () => {
propsData: config.props,
slots: config.slots,
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('without input', () => {
const props = {
value: '1',
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 63d51953915..d1a81dd1639 100644
--- a/spec/frontend/ide/components/commit_sidebar/success_message_spec.js
+++ b/spec/frontend/ide/components/commit_sidebar/success_message_spec.js
@@ -12,10 +12,6 @@ describe('IDE commit panel successful commit state', () => {
wrapper = shallowMount(SuccessMessage, { store });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders last commit message when it exists', () => {
expect(wrapper.text()).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 204d39de741..5f6579654bc 100644
--- a/spec/frontend/ide/components/error_message_spec.js
+++ b/spec/frontend/ide/components/error_message_spec.js
@@ -32,11 +32,6 @@ describe('IDE error message component', () => {
setErrorMessageMock.mockReset();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findDismissButton = () => wrapper.find('button[aria-label=Dismiss]');
const findActionButton = () => wrapper.find('button.gl-alert-action');
diff --git a/spec/frontend/ide/components/file_row_extra_spec.js b/spec/frontend/ide/components/file_row_extra_spec.js
index 281c549a1b4..f5a6e7222f9 100644
--- a/spec/frontend/ide/components/file_row_extra_spec.js
+++ b/spec/frontend/ide/components/file_row_extra_spec.js
@@ -37,8 +37,6 @@ describe('IDE extra file row component', () => {
};
afterEach(() => {
- wrapper.destroy();
-
stagedFilesCount = 0;
unstagedFilesCount = 0;
changesCount = 0;
diff --git a/spec/frontend/ide/components/file_templates/bar_spec.js b/spec/frontend/ide/components/file_templates/bar_spec.js
index 60f37260393..b8c850fdd13 100644
--- a/spec/frontend/ide/components/file_templates/bar_spec.js
+++ b/spec/frontend/ide/components/file_templates/bar_spec.js
@@ -21,10 +21,6 @@ describe('IDE file templates bar component', () => {
wrapper = mount(Bar, { store });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template type dropdown', () => {
it('renders dropdown component', () => {
expect(wrapper.find('.dropdown').text()).toContain('Choose a type');
diff --git a/spec/frontend/ide/components/file_templates/dropdown_spec.js b/spec/frontend/ide/components/file_templates/dropdown_spec.js
index ee90d87357c..72fdd05eb2c 100644
--- a/spec/frontend/ide/components/file_templates/dropdown_spec.js
+++ b/spec/frontend/ide/components/file_templates/dropdown_spec.js
@@ -49,11 +49,6 @@ describe('IDE file templates dropdown component', () => {
({ element } = wrapper);
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('calls clickItem on click', async () => {
const itemData = { name: 'test.yml ' };
createComponent({ props: { data: [itemData] } });
diff --git a/spec/frontend/ide/components/ide_file_row_spec.js b/spec/frontend/ide/components/ide_file_row_spec.js
index aa66224fa19..331877ff112 100644
--- a/spec/frontend/ide/components/ide_file_row_spec.js
+++ b/spec/frontend/ide/components/ide_file_row_spec.js
@@ -34,11 +34,6 @@ describe('Ide File Row component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findFileRowExtra = () => wrapper.findComponent(FileRowExtra);
const findFileRow = () => wrapper.findComponent(FileRow);
const hasDropdownOpen = () => findFileRowExtra().props('dropdownOpen');
diff --git a/spec/frontend/ide/components/ide_project_header_spec.js b/spec/frontend/ide/components/ide_project_header_spec.js
index d0636352a3f..7613f407e45 100644
--- a/spec/frontend/ide/components/ide_project_header_spec.js
+++ b/spec/frontend/ide/components/ide_project_header_spec.js
@@ -20,10 +20,6 @@ describe('IDE project header', () => {
wrapper = shallowMount(IDEProjectHeader, { propsData: { project: mockProject } });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/ide/components/ide_review_spec.js b/spec/frontend/ide/components/ide_review_spec.js
index 0759f957374..e6fd018969f 100644
--- a/spec/frontend/ide/components/ide_review_spec.js
+++ b/spec/frontend/ide/components/ide_review_spec.js
@@ -30,10 +30,6 @@ describe('IDE review mode', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders list of files', () => {
expect(wrapper.text()).toContain('fileName');
});
diff --git a/spec/frontend/ide/components/ide_side_bar_spec.js b/spec/frontend/ide/components/ide_side_bar_spec.js
index 4784d6c516f..c258c5312d8 100644
--- a/spec/frontend/ide/components/ide_side_bar_spec.js
+++ b/spec/frontend/ide/components/ide_side_bar_spec.js
@@ -29,11 +29,6 @@ describe('IdeSidebar', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders a sidebar', () => {
wrapper = createComponent();
diff --git a/spec/frontend/ide/components/ide_sidebar_nav_spec.js b/spec/frontend/ide/components/ide_sidebar_nav_spec.js
index 80e8aba4072..4ee24f63f76 100644
--- a/spec/frontend/ide/components/ide_sidebar_nav_spec.js
+++ b/spec/frontend/ide/components/ide_sidebar_nav_spec.js
@@ -25,10 +25,6 @@ describe('ide/components/ide_sidebar_nav', () => {
let wrapper;
const createComponent = (props = {}) => {
- if (wrapper) {
- throw new Error('wrapper already exists');
- }
-
wrapper = shallowMount(IdeSidebarNav, {
propsData: {
tabs: TEST_TABS,
@@ -37,16 +33,11 @@ describe('ide/components/ide_sidebar_nav', () => {
...props,
},
directives: {
- tooltip: createMockDirective(),
+ tooltip: createMockDirective('tooltip'),
},
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findButtons = () => wrapper.findAll('li button');
const findButtonsData = () =>
findButtons().wrappers.map((button) => {
diff --git a/spec/frontend/ide/components/ide_spec.js b/spec/frontend/ide/components/ide_spec.js
index a575f428a69..1c8d570cdce 100644
--- a/spec/frontend/ide/components/ide_spec.js
+++ b/spec/frontend/ide/components/ide_spec.js
@@ -52,8 +52,6 @@ describe('WebIDE', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
window.onbeforeunload = null;
});
diff --git a/spec/frontend/ide/components/ide_status_bar_spec.js b/spec/frontend/ide/components/ide_status_bar_spec.js
index e6e0ebaf1e8..0ee16f98e7e 100644
--- a/spec/frontend/ide/components/ide_status_bar_spec.js
+++ b/spec/frontend/ide/components/ide_status_bar_spec.js
@@ -34,10 +34,6 @@ describe('IdeStatusBar component', () => {
wrapper = mount(IdeStatusBar, { store });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
it('triggers a setInterval', () => {
mountComponent();
diff --git a/spec/frontend/ide/components/ide_status_list_spec.js b/spec/frontend/ide/components/ide_status_list_spec.js
index 0b54e8b6afb..344a1fbc4f6 100644
--- a/spec/frontend/ide/components/ide_status_list_spec.js
+++ b/spec/frontend/ide/components/ide_status_list_spec.js
@@ -53,10 +53,7 @@ describe('ide/components/ide_status_list', () => {
});
afterEach(() => {
- wrapper.destroy();
-
store = null;
- wrapper = null;
});
describe('with regular file', () => {
diff --git a/spec/frontend/ide/components/ide_status_mr_spec.js b/spec/frontend/ide/components/ide_status_mr_spec.js
index 0b9111c0e2a..3501ecce061 100644
--- a/spec/frontend/ide/components/ide_status_mr_spec.js
+++ b/spec/frontend/ide/components/ide_status_mr_spec.js
@@ -17,10 +17,6 @@ describe('ide/components/ide_status_mr', () => {
const findIcon = () => wrapper.findComponent(GlIcon);
const findLink = () => wrapper.findComponent(GlLink);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when mounted', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/ide/components/ide_tree_list_spec.js b/spec/frontend/ide/components/ide_tree_list_spec.js
index 0f61aa80e53..427daa57324 100644
--- a/spec/frontend/ide/components/ide_tree_list_spec.js
+++ b/spec/frontend/ide/components/ide_tree_list_spec.js
@@ -25,10 +25,6 @@ describe('IdeTreeList component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('normal branch', () => {
const tree = [file('fileName')];
diff --git a/spec/frontend/ide/components/ide_tree_spec.js b/spec/frontend/ide/components/ide_tree_spec.js
index f00017a2736..9f452910496 100644
--- a/spec/frontend/ide/components/ide_tree_spec.js
+++ b/spec/frontend/ide/components/ide_tree_spec.js
@@ -29,10 +29,6 @@ describe('IdeTree', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders list of files', () => {
expect(wrapper.text()).toContain('fileName');
});
diff --git a/spec/frontend/ide/components/jobs/detail/description_spec.js b/spec/frontend/ide/components/jobs/detail/description_spec.js
index 629c4424314..2bb0f3fccf4 100644
--- a/spec/frontend/ide/components/jobs/detail/description_spec.js
+++ b/spec/frontend/ide/components/jobs/detail/description_spec.js
@@ -14,10 +14,6 @@ describe('IDE job description', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders job details', () => {
expect(wrapper.text()).toContain('#1');
expect(wrapper.text()).toContain('test');
diff --git a/spec/frontend/ide/components/jobs/detail/scroll_button_spec.js b/spec/frontend/ide/components/jobs/detail/scroll_button_spec.js
index 5eb66f75978..eec1bd6b123 100644
--- a/spec/frontend/ide/components/jobs/detail/scroll_button_spec.js
+++ b/spec/frontend/ide/components/jobs/detail/scroll_button_spec.js
@@ -15,10 +15,6 @@ describe('IDE job log scroll button', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe.each`
direction | icon | title
${'up'} | ${'scroll_up'} | ${'Scroll to top'}
diff --git a/spec/frontend/ide/components/jobs/detail_spec.js b/spec/frontend/ide/components/jobs/detail_spec.js
index bf2be3aa595..60e03a7b882 100644
--- a/spec/frontend/ide/components/jobs/detail_spec.js
+++ b/spec/frontend/ide/components/jobs/detail_spec.js
@@ -34,10 +34,6 @@ describe('IDE jobs detail view', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('mounted', () => {
const findJobOutput = () => wrapper.find('.bash');
const findBuildLoaderAnimation = () => wrapper.find('.build-loader-animation');
diff --git a/spec/frontend/ide/components/jobs/item_spec.js b/spec/frontend/ide/components/jobs/item_spec.js
index 32e27333e42..ab442a27817 100644
--- a/spec/frontend/ide/components/jobs/item_spec.js
+++ b/spec/frontend/ide/components/jobs/item_spec.js
@@ -12,10 +12,6 @@ describe('IDE jobs item', () => {
wrapper = mount(JobItem, { propsData: { job } });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders job details', () => {
expect(wrapper.text()).toContain(job.name);
expect(wrapper.text()).toContain(`#${job.id}`);
diff --git a/spec/frontend/ide/components/jobs/stage_spec.js b/spec/frontend/ide/components/jobs/stage_spec.js
index 52fbff2f497..23ef92f9682 100644
--- a/spec/frontend/ide/components/jobs/stage_spec.js
+++ b/spec/frontend/ide/components/jobs/stage_spec.js
@@ -31,11 +31,6 @@ describe('IDE pipeline stage', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('emits fetch event when mounted', () => {
createComponent();
expect(wrapper.emitted().fetch).toBeDefined();
diff --git a/spec/frontend/ide/components/merge_requests/item_spec.js b/spec/frontend/ide/components/merge_requests/item_spec.js
index d6cf8127b53..2fbb6919b8b 100644
--- a/spec/frontend/ide/components/merge_requests/item_spec.js
+++ b/spec/frontend/ide/components/merge_requests/item_spec.js
@@ -39,11 +39,6 @@ describe('IDE merge request item', () => {
router = createRouter(store);
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('default', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/ide/components/merge_requests/list_spec.js b/spec/frontend/ide/components/merge_requests/list_spec.js
index ea6e2741a85..3b0e8c632fb 100644
--- a/spec/frontend/ide/components/merge_requests/list_spec.js
+++ b/spec/frontend/ide/components/merge_requests/list_spec.js
@@ -48,11 +48,6 @@ describe('IDE merge requests list', () => {
fetchMergeRequestsMock = jest.fn();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('calls fetch on mounted', () => {
createComponent();
expect(fetchMergeRequestsMock).toHaveBeenCalledWith(expect.any(Object), {
diff --git a/spec/frontend/ide/components/nav_dropdown_button_spec.js b/spec/frontend/ide/components/nav_dropdown_button_spec.js
index 8eebcdd9e08..3aae2c83e80 100644
--- a/spec/frontend/ide/components/nav_dropdown_button_spec.js
+++ b/spec/frontend/ide/components/nav_dropdown_button_spec.js
@@ -9,10 +9,6 @@ describe('NavDropdownButton component', () => {
const TEST_MR_ID = '12345';
let wrapper;
- afterEach(() => {
- wrapper.destroy();
- });
-
const createComponent = ({ props = {}, state = {} } = {}) => {
const store = createStore();
store.replaceState(state);
diff --git a/spec/frontend/ide/components/nav_dropdown_spec.js b/spec/frontend/ide/components/nav_dropdown_spec.js
index 33e638843f5..794aaba6d01 100644
--- a/spec/frontend/ide/components/nav_dropdown_spec.js
+++ b/spec/frontend/ide/components/nav_dropdown_spec.js
@@ -30,10 +30,6 @@ describe('IDE NavDropdown', () => {
jest.spyOn(store, 'dispatch').mockImplementation(() => {});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const createComponent = () => {
wrapper = mount(NavDropdown, {
store,
diff --git a/spec/frontend/ide/components/new_dropdown/button_spec.js b/spec/frontend/ide/components/new_dropdown/button_spec.js
index a9cfdfd20c1..bfd5cdf7263 100644
--- a/spec/frontend/ide/components/new_dropdown/button_spec.js
+++ b/spec/frontend/ide/components/new_dropdown/button_spec.js
@@ -14,10 +14,6 @@ describe('IDE new entry dropdown button component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders button with label', () => {
createComponent();
diff --git a/spec/frontend/ide/components/new_dropdown/index_spec.js b/spec/frontend/ide/components/new_dropdown/index_spec.js
index 747c099db33..01dcb174c41 100644
--- a/spec/frontend/ide/components/new_dropdown/index_spec.js
+++ b/spec/frontend/ide/components/new_dropdown/index_spec.js
@@ -30,10 +30,6 @@ describe('new dropdown component', () => {
jest.spyOn(wrapper.vm.$refs.newModal, 'open').mockImplementation(() => {});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders new file, upload and new directory links', () => {
expect(findAllButtons().at(0).text()).toBe('New file');
expect(findAllButtons().at(1).text()).toBe('Upload file');
diff --git a/spec/frontend/ide/components/new_dropdown/modal_spec.js b/spec/frontend/ide/components/new_dropdown/modal_spec.js
index c6f9fd0c4ea..36c3d323e63 100644
--- a/spec/frontend/ide/components/new_dropdown/modal_spec.js
+++ b/spec/frontend/ide/components/new_dropdown/modal_spec.js
@@ -1,13 +1,13 @@
import { GlButton, GlModal } from '@gitlab/ui';
import { nextTick } from 'vue';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import Modal from '~/ide/components/new_dropdown/modal.vue';
import { createStore } from '~/ide/stores';
import { stubComponent } from 'helpers/stub_component';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createEntriesFromPaths } from '../../helpers';
-jest.mock('~/flash');
+jest.mock('~/alert');
const NEW_NAME = 'babar';
@@ -79,7 +79,6 @@ describe('new file modal component', () => {
afterEach(() => {
store = null;
- wrapper.destroy();
document.body.innerHTML = '';
});
@@ -94,11 +93,11 @@ describe('new file modal component', () => {
it('renders modal', () => {
expect(findGlModal().props()).toMatchObject({
actionCancel: {
- attributes: [{ variant: 'default' }],
+ attributes: { variant: 'default' },
text: 'Cancel',
},
actionPrimary: {
- attributes: [{ variant: 'confirm' }],
+ attributes: { variant: 'confirm' },
text: 'Create file',
},
actionSecondary: null,
@@ -170,7 +169,7 @@ describe('new file modal component', () => {
expect(findGlModal().props()).toMatchObject({
title: modalTitle,
actionPrimary: {
- attributes: [{ variant: 'confirm' }],
+ attributes: { variant: 'confirm' },
text: btnTitle,
},
});
@@ -298,7 +297,7 @@ describe('new file modal component', () => {
expect(findGlModal().props()).toMatchObject({
title,
actionPrimary: {
- attributes: [{ variant: 'confirm' }],
+ attributes: { variant: 'confirm' },
text: title,
},
});
@@ -340,7 +339,7 @@ describe('new file modal component', () => {
});
});
- it('does not trigger flash', () => {
+ it('does not trigger alert', () => {
expect(createAlert).not.toHaveBeenCalled();
});
});
@@ -359,7 +358,7 @@ describe('new file modal component', () => {
});
});
- it('does not trigger flash', () => {
+ it('does not trigger alert', () => {
expect(createAlert).not.toHaveBeenCalled();
});
});
@@ -379,7 +378,7 @@ describe('new file modal component', () => {
triggerSubmitModal();
});
- it('creates flash', () => {
+ it('creates alert', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'The name "src" is already taken in this directory.',
fadeTransition: false,
@@ -404,7 +403,7 @@ describe('new file modal component', () => {
triggerSubmitModal();
});
- it('does not create flash', () => {
+ it('does not create alert', () => {
expect(createAlert).not.toHaveBeenCalled();
});
diff --git a/spec/frontend/ide/components/new_dropdown/upload_spec.js b/spec/frontend/ide/components/new_dropdown/upload_spec.js
index fc643589d51..40780c7f0bd 100644
--- a/spec/frontend/ide/components/new_dropdown/upload_spec.js
+++ b/spec/frontend/ide/components/new_dropdown/upload_spec.js
@@ -12,10 +12,6 @@ describe('new dropdown upload', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('openFile', () => {
it('calls for each file', () => {
const files = ['test', 'test2', 'test3'];
diff --git a/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js b/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js
index e92f843ae6e..42eb5b3fc7a 100644
--- a/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js
+++ b/spec/frontend/ide/components/panes/collapsible_sidebar_spec.js
@@ -35,11 +35,6 @@ describe('ide/components/panes/collapsible_sidebar.vue', () => {
jest.spyOn(store, 'dispatch').mockImplementation();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('with a tab', () => {
let fakeView;
let extensionTabs;
diff --git a/spec/frontend/ide/components/panes/right_spec.js b/spec/frontend/ide/components/panes/right_spec.js
index 1d81c3ea89d..832983edf21 100644
--- a/spec/frontend/ide/components/panes/right_spec.js
+++ b/spec/frontend/ide/components/panes/right_spec.js
@@ -28,11 +28,6 @@ describe('ide/components/panes/right.vue', () => {
store = createStore();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('default', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/ide/components/pipelines/empty_state_spec.js b/spec/frontend/ide/components/pipelines/empty_state_spec.js
index 31081e8f9d5..71de9aecb52 100644
--- a/spec/frontend/ide/components/pipelines/empty_state_spec.js
+++ b/spec/frontend/ide/components/pipelines/empty_state_spec.js
@@ -22,10 +22,6 @@ describe('~/ide/components/pipelines/empty_state.vue', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/ide/components/pipelines/list_spec.js b/spec/frontend/ide/components/pipelines/list_spec.js
index d82b97561f0..e913fa84d56 100644
--- a/spec/frontend/ide/components/pipelines/list_spec.js
+++ b/spec/frontend/ide/components/pipelines/list_spec.js
@@ -65,11 +65,6 @@ describe('IDE pipelines list', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('fetches latest pipeline', () => {
createComponent();
diff --git a/spec/frontend/ide/components/repo_commit_section_spec.js b/spec/frontend/ide/components/repo_commit_section_spec.js
index d3312358402..92bb645b1c0 100644
--- a/spec/frontend/ide/components/repo_commit_section_spec.js
+++ b/spec/frontend/ide/components/repo_commit_section_spec.js
@@ -63,11 +63,6 @@ describe('RepoCommitSection', () => {
jest.spyOn(router, 'push').mockImplementation();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('empty state', () => {
beforeEach(() => {
store.state.noChangesStateSvgPath = TEST_NO_CHANGES_SVG;
diff --git a/spec/frontend/ide/components/repo_editor_spec.js b/spec/frontend/ide/components/repo_editor_spec.js
index c9f033bffbb..9253bfc7e71 100644
--- a/spec/frontend/ide/components/repo_editor_spec.js
+++ b/spec/frontend/ide/components/repo_editor_spec.js
@@ -162,8 +162,6 @@ describe('RepoEditor', () => {
// create a new model each time, otherwise tests conflict with each other
// because of same model being used in multiple tests
monacoEditor.getModels().forEach((model) => model.dispose());
- wrapper.destroy();
- wrapper = null;
});
describe('default', () => {
diff --git a/spec/frontend/ide/components/repo_tab_spec.js b/spec/frontend/ide/components/repo_tab_spec.js
index b26edc5a85b..b329baea783 100644
--- a/spec/frontend/ide/components/repo_tab_spec.js
+++ b/spec/frontend/ide/components/repo_tab_spec.js
@@ -37,11 +37,6 @@ describe('RepoTab', () => {
jest.spyOn(router, 'push').mockImplementation(() => {});
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders a close link and a name link', () => {
createComponent({
tab: file(),
diff --git a/spec/frontend/ide/components/repo_tabs_spec.js b/spec/frontend/ide/components/repo_tabs_spec.js
index 1cfc1f12745..06ad162d398 100644
--- a/spec/frontend/ide/components/repo_tabs_spec.js
+++ b/spec/frontend/ide/components/repo_tabs_spec.js
@@ -25,10 +25,6 @@ describe('RepoTabs', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a list of tabs', async () => {
store.state.openFiles[0].active = true;
diff --git a/spec/frontend/ide/components/resizable_panel_spec.js b/spec/frontend/ide/components/resizable_panel_spec.js
index fe2a128c9c8..240e675a38e 100644
--- a/spec/frontend/ide/components/resizable_panel_spec.js
+++ b/spec/frontend/ide/components/resizable_panel_spec.js
@@ -19,11 +19,6 @@ describe('~/ide/components/resizable_panel', () => {
jest.spyOn(store, 'dispatch').mockImplementation();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const createComponent = (props = {}) => {
wrapper = shallowMount(ResizablePanel, {
propsData: {
diff --git a/spec/frontend/ide/components/shared/commit_message_field_spec.js b/spec/frontend/ide/components/shared/commit_message_field_spec.js
index 94da06f4cb2..186b1997497 100644
--- a/spec/frontend/ide/components/shared/commit_message_field_spec.js
+++ b/spec/frontend/ide/components/shared/commit_message_field_spec.js
@@ -23,10 +23,6 @@ describe('CommitMessageField', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findTextArea = () => wrapper.find('textarea');
const findHighlights = () => wrapper.findByTestId('highlights');
const findHighlightsText = () => wrapper.findByTestId('highlights-text');
diff --git a/spec/frontend/ide/components/shared/tokened_input_spec.js b/spec/frontend/ide/components/shared/tokened_input_spec.js
index b70c9659e46..4bd5a6527e2 100644
--- a/spec/frontend/ide/components/shared/tokened_input_spec.js
+++ b/spec/frontend/ide/components/shared/tokened_input_spec.js
@@ -28,10 +28,6 @@ describe('IDE shared/TokenedInput', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders tokens', () => {
createComponent();
const renderedTokens = getTokenElements(wrapper).wrappers.map((w) => w.text());
diff --git a/spec/frontend/ide/components/terminal/empty_state_spec.js b/spec/frontend/ide/components/terminal/empty_state_spec.js
index 15fb0fe9013..3a691c151d5 100644
--- a/spec/frontend/ide/components/terminal/empty_state_spec.js
+++ b/spec/frontend/ide/components/terminal/empty_state_spec.js
@@ -16,10 +16,6 @@ describe('IDE TerminalEmptyState', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('does not show illustration, if no path specified', () => {
factory();
diff --git a/spec/frontend/ide/components/terminal/terminal_spec.js b/spec/frontend/ide/components/terminal/terminal_spec.js
index 0d22f7f73fe..0500c116d23 100644
--- a/spec/frontend/ide/components/terminal/terminal_spec.js
+++ b/spec/frontend/ide/components/terminal/terminal_spec.js
@@ -59,10 +59,6 @@ describe('IDE Terminal', () => {
};
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('loading text', () => {
[STARTING, PENDING].forEach((status) => {
it(`shows when starting (${status})`, () => {
diff --git a/spec/frontend/ide/components/terminal/view_spec.js b/spec/frontend/ide/components/terminal/view_spec.js
index 57c8da9f5b7..b8ffaa89047 100644
--- a/spec/frontend/ide/components/terminal/view_spec.js
+++ b/spec/frontend/ide/components/terminal/view_spec.js
@@ -59,10 +59,6 @@ describe('IDE TerminalView', () => {
};
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders empty state', async () => {
await factory();
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 5b1502cc190..e420e28c7b6 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
@@ -22,10 +22,6 @@ describe('ide/components/terminal_sync/terminal_sync_status_safe', () => {
beforeEach(createComponent);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with terminal sync module in store', () => {
beforeEach(() => {
store.registerModule('terminalSync', {
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 147235abc8e..4541c3b5ec8 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
@@ -48,10 +48,6 @@ describe('ide/components/terminal_sync/terminal_sync_status', () => {
};
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when doing nothing', () => {
it('shows nothing', () => {
createComponent();
diff --git a/spec/frontend/ide/services/index_spec.js b/spec/frontend/ide/services/index_spec.js
index 623dee387e5..cd099e60070 100644
--- a/spec/frontend/ide/services/index_spec.js
+++ b/spec/frontend/ide/services/index_spec.js
@@ -252,12 +252,10 @@ describe('IDE services', () => {
describe('pingUsage', () => {
let mock;
- let relativeUrlRoot;
const TEST_RELATIVE_URL_ROOT = 'blah-blah';
beforeEach(() => {
jest.spyOn(axios, 'post');
- relativeUrlRoot = gon.relative_url_root;
gon.relative_url_root = TEST_RELATIVE_URL_ROOT;
mock = new MockAdapter(axios);
@@ -265,7 +263,6 @@ describe('IDE services', () => {
afterEach(() => {
mock.restore();
- gon.relative_url_root = relativeUrlRoot;
});
it('posts to usage endpoint', () => {
diff --git a/spec/frontend/ide/services/terminals_spec.js b/spec/frontend/ide/services/terminals_spec.js
index 5f752197e13..5b6b60a250c 100644
--- a/spec/frontend/ide/services/terminals_spec.js
+++ b/spec/frontend/ide/services/terminals_spec.js
@@ -9,7 +9,6 @@ const TEST_BRANCH = 'ref';
describe('~/ide/services/terminals', () => {
let axiosSpy;
let mock;
- const prevRelativeUrlRoot = gon.relative_url_root;
beforeEach(() => {
axiosSpy = jest.fn().mockReturnValue([HTTP_STATUS_OK, {}]);
@@ -19,7 +18,6 @@ describe('~/ide/services/terminals', () => {
});
afterEach(() => {
- gon.relative_url_root = prevRelativeUrlRoot;
mock.restore();
});
diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js
index 90ca8526698..7f4e1cf761d 100644
--- a/spec/frontend/ide/stores/actions/file_spec.js
+++ b/spec/frontend/ide/stores/actions/file_spec.js
@@ -16,7 +16,6 @@ const RELATIVE_URL_ROOT = '/gitlab';
describe('IDE store file actions', () => {
let mock;
- let originalGon;
let store;
let router;
@@ -24,9 +23,7 @@ describe('IDE store file actions', () => {
stubPerformanceWebAPI();
mock = new MockAdapter(axios);
- originalGon = window.gon;
window.gon = {
- ...window.gon,
relative_url_root: RELATIVE_URL_ROOT,
};
@@ -44,7 +41,6 @@ describe('IDE store file actions', () => {
afterEach(() => {
mock.restore();
- window.gon = originalGon;
});
describe('closeFile', () => {
diff --git a/spec/frontend/ide/stores/actions/merge_request_spec.js b/spec/frontend/ide/stores/actions/merge_request_spec.js
index fbae84631ee..a41ffdb0a31 100644
--- a/spec/frontend/ide/stores/actions/merge_request_spec.js
+++ b/spec/frontend/ide/stores/actions/merge_request_spec.js
@@ -3,7 +3,7 @@ import { range } from 'lodash';
import { stubPerformanceWebAPI } from 'helpers/performance';
import { TEST_HOST } from 'helpers/test_constants';
import testAction from 'helpers/vuex_action_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { leftSidebarViews, PERMISSION_READ_MR, MAX_MR_FILES_AUTO_OPEN } from '~/ide/constants';
import service from '~/ide/services';
import { createStore } from '~/ide/stores';
@@ -30,7 +30,7 @@ const createMergeRequestChangesCount = (n) =>
const testGetUrlForPath = (path) => `${TEST_HOST}/test/${path}`;
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('IDE store merge request actions', () => {
let store;
@@ -135,7 +135,7 @@ describe('IDE store merge request actions', () => {
mock.onGet(/api\/(.*)\/projects\/abcproject\/merge_requests/).networkError();
});
- it('flashes message, if error', () => {
+ it('shows an alert, if error', () => {
return store
.dispatch('getMergeRequestsForBranch', {
projectId: TEST_PROJECT,
@@ -519,7 +519,7 @@ describe('IDE store merge request actions', () => {
);
});
- it('flashes message, if error', () => {
+ it('shows an alert, if error', () => {
store.dispatch.mockRejectedValue();
return openMergeRequest(store, mr).catch(() => {
diff --git a/spec/frontend/ide/stores/actions/project_spec.js b/spec/frontend/ide/stores/actions/project_spec.js
index 5a5ead4c544..b13228c20f5 100644
--- a/spec/frontend/ide/stores/actions/project_spec.js
+++ b/spec/frontend/ide/stores/actions/project_spec.js
@@ -2,7 +2,7 @@ import MockAdapter from 'axios-mock-adapter';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import testAction from 'helpers/vuex_action_helper';
import api from '~/api';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import service from '~/ide/services';
import { createStore } from '~/ide/stores';
import {
@@ -19,7 +19,7 @@ import {
import { logError } from '~/lib/logger';
import axios from '~/lib/utils/axios_utils';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/logger');
const TEST_PROJECT_ID = 'abc/def';
@@ -104,7 +104,7 @@ describe('IDE store project actions', () => {
desc | projectPath | responseSuccess | expectedMutations
${'does not fetch permissions if project does not exist'} | ${undefined} | ${true} | ${[]}
${'fetches permission when project is specified'} | ${TEST_PROJECT_ID} | ${true} | ${[...permissionsMutations]}
- ${'flashes an error if the request fails'} | ${TEST_PROJECT_ID} | ${false} | ${[]}
+ ${'alerts an error if the request fails'} | ${TEST_PROJECT_ID} | ${false} | ${[]}
`('$desc', async ({ projectPath, expectedMutations, responseSuccess } = {}) => {
store.state.currentProjectId = projectPath;
if (responseSuccess) {
diff --git a/spec/frontend/ide/stores/actions_spec.js b/spec/frontend/ide/stores/actions_spec.js
index 1c90c0f943a..63b63af667c 100644
--- a/spec/frontend/ide/stores/actions_spec.js
+++ b/spec/frontend/ide/stores/actions_spec.js
@@ -4,7 +4,7 @@ import testAction from 'helpers/vuex_action_helper';
import eventHub from '~/ide/eventhub';
import { createRouter } from '~/ide/ide_router';
import { createStore } from '~/ide/stores';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import {
init,
stageAllChanges,
@@ -31,7 +31,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn(),
joinPaths: jest.requireActual('~/lib/utils/url_utility').joinPaths,
}));
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Multi-file store actions', () => {
let store;
@@ -210,7 +210,7 @@ describe('Multi-file store actions', () => {
expect(store.dispatch).toHaveBeenCalledWith('setFileActive', 'test');
});
- it('creates flash message if file already exists', async () => {
+ it('creates alert message if file already exists', async () => {
const f = file('test', '1', 'blob');
store.state.trees['abcproject/mybranch'].tree = [f];
store.state.entries[f.path] = f;
@@ -927,7 +927,7 @@ describe('Multi-file store actions', () => {
expect(document.querySelector('.flash-alert')).toBeNull();
});
- it('does not pass the error further and flashes an alert if error is not 404', async () => {
+ it('does not pass the error further and creates an alert if error is not 404', async () => {
mock.onGet(/(.*)/).replyOnce(HTTP_STATUS_IM_A_TEAPOT);
await expect(getBranchData(...callParams)).rejects.toEqual(
diff --git a/spec/frontend/ide/stores/extend_spec.js b/spec/frontend/ide/stores/extend_spec.js
index ffb00f9ef5b..88909999c82 100644
--- a/spec/frontend/ide/stores/extend_spec.js
+++ b/spec/frontend/ide/stores/extend_spec.js
@@ -6,12 +6,10 @@ jest.mock('~/ide/stores/plugins/terminal', () => jest.fn());
jest.mock('~/ide/stores/plugins/terminal_sync', () => jest.fn());
describe('ide/stores/extend', () => {
- let prevGon;
let store;
let el;
beforeEach(() => {
- prevGon = global.gon;
store = {};
el = {};
@@ -23,13 +21,12 @@ describe('ide/stores/extend', () => {
});
afterEach(() => {
- global.gon = prevGon;
terminalPlugin.mockClear();
terminalSyncPlugin.mockClear();
});
const withGonFeatures = (features) => {
- global.gon = { ...global.gon, features };
+ global.gon.features = features;
};
describe('terminalPlugin', () => {
diff --git a/spec/frontend/ide/stores/getters_spec.js b/spec/frontend/ide/stores/getters_spec.js
index d4166a3bd6d..0fe6a16c676 100644
--- a/spec/frontend/ide/stores/getters_spec.js
+++ b/spec/frontend/ide/stores/getters_spec.js
@@ -24,11 +24,8 @@ const TEST_FORK_PATH = '/test/fork/path';
describe('IDE store getters', () => {
let localState;
let localStore;
- let origGon;
beforeEach(() => {
- origGon = window.gon;
-
// Feature flag is defaulted to on in prod
window.gon = { features: { rejectUnsignedCommitsByGitlab: true } };
@@ -36,10 +33,6 @@ describe('IDE store getters', () => {
localState = localStore.state;
});
- afterEach(() => {
- window.gon = origGon;
- });
-
describe('activeFile', () => {
it('returns the current active file', () => {
localState.openFiles.push(file());
diff --git a/spec/frontend/ide/stores/modules/commit/actions_spec.js b/spec/frontend/ide/stores/modules/commit/actions_spec.js
index 4068a9d0919..872aa9b6e6b 100644
--- a/spec/frontend/ide/stores/modules/commit/actions_spec.js
+++ b/spec/frontend/ide/stores/modules/commit/actions_spec.js
@@ -1,6 +1,7 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { file } from 'jest/ide/helpers';
import { commitActionTypes, PERMISSION_CREATE_MR } from '~/ide/constants';
import eventHub from '~/ide/eventhub';
@@ -39,12 +40,14 @@ describe('IDE commit module actions', () => {
let mock;
let store;
let router;
+ let trackingSpy;
beforeEach(() => {
store = createStore();
router = createRouter(store);
gon.api_version = 'v1';
mock = new MockAdapter(axios);
+ trackingSpy = mockTracking(undefined, undefined, jest.spyOn);
jest.spyOn(router, 'push').mockImplementation();
mock
@@ -53,7 +56,7 @@ describe('IDE commit module actions', () => {
});
afterEach(() => {
- delete gon.api_version;
+ unmockTracking();
mock.restore();
});
@@ -81,19 +84,12 @@ describe('IDE commit module actions', () => {
});
describe('updateBranchName', () => {
- let originalGon;
-
beforeEach(() => {
- originalGon = window.gon;
- window.gon = { current_username: 'johndoe' };
+ window.gon.current_username = 'johndoe';
store.state.currentBranchId = 'main';
});
- afterEach(() => {
- window.gon = originalGon;
- });
-
it('updates store with new branch name', async () => {
await store.dispatch('commit/updateBranchName', 'branch-name');
@@ -430,6 +426,28 @@ describe('IDE commit module actions', () => {
});
});
});
+
+ describe('learnGitlabSource', () => {
+ describe('learnGitlabSource is true', () => {
+ it('tracks commit', async () => {
+ store.state.learnGitlabSource = true;
+
+ await store.dispatch('commit/commitChanges');
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'commit', {
+ label: 'web_ide_learn_gitlab_source',
+ });
+ });
+ });
+
+ describe('learnGitlabSource is false', () => {
+ it('does not track commit', async () => {
+ await store.dispatch('commit/commitChanges');
+
+ expect(trackingSpy).not.toHaveBeenCalled();
+ });
+ });
+ });
});
describe('success response with failed message', () => {
@@ -447,6 +465,26 @@ describe('IDE commit module actions', () => {
expect(alert.textContent.trim()).toBe('failed message');
});
+
+ describe('learnGitlabSource', () => {
+ describe('learnGitlabSource is true', () => {
+ it('does not track commit', async () => {
+ store.state.learnGitlabSource = true;
+
+ await store.dispatch('commit/commitChanges');
+
+ expect(trackingSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('learnGitlabSource is false', () => {
+ it('does not track commit', async () => {
+ await store.dispatch('commit/commitChanges');
+
+ expect(trackingSpy).not.toHaveBeenCalled();
+ });
+ });
+ });
});
describe('failed response', () => {
@@ -466,6 +504,26 @@ describe('IDE commit module actions', () => {
['commit/SET_ERROR', createUnexpectedCommitError(), undefined],
]);
});
+
+ describe('learnGitlabSource', () => {
+ describe('learnGitlabSource is true', () => {
+ it('does not track commit', async () => {
+ store.state.learnGitlabSource = true;
+
+ await store.dispatch('commit/commitChanges').catch(() => {});
+
+ expect(trackingSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('learnGitlabSource is false', () => {
+ it('does not track commit', async () => {
+ await store.dispatch('commit/commitChanges').catch(() => {});
+
+ expect(trackingSpy).not.toHaveBeenCalled();
+ });
+ });
+ });
});
describe('first commit of a branch', () => {
diff --git a/spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js b/spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js
index 0287e5269ee..3f7ded5e718 100644
--- a/spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js
+++ b/spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import * as actions from '~/ide/stores/modules/terminal/actions/session_controls';
import { STARTING, PENDING, STOPPING, STOPPED } from '~/ide/stores/modules/terminal/constants';
import * as messages from '~/ide/stores/modules/terminal/messages';
@@ -13,7 +13,7 @@ import {
HTTP_STATUS_UNPROCESSABLE_ENTITY,
} from '~/lib/utils/http_status';
-jest.mock('~/flash');
+jest.mock('~/alert');
const TEST_PROJECT_PATH = 'lorem/root';
const TEST_BRANCH_ID = 'main';
@@ -91,7 +91,7 @@ describe('IDE store terminal session controls actions', () => {
});
describe('receiveStartSessionError', () => {
- it('flashes message', () => {
+ it('shows an alert', () => {
actions.receiveStartSessionError({ dispatch });
expect(createAlert).toHaveBeenCalledWith({
@@ -165,7 +165,7 @@ describe('IDE store terminal session controls actions', () => {
});
describe('receiveStopSessionError', () => {
- it('flashes message', () => {
+ it('shows an alert', () => {
actions.receiveStopSessionError({ dispatch });
expect(createAlert).toHaveBeenCalledWith({
diff --git a/spec/frontend/ide/stores/modules/terminal/actions/session_status_spec.js b/spec/frontend/ide/stores/modules/terminal/actions/session_status_spec.js
index 9616733f052..30ae7d203a9 100644
--- a/spec/frontend/ide/stores/modules/terminal/actions/session_status_spec.js
+++ b/spec/frontend/ide/stores/modules/terminal/actions/session_status_spec.js
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import * as actions from '~/ide/stores/modules/terminal/actions/session_status';
import { PENDING, RUNNING, STOPPING, STOPPED } from '~/ide/stores/modules/terminal/constants';
import * as messages from '~/ide/stores/modules/terminal/messages';
@@ -8,7 +8,7 @@ import * as mutationTypes from '~/ide/stores/modules/terminal/mutation_types';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
-jest.mock('~/flash');
+jest.mock('~/alert');
const TEST_SESSION = {
id: 7,
@@ -113,7 +113,7 @@ describe('IDE store terminal session controls actions', () => {
});
describe('receiveSessionStatusError', () => {
- it('flashes message', () => {
+ it('shows an alert', () => {
actions.receiveSessionStatusError({ dispatch });
expect(createAlert).toHaveBeenCalledWith({
diff --git a/spec/frontend/import_entities/components/group_dropdown_spec.js b/spec/frontend/import_entities/components/group_dropdown_spec.js
index 31e097cfa7b..b44bc33de6f 100644
--- a/spec/frontend/import_entities/components/group_dropdown_spec.js
+++ b/spec/frontend/import_entities/components/group_dropdown_spec.js
@@ -64,10 +64,6 @@ describe('Import entities group dropdown component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('passes namespaces from graphql query to default slot', async () => {
createComponent();
jest.advanceTimersByTime(DEBOUNCE_DELAY);
diff --git a/spec/frontend/import_entities/components/import_status_spec.js b/spec/frontend/import_entities/components/import_status_spec.js
index 56c4ed827d7..3488d9f60c8 100644
--- a/spec/frontend/import_entities/components/import_status_spec.js
+++ b/spec/frontend/import_entities/components/import_status_spec.js
@@ -12,10 +12,6 @@ describe('Import entities status component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('success status', () => {
const getStatusText = () => wrapper.findComponent(GlBadge).text();
const getStatusIcon = () => wrapper.findComponent(GlBadge).props('icon');
diff --git a/spec/frontend/import_entities/import_groups/components/import_actions_cell_spec.js b/spec/frontend/import_entities/import_groups/components/import_actions_cell_spec.js
index 163a60bae36..1a52485f779 100644
--- a/spec/frontend/import_entities/import_groups/components/import_actions_cell_spec.js
+++ b/spec/frontend/import_entities/import_groups/components/import_actions_cell_spec.js
@@ -1,4 +1,4 @@
-import { GlButton, GlIcon, GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { GlDropdown, GlIcon, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import ImportActionsCell from '~/import_entities/import_groups/components/import_actions_cell.vue';
@@ -8,7 +8,6 @@ describe('import actions cell', () => {
const createComponent = (props) => {
wrapper = shallowMount(ImportActionsCell, {
propsData: {
- isProjectsImportEnabled: false,
isFinished: false,
isAvailableForImport: false,
isInvalid: false,
@@ -17,19 +16,15 @@ describe('import actions cell', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when group is available for import', () => {
beforeEach(() => {
createComponent({ isAvailableForImport: true });
});
- it('renders import button', () => {
- const button = wrapper.findComponent(GlButton);
- expect(button.exists()).toBe(true);
- expect(button.text()).toBe('Import');
+ it('renders import dropdown', () => {
+ const dropdown = wrapper.findComponent(GlDropdown);
+ expect(dropdown.exists()).toBe(true);
+ expect(dropdown.props('text')).toBe('Import with projects');
});
it('does not render icon with a hint', () => {
@@ -42,10 +37,10 @@ describe('import actions cell', () => {
createComponent({ isAvailableForImport: false, isFinished: true });
});
- it('renders re-import button', () => {
- const button = wrapper.findComponent(GlButton);
- expect(button.exists()).toBe(true);
- expect(button.text()).toBe('Re-import');
+ it('renders re-import dropdown', () => {
+ const dropdown = wrapper.findComponent(GlDropdown);
+ expect(dropdown.exists()).toBe(true);
+ expect(dropdown.props('text')).toBe('Re-import with projects');
});
it('renders icon with a hint', () => {
@@ -57,25 +52,25 @@ describe('import actions cell', () => {
});
});
- it('does not render import button when group is not available for import', () => {
+ it('does not render import dropdown when group is not available for import', () => {
createComponent({ isAvailableForImport: false });
- const button = wrapper.findComponent(GlButton);
- expect(button.exists()).toBe(false);
+ const dropdown = wrapper.findComponent(GlDropdown);
+ expect(dropdown.exists()).toBe(false);
});
- it('renders import button as disabled when group is invalid', () => {
+ it('renders import dropdown as disabled when group is invalid', () => {
createComponent({ isInvalid: true, isAvailableForImport: true });
- const button = wrapper.findComponent(GlButton);
- expect(button.props().disabled).toBe(true);
+ const dropdown = wrapper.findComponent(GlDropdown);
+ expect(dropdown.props().disabled).toBe(true);
});
it('emits import-group event when import button is clicked', () => {
createComponent({ isAvailableForImport: true });
- const button = wrapper.findComponent(GlButton);
- button.vm.$emit('click');
+ const dropdown = wrapper.findComponent(GlDropdown);
+ dropdown.vm.$emit('click');
expect(wrapper.emitted('import-group')).toHaveLength(1);
});
@@ -85,10 +80,10 @@ describe('import actions cell', () => {
${false} | ${'Import'}
${true} | ${'Re-import'}
`(
- 'when import projects is enabled, group is available for import and finish status is $status',
+ 'group is available for import and finish status is $isFinished',
({ isFinished, expectedAction }) => {
beforeEach(() => {
- createComponent({ isProjectsImportEnabled: true, isAvailableForImport: true, isFinished });
+ createComponent({ isAvailableForImport: true, isFinished });
});
it('render import dropdown', () => {
diff --git a/spec/frontend/import_entities/import_groups/components/import_source_cell_spec.js b/spec/frontend/import_entities/import_groups/components/import_source_cell_spec.js
index f2735d86493..9ead483d02f 100644
--- a/spec/frontend/import_entities/import_groups/components/import_source_cell_spec.js
+++ b/spec/frontend/import_entities/import_groups/components/import_source_cell_spec.js
@@ -22,10 +22,6 @@ describe('import source cell', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when group status is NONE', () => {
beforeEach(() => {
group = generateFakeTableEntry({ id: 1, status: STATUSES.NONE });
diff --git a/spec/frontend/import_entities/import_groups/components/import_table_spec.js b/spec/frontend/import_entities/import_groups/components/import_table_spec.js
index c7bda5a60ec..205218fdabd 100644
--- a/spec/frontend/import_entities/import_groups/components/import_table_spec.js
+++ b/spec/frontend/import_entities/import_groups/components/import_table_spec.js
@@ -6,7 +6,7 @@ import MockAdapter from 'axios-mock-adapter';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { HTTP_STATUS_OK, HTTP_STATUS_TOO_MANY_REQUESTS } from '~/lib/utils/http_status';
import axios from '~/lib/utils/axios_utils';
import { STATUSES } from '~/import_entities/constants';
@@ -23,7 +23,7 @@ import {
generateFakeEntry,
} from '../graphql/fixtures';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/import_entities/import_groups/services/status_poller');
Vue.use(VueApollo);
@@ -49,12 +49,12 @@ describe('import table', () => {
},
};
- const findImportSelectedButton = () =>
- wrapper.findAll('button').wrappers.find((w) => w.text() === 'Import selected');
const findImportSelectedDropdown = () =>
- wrapper.findAll('.gl-dropdown').wrappers.find((w) => w.text().includes('Import with projects'));
- const findImportButtons = () =>
- wrapper.findAll('button').wrappers.filter((w) => w.text() === 'Import');
+ wrapper.find('[data-testid="import-selected-groups-dropdown"]');
+ const findRowImportDropdownAtIndex = (idx) =>
+ wrapper.findAll('tbody td button').wrappers.filter((w) => w.text() === 'Import with projects')[
+ idx
+ ];
const findPaginationDropdown = () => wrapper.find('[data-testid="page-size"]');
const findTargetNamespaceDropdown = (rowWrapper) =>
rowWrapper.find('[data-testid="target-namespace-selector"]');
@@ -70,12 +70,7 @@ describe('import table', () => {
const findRowCheckbox = (idx) => wrapper.findAll('tbody td input[type=checkbox]').at(idx);
const selectRow = (idx) => findRowCheckbox(idx).setChecked(true);
- const createComponent = ({
- bulkImportSourceGroups,
- importGroups,
- defaultTargetNamespace,
- glFeatures = {},
- }) => {
+ const createComponent = ({ bulkImportSourceGroups, importGroups, defaultTargetNamespace }) => {
apolloProvider = createMockApollo(
[
[
@@ -102,10 +97,7 @@ describe('import table', () => {
defaultTargetNamespace,
},
directives: {
- GlTooltip: createMockDirective(),
- },
- provide: {
- glFeatures,
+ GlTooltip: createMockDirective('gl-tooltip'),
},
apolloProvider,
});
@@ -120,10 +112,6 @@ describe('import table', () => {
axiosMock.onGet(/.*\/exists$/, () => []).reply(HTTP_STATUS_OK, { exists: false });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('loading state', () => {
it('renders loading icon while performing request', async () => {
createComponent({
@@ -134,7 +122,7 @@ describe('import table', () => {
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
- it('does not renders loading icon when request is completed', async () => {
+ it('does not render loading icon when request is completed', async () => {
createComponent({
bulkImportSourceGroups: () => [],
});
@@ -245,12 +233,13 @@ describe('import table', () => {
await waitForPromises();
- await findImportButtons()[0].trigger('click');
+ await findRowImportDropdownAtIndex(0).trigger('click');
expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({
mutation: importGroupsMutation,
variables: {
importRequests: [
{
+ migrateProjects: true,
newName: FAKE_GROUP.lastImportTarget.newName,
sourceGroupId: FAKE_GROUP.id,
targetNamespace: AVAILABLE_NAMESPACES[0].fullPath,
@@ -273,7 +262,7 @@ describe('import table', () => {
});
await waitForPromises();
- await findImportButtons()[0].trigger('click');
+ await findRowImportDropdownAtIndex(0).trigger('click');
await waitForPromises();
expect(createAlert).toHaveBeenCalledWith(
@@ -298,7 +287,7 @@ describe('import table', () => {
});
await waitForPromises();
- await findImportButtons()[0].trigger('click');
+ await findRowImportDropdownAtIndex(0).trigger('click');
await waitForPromises();
expect(createAlert).not.toHaveBeenCalled();
@@ -476,7 +465,7 @@ describe('import table', () => {
});
await waitForPromises();
- expect(findImportSelectedButton().props().disabled).toBe(true);
+ expect(findImportSelectedDropdown().props().disabled).toBe(true);
});
it('import selected button is enabled when groups were selected for import', async () => {
@@ -491,7 +480,7 @@ describe('import table', () => {
await selectRow(0);
- expect(findImportSelectedButton().props().disabled).toBe(false);
+ expect(findImportSelectedDropdown().props().disabled).toBe(false);
});
it('does not allow selecting already started groups', async () => {
@@ -509,7 +498,7 @@ describe('import table', () => {
await selectRow(0);
await nextTick();
- expect(findImportSelectedButton().props().disabled).toBe(true);
+ expect(findImportSelectedDropdown().props().disabled).toBe(true);
});
it('does not allow selecting groups with validation errors', async () => {
@@ -534,10 +523,10 @@ describe('import table', () => {
await selectRow(0);
await nextTick();
- expect(findImportSelectedButton().props().disabled).toBe(true);
+ expect(findImportSelectedDropdown().props().disabled).toBe(true);
});
- it('invokes importGroups mutation when import selected button is clicked', async () => {
+ it('invokes importGroups mutation when import selected dropdown is clicked', async () => {
const NEW_GROUPS = [
generateFakeEntry({ id: 1, status: STATUSES.NONE }),
generateFakeEntry({ id: 2, status: STATUSES.NONE }),
@@ -558,7 +547,7 @@ describe('import table', () => {
await selectRow(1);
await nextTick();
- await findImportSelectedButton().trigger('click');
+ await findImportSelectedDropdown().find('button').trigger('click');
expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({
mutation: importGroupsMutation,
@@ -679,7 +668,7 @@ describe('import table', () => {
});
});
- describe('when import projects is enabled', () => {
+ describe('importing projects', () => {
const NEW_GROUPS = [
generateFakeEntry({ id: 1, status: STATUSES.NONE }),
generateFakeEntry({ id: 2, status: STATUSES.NONE }),
@@ -693,9 +682,6 @@ describe('import table', () => {
pageInfo: FAKE_PAGE_INFO,
versionValidation: FAKE_VERSION_VALIDATION,
}),
- glFeatures: {
- bulkImportProjects: true,
- },
});
jest.spyOn(apolloProvider.defaultClient, 'mutate');
return waitForPromises();
diff --git a/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js b/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js
index d5286e71c44..a524d9ebdb0 100644
--- a/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js
+++ b/spec/frontend/import_entities/import_groups/components/import_target_cell_spec.js
@@ -57,11 +57,6 @@ describe('import target cell', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('events', () => {
beforeEach(async () => {
group = generateFakeTableEntry({ id: 1, status: STATUSES.NONE });
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 ce111a0c10c..83566469176 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
@@ -16,7 +16,7 @@ import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { statusEndpointFixture } from './fixtures';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/import_entities/import_groups/graphql/services/local_storage_cache', () => ({
LocalStorageCache: jest.fn().mockImplementation(function mock() {
this.get = jest.fn();
diff --git a/spec/frontend/import_entities/import_groups/services/status_poller_spec.js b/spec/frontend/import_entities/import_groups/services/status_poller_spec.js
index 4a1b85d24e3..5ee2b2e698f 100644
--- a/spec/frontend/import_entities/import_groups/services/status_poller_spec.js
+++ b/spec/frontend/import_entities/import_groups/services/status_poller_spec.js
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import Visibility from 'visibilityjs';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { STATUSES } from '~/import_entities/constants';
import { StatusPoller } from '~/import_entities/import_groups/services/status_poller';
import axios from '~/lib/utils/axios_utils';
@@ -8,7 +8,7 @@ import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import Poll from '~/lib/utils/poll';
jest.mock('visibilityjs');
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/poll');
const FAKE_POLL_PATH = '/fake/poll/path';
@@ -81,7 +81,7 @@ describe('Bulk import status poller', () => {
expect(pollInstance.makeRequest).toHaveBeenCalled();
});
- it('when error occurs shows flash with error', () => {
+ it('when error occurs shows alert with error', () => {
const [[pollConfig]] = Poll.mock.calls;
pollConfig.errorCallback();
expect(createAlert).toHaveBeenCalled();
diff --git a/spec/frontend/import_entities/import_projects/components/advanced_settings_spec.js b/spec/frontend/import_entities/import_projects/components/advanced_settings_spec.js
index 68716600592..2294d236e8b 100644
--- a/spec/frontend/import_entities/import_projects/components/advanced_settings_spec.js
+++ b/spec/frontend/import_entities/import_projects/components/advanced_settings_spec.js
@@ -25,10 +25,6 @@ describe('Import Advanced Settings', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders GLFormCheckbox for each optional stage', () => {
expect(wrapper.findAllComponents(GlFormCheckbox)).toHaveLength(OPTIONAL_STAGES.length);
});
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 e613b9756af..8e73f76382a 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
@@ -60,11 +60,6 @@ describe('ProviderRepoTableRow', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when rendering importable project', () => {
const repo = {
importSource: {
diff --git a/spec/frontend/import_entities/import_projects/store/actions_spec.js b/spec/frontend/import_entities/import_projects/store/actions_spec.js
index 990587d4af7..f78016eefcf 100644
--- a/spec/frontend/import_entities/import_projects/store/actions_spec.js
+++ b/spec/frontend/import_entities/import_projects/store/actions_spec.js
@@ -1,7 +1,7 @@
import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
import testAction from 'helpers/vuex_action_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { STATUSES, PROVIDERS } from '~/import_entities/constants';
import actionsFactory from '~/import_entities/import_projects/store/actions';
import { getImportTarget } from '~/import_entities/import_projects/store/getters';
@@ -27,7 +27,7 @@ import {
HTTP_STATUS_TOO_MANY_REQUESTS,
} from '~/lib/utils/http_status';
-jest.mock('~/flash');
+jest.mock('~/alert');
const MOCK_ENDPOINT = `${TEST_HOST}/endpoint.json`;
const endpoints = {
diff --git a/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js b/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js
index 1d1b285c1b6..c5c29b4bb19 100644
--- a/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js
+++ b/spec/frontend/incidents_settings/components/incidents_settings_service_spec.js
@@ -1,12 +1,12 @@
import AxiosMockAdapter from 'axios-mock-adapter';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { ERROR_MSG } from '~/incidents_settings/constants';
import IncidentsSettingsService from '~/incidents_settings/incidents_settings_service';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility');
describe('IncidentsSettingsService', () => {
@@ -33,7 +33,7 @@ describe('IncidentsSettingsService', () => {
});
});
- it('should display a flash message on update error', () => {
+ it('should display an alert message on update error', () => {
mock.onPatch().reply(HTTP_STATUS_BAD_REQUEST);
return service.updateSettings({}).then(() => {
diff --git a/spec/frontend/incidents_settings/components/pagerduty_form_spec.js b/spec/frontend/incidents_settings/components/pagerduty_form_spec.js
index 521a861829b..77258db437d 100644
--- a/spec/frontend/incidents_settings/components/pagerduty_form_spec.js
+++ b/spec/frontend/incidents_settings/components/pagerduty_form_spec.js
@@ -26,10 +26,6 @@ describe('Alert integration settings form', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should match the default snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
diff --git a/spec/frontend/integrations/edit/components/active_checkbox_spec.js b/spec/frontend/integrations/edit/components/active_checkbox_spec.js
index 1f7a5f0dbc9..8afff842a85 100644
--- a/spec/frontend/integrations/edit/components/active_checkbox_spec.js
+++ b/spec/frontend/integrations/edit/components/active_checkbox_spec.js
@@ -17,10 +17,6 @@ describe('ActiveCheckbox', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGlFormCheckbox = () => wrapper.findComponent(GlFormCheckbox);
const findInputInCheckbox = () => findGlFormCheckbox().find('input');
diff --git a/spec/frontend/integrations/edit/components/confirmation_modal_spec.js b/spec/frontend/integrations/edit/components/confirmation_modal_spec.js
index cbe3402727a..dfb6b7d9a9c 100644
--- a/spec/frontend/integrations/edit/components/confirmation_modal_spec.js
+++ b/spec/frontend/integrations/edit/components/confirmation_modal_spec.js
@@ -14,10 +14,6 @@ describe('ConfirmationModal', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGlModal = () => wrapper.findComponent(GlModal);
describe('template', () => {
diff --git a/spec/frontend/integrations/edit/components/dynamic_field_spec.js b/spec/frontend/integrations/edit/components/dynamic_field_spec.js
index 7589b04b0fd..e1d9aef752f 100644
--- a/spec/frontend/integrations/edit/components/dynamic_field_spec.js
+++ b/spec/frontend/integrations/edit/components/dynamic_field_spec.js
@@ -21,10 +21,6 @@ describe('DynamicField', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGlFormGroup = () => wrapper.findComponent(GlFormGroup);
const findGlFormCheckbox = () => wrapper.findComponent(GlFormCheckbox);
const findGlFormInput = () => wrapper.findComponent(GlFormInput);
diff --git a/spec/frontend/integrations/edit/components/integration_form_spec.js b/spec/frontend/integrations/edit/components/integration_form_spec.js
index 383dfb36aa5..58fb456eb53 100644
--- a/spec/frontend/integrations/edit/components/integration_form_spec.js
+++ b/spec/frontend/integrations/edit/components/integration_form_spec.js
@@ -84,7 +84,6 @@ describe('IntegrationForm', () => {
});
afterEach(() => {
- wrapper.destroy();
mockAxios.restore();
});
@@ -507,30 +506,21 @@ describe('IntegrationForm', () => {
const dummyHelp = 'Foo Help';
it.each`
- integration | flagIsOn | helpHtml | sections | shouldShowSections | shouldShowHelp
- ${INTEGRATION_FORM_TYPE_SLACK} | ${false} | ${''} | ${[]} | ${false} | ${false}
- ${INTEGRATION_FORM_TYPE_SLACK} | ${false} | ${dummyHelp} | ${[]} | ${false} | ${true}
- ${INTEGRATION_FORM_TYPE_SLACK} | ${false} | ${undefined} | ${[mockSectionConnection]} | ${false} | ${false}
- ${INTEGRATION_FORM_TYPE_SLACK} | ${false} | ${dummyHelp} | ${[mockSectionConnection]} | ${false} | ${true}
- ${INTEGRATION_FORM_TYPE_SLACK} | ${true} | ${''} | ${[]} | ${false} | ${false}
- ${INTEGRATION_FORM_TYPE_SLACK} | ${true} | ${dummyHelp} | ${[]} | ${false} | ${true}
- ${INTEGRATION_FORM_TYPE_SLACK} | ${true} | ${undefined} | ${[mockSectionConnection]} | ${true} | ${false}
- ${INTEGRATION_FORM_TYPE_SLACK} | ${true} | ${dummyHelp} | ${[mockSectionConnection]} | ${true} | ${true}
- ${'foo'} | ${false} | ${''} | ${[]} | ${false} | ${false}
- ${'foo'} | ${false} | ${dummyHelp} | ${[]} | ${false} | ${true}
- ${'foo'} | ${false} | ${undefined} | ${[mockSectionConnection]} | ${true} | ${false}
- ${'foo'} | ${false} | ${dummyHelp} | ${[mockSectionConnection]} | ${true} | ${false}
- ${'foo'} | ${true} | ${''} | ${[]} | ${false} | ${false}
- ${'foo'} | ${true} | ${dummyHelp} | ${[]} | ${false} | ${true}
- ${'foo'} | ${true} | ${undefined} | ${[mockSectionConnection]} | ${true} | ${false}
- ${'foo'} | ${true} | ${dummyHelp} | ${[mockSectionConnection]} | ${true} | ${false}
+ integration | helpHtml | sections | shouldShowSections | shouldShowHelp
+ ${INTEGRATION_FORM_TYPE_SLACK} | ${''} | ${[]} | ${false} | ${false}
+ ${INTEGRATION_FORM_TYPE_SLACK} | ${dummyHelp} | ${[]} | ${false} | ${true}
+ ${INTEGRATION_FORM_TYPE_SLACK} | ${undefined} | ${[mockSectionConnection]} | ${true} | ${false}
+ ${INTEGRATION_FORM_TYPE_SLACK} | ${dummyHelp} | ${[mockSectionConnection]} | ${true} | ${true}
+ ${'foo'} | ${''} | ${[]} | ${false} | ${false}
+ ${'foo'} | ${dummyHelp} | ${[]} | ${false} | ${true}
+ ${'foo'} | ${undefined} | ${[mockSectionConnection]} | ${true} | ${false}
+ ${'foo'} | ${dummyHelp} | ${[mockSectionConnection]} | ${true} | ${false}
`(
- '$sections sections, and "$helpHtml" helpHtml when the FF is "$flagIsOn" for "$integration" integration',
- ({ integration, flagIsOn, helpHtml, sections, shouldShowSections, shouldShowHelp }) => {
+ '$sections sections, and "$helpHtml" helpHtml for "$integration" integration',
+ ({ integration, helpHtml, sections, shouldShowSections, shouldShowHelp }) => {
createComponent({
provide: {
helpHtml,
- glFeatures: { integrationSlackAppNotifications: flagIsOn },
},
customStateProps: {
sections,
@@ -553,20 +543,15 @@ describe('IntegrationForm', () => {
${false} | ${true} | ${'When having only the fields without a section'}
`('$description', ({ hasSections, hasFieldsWithoutSections }) => {
it.each`
- prefix | integration | shouldUpgradeSlack | flagIsOn | shouldShowAlert
- ${'does'} | ${INTEGRATION_FORM_TYPE_SLACK} | ${true} | ${true} | ${true}
- ${'does not'} | ${INTEGRATION_FORM_TYPE_SLACK} | ${false} | ${true} | ${false}
- ${'does not'} | ${INTEGRATION_FORM_TYPE_SLACK} | ${true} | ${false} | ${false}
- ${'does not'} | ${'foo'} | ${true} | ${true} | ${false}
- ${'does not'} | ${'foo'} | ${false} | ${true} | ${false}
- ${'does not'} | ${'foo'} | ${true} | ${false} | ${false}
+ prefix | integration | shouldUpgradeSlack | shouldShowAlert
+ ${'does'} | ${INTEGRATION_FORM_TYPE_SLACK} | ${true} | ${true}
+ ${'does not'} | ${INTEGRATION_FORM_TYPE_SLACK} | ${false} | ${false}
+ ${'does not'} | ${'foo'} | ${true} | ${false}
+ ${'does not'} | ${'foo'} | ${false} | ${false}
`(
- '$prefix render the upgrade warning when we are in "$integration" integration with the flag "$flagIsOn" and Slack-needs-upgrade is "$shouldUpgradeSlack" and have sections',
- ({ integration, shouldUpgradeSlack, flagIsOn, shouldShowAlert }) => {
+ '$prefix render the upgrade warning when we are in "$integration" integration with Slack-needs-upgrade is "$shouldUpgradeSlack" and have sections',
+ ({ integration, shouldUpgradeSlack, shouldShowAlert }) => {
createComponent({
- provide: {
- glFeatures: { integrationSlackAppNotifications: flagIsOn },
- },
customStateProps: {
shouldUpgradeSlack,
type: integration,
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 fa91f8de45a..90ee69ef2dc 100644
--- a/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js
+++ b/spec/frontend/integrations/edit/components/jira_issues_fields_spec.js
@@ -31,10 +31,6 @@ describe('JiraIssuesFields', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findEnableCheckbox = () => wrapper.findComponent(GlFormCheckbox);
const findEnableCheckboxDisabled = () =>
findEnableCheckbox().find('[type=checkbox]').attributes('disabled');
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 6011b3e6edc..f876a497f98 100644
--- a/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js
+++ b/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js
@@ -22,10 +22,6 @@ describe('JiraTriggerFields', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findCommentSettings = () => wrapper.findByTestId('comment-settings');
const findCommentDetail = () => wrapper.findByTestId('comment-detail');
const findCommentSettingsCheckbox = () => findCommentSettings().findComponent(GlFormCheckbox);
diff --git a/spec/frontend/integrations/edit/components/override_dropdown_spec.js b/spec/frontend/integrations/edit/components/override_dropdown_spec.js
index 90facaff1f9..2d1a6b3ace1 100644
--- a/spec/frontend/integrations/edit/components/override_dropdown_spec.js
+++ b/spec/frontend/integrations/edit/components/override_dropdown_spec.js
@@ -26,10 +26,6 @@ describe('OverrideDropdown', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGlLink = () => wrapper.findComponent(GlLink);
const findGlDropdown = () => wrapper.findComponent(GlDropdown);
diff --git a/spec/frontend/integrations/edit/components/sections/apple_app_store_spec.js b/spec/frontend/integrations/edit/components/sections/apple_app_store_spec.js
new file mode 100644
index 00000000000..62f0439a13f
--- /dev/null
+++ b/spec/frontend/integrations/edit/components/sections/apple_app_store_spec.js
@@ -0,0 +1,57 @@
+import { shallowMount } from '@vue/test-utils';
+
+import IntegrationSectionAppleAppStore from '~/integrations/edit/components/sections/apple_app_store.vue';
+import UploadDropzoneField from '~/integrations/edit/components/upload_dropzone_field.vue';
+import { createStore } from '~/integrations/edit/store';
+
+describe('IntegrationSectionAppleAppStore', () => {
+ let wrapper;
+
+ const createComponent = (componentFields) => {
+ const store = createStore({
+ customState: { ...componentFields },
+ });
+ wrapper = shallowMount(IntegrationSectionAppleAppStore, {
+ store,
+ });
+ };
+
+ const componentFields = (fileName = '') => {
+ return {
+ fields: [
+ {
+ name: 'app_store_private_key_file_name',
+ value: fileName,
+ },
+ ],
+ };
+ };
+
+ const findUploadDropzoneField = () => wrapper.findComponent(UploadDropzoneField);
+
+ describe('computed properties', () => {
+ it('renders UploadDropzoneField with default values', () => {
+ createComponent(componentFields());
+
+ const field = findUploadDropzoneField();
+
+ expect(field.exists()).toBe(true);
+ expect(field.props()).toMatchObject({
+ label: 'The Apple App Store Connect Private Key (.p8)',
+ helpText: '',
+ });
+ });
+
+ it('renders UploadDropzoneField with custom values for an attached file', () => {
+ createComponent(componentFields('fileName.txt'));
+
+ const field = findUploadDropzoneField();
+
+ expect(field.exists()).toBe(true);
+ expect(field.props()).toMatchObject({
+ label: 'Upload a new Apple App Store Connect Private Key (replace fileName.txt)',
+ helpText: 'Leave empty to use your current Private Key.',
+ });
+ });
+ });
+});
diff --git a/spec/frontend/integrations/edit/components/sections/configuration_spec.js b/spec/frontend/integrations/edit/components/sections/configuration_spec.js
index e697212ea0b..c8a7d17c041 100644
--- a/spec/frontend/integrations/edit/components/sections/configuration_spec.js
+++ b/spec/frontend/integrations/edit/components/sections/configuration_spec.js
@@ -19,10 +19,6 @@ describe('IntegrationSectionCoonfiguration', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findAllDynamicFields = () => wrapper.findAllComponents(DynamicField);
describe('template', () => {
diff --git a/spec/frontend/integrations/edit/components/sections/connection_spec.js b/spec/frontend/integrations/edit/components/sections/connection_spec.js
index 1eb92e80723..a24253d542d 100644
--- a/spec/frontend/integrations/edit/components/sections/connection_spec.js
+++ b/spec/frontend/integrations/edit/components/sections/connection_spec.js
@@ -20,10 +20,6 @@ describe('IntegrationSectionConnection', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findActiveCheckbox = () => wrapper.findComponent(ActiveCheckbox);
const findAllDynamicFields = () => wrapper.findAllComponents(DynamicField);
diff --git a/spec/frontend/integrations/edit/components/sections/google_play_spec.js b/spec/frontend/integrations/edit/components/sections/google_play_spec.js
new file mode 100644
index 00000000000..c0d6d17f639
--- /dev/null
+++ b/spec/frontend/integrations/edit/components/sections/google_play_spec.js
@@ -0,0 +1,54 @@
+import { shallowMount } from '@vue/test-utils';
+
+import IntegrationSectionGooglePlay from '~/integrations/edit/components/sections/google_play.vue';
+import UploadDropzoneField from '~/integrations/edit/components/upload_dropzone_field.vue';
+import { createStore } from '~/integrations/edit/store';
+
+describe('IntegrationSectionGooglePlay', () => {
+ let wrapper;
+
+ const createComponent = (fileName = '') => {
+ const store = createStore({
+ customState: {
+ fields: [
+ {
+ name: 'service_account_key_file_name',
+ value: fileName,
+ },
+ ],
+ },
+ });
+
+ wrapper = shallowMount(IntegrationSectionGooglePlay, {
+ store,
+ });
+ };
+
+ const findUploadDropzoneField = () => wrapper.findComponent(UploadDropzoneField);
+
+ describe('computed properties', () => {
+ it('renders UploadDropzoneField with default values', () => {
+ createComponent();
+
+ const field = findUploadDropzoneField();
+
+ expect(field.exists()).toBe(true);
+ expect(field.props()).toMatchObject({
+ label: 'Service account key (.json)',
+ helpText: '',
+ });
+ });
+
+ it('renders UploadDropzoneField with custom values for an attached file', () => {
+ createComponent('fileName.txt');
+
+ const field = findUploadDropzoneField();
+
+ expect(field.exists()).toBe(true);
+ expect(field.props()).toMatchObject({
+ label: 'Upload a new service account key (replace fileName.txt)',
+ helpText: 'Leave empty to use your current service account key.',
+ });
+ });
+ });
+});
diff --git a/spec/frontend/integrations/edit/components/sections/jira_issues_spec.js b/spec/frontend/integrations/edit/components/sections/jira_issues_spec.js
index a7c1cc2a03f..8b39fa8f583 100644
--- a/spec/frontend/integrations/edit/components/sections/jira_issues_spec.js
+++ b/spec/frontend/integrations/edit/components/sections/jira_issues_spec.js
@@ -18,10 +18,6 @@ describe('IntegrationSectionJiraIssue', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findJiraIssuesFields = () => wrapper.findComponent(JiraIssuesFields);
describe('template', () => {
diff --git a/spec/frontend/integrations/edit/components/sections/jira_trigger_spec.js b/spec/frontend/integrations/edit/components/sections/jira_trigger_spec.js
index d4ab9864fab..b3b7f508e25 100644
--- a/spec/frontend/integrations/edit/components/sections/jira_trigger_spec.js
+++ b/spec/frontend/integrations/edit/components/sections/jira_trigger_spec.js
@@ -18,10 +18,6 @@ describe('IntegrationSectionJiraTrigger', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findJiraTriggerFields = () => wrapper.findComponent(JiraTriggerFields);
describe('template', () => {
diff --git a/spec/frontend/integrations/edit/components/sections/trigger_spec.js b/spec/frontend/integrations/edit/components/sections/trigger_spec.js
index 883f5c7bf79..b9c1efbb0a2 100644
--- a/spec/frontend/integrations/edit/components/sections/trigger_spec.js
+++ b/spec/frontend/integrations/edit/components/sections/trigger_spec.js
@@ -18,10 +18,6 @@ describe('IntegrationSectionTrigger', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findAllTriggerFields = () => wrapper.findAllComponents(TriggerField);
describe('template', () => {
diff --git a/spec/frontend/integrations/edit/components/trigger_field_spec.js b/spec/frontend/integrations/edit/components/trigger_field_spec.js
index ed0b3324708..3b736b33a2f 100644
--- a/spec/frontend/integrations/edit/components/trigger_field_spec.js
+++ b/spec/frontend/integrations/edit/components/trigger_field_spec.js
@@ -23,10 +23,6 @@ describe('TriggerField', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGlFormCheckbox = () => wrapper.findComponent(GlFormCheckbox);
const findGlFormInput = () => wrapper.findComponent(GlFormInput);
const findHiddenInput = () => wrapper.find('input[type="hidden"]');
diff --git a/spec/frontend/integrations/edit/components/trigger_fields_spec.js b/spec/frontend/integrations/edit/components/trigger_fields_spec.js
index 082eeea30f1..defa02aefd2 100644
--- a/spec/frontend/integrations/edit/components/trigger_fields_spec.js
+++ b/spec/frontend/integrations/edit/components/trigger_fields_spec.js
@@ -20,10 +20,6 @@ describe('TriggerFields', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findTriggerLabel = () => wrapper.findByTestId('trigger-fields-group').find('label');
const findAllGlFormGroups = () => wrapper.find('#trigger-fields').findAllComponents(GlFormGroup);
const findAllGlFormCheckboxes = () => wrapper.findAllComponents(GlFormCheckbox);
diff --git a/spec/frontend/integrations/edit/components/upload_dropzone_field_spec.js b/spec/frontend/integrations/edit/components/upload_dropzone_field_spec.js
new file mode 100644
index 00000000000..36e20db0022
--- /dev/null
+++ b/spec/frontend/integrations/edit/components/upload_dropzone_field_spec.js
@@ -0,0 +1,88 @@
+import { mount } from '@vue/test-utils';
+import { GlAlert } from '@gitlab/ui';
+import { nextTick } from 'vue';
+
+import UploadDropzoneField from '~/integrations/edit/components/upload_dropzone_field.vue';
+import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
+import { mockField } from '../mock_data';
+
+describe('UploadDropzoneField', () => {
+ let wrapper;
+
+ const contentsInputName = 'service[app_store_private_key]';
+ const fileNameInputName = 'service[app_store_private_key_file_name]';
+
+ const createComponent = (props) => {
+ wrapper = mount(UploadDropzoneField, {
+ propsData: {
+ ...mockField,
+ ...props,
+ name: contentsInputName,
+ label: 'Input Label',
+ fileInputName: fileNameInputName,
+ },
+ });
+ };
+
+ const findGlAlert = () => wrapper.findComponent(GlAlert);
+ const findUploadDropzone = () => wrapper.findComponent(UploadDropzone);
+ const findFileContentsHiddenInput = () => wrapper.find(`input[name="${contentsInputName}"]`);
+ const findFileNameHiddenInput = () => wrapper.find(`input[name="${fileNameInputName}"]`);
+
+ describe('template', () => {
+ it('adds the expected file inputFieldName', () => {
+ createComponent();
+
+ expect(findUploadDropzone().props('inputFieldName')).toBe('service[dropzone_file_name]');
+ });
+
+ it('adds a disabled, hidden text input for the file contents', () => {
+ createComponent();
+
+ expect(findFileContentsHiddenInput().attributes('name')).toBe(contentsInputName);
+ expect(findFileContentsHiddenInput().attributes('disabled')).toBeDefined();
+ });
+
+ it('adds a disabled, hidden text input for the file name', () => {
+ createComponent();
+
+ expect(findFileNameHiddenInput().attributes('name')).toBe(fileNameInputName);
+ expect(findFileNameHiddenInput().attributes('disabled')).toBeDefined();
+ });
+ });
+
+ describe('clearError', () => {
+ it('clears uploadError when called', async () => {
+ createComponent();
+
+ expect(findGlAlert().exists()).toBe(false);
+
+ findUploadDropzone().vm.$emit('error');
+ await nextTick();
+
+ expect(findGlAlert().exists()).toBe(true);
+ expect(findGlAlert().text()).toBe(
+ 'Error: You are trying to upload something other than an allowed file.',
+ );
+
+ findGlAlert().vm.$emit('dismiss');
+ await nextTick();
+
+ expect(findGlAlert().exists()).toBe(false);
+ });
+ });
+
+ describe('onError', () => {
+ it('assigns uploadError to the supplied custom message', async () => {
+ const message = 'test error message';
+ createComponent({ errorMessage: message });
+
+ findUploadDropzone().vm.$emit('error');
+
+ await nextTick();
+
+ expect(findGlAlert().exists()).toBe(true);
+ expect(findGlAlert().text()).toBe(message);
+ });
+ });
+});
diff --git a/spec/frontend/integrations/index/components/integrations_list_spec.js b/spec/frontend/integrations/index/components/integrations_list_spec.js
index ee54a5fd359..155a3d1c6be 100644
--- a/spec/frontend/integrations/index/components/integrations_list_spec.js
+++ b/spec/frontend/integrations/index/components/integrations_list_spec.js
@@ -13,10 +13,6 @@ describe('IntegrationsList', () => {
wrapper = shallowMountExtended(IntegrationsList, { propsData });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('provides correct `integrations` prop to the IntegrationsTable instance', () => {
createComponent({ integrations: [...mockInactiveIntegrations, ...mockActiveIntegrations] });
diff --git a/spec/frontend/integrations/index/components/integrations_table_spec.js b/spec/frontend/integrations/index/components/integrations_table_spec.js
index 976c7b74890..5456a23a98d 100644
--- a/spec/frontend/integrations/index/components/integrations_table_spec.js
+++ b/spec/frontend/integrations/index/components/integrations_table_spec.js
@@ -1,6 +1,5 @@
-import { GlTable, GlIcon, GlLink } from '@gitlab/ui';
+import { GlTable, GlIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import { INTEGRATION_TYPE_SLACK } from '~/integrations/constants';
import IntegrationsTable from '~/integrations/index/components/integrations_table.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
@@ -11,24 +10,15 @@ describe('IntegrationsTable', () => {
const findTable = () => wrapper.findComponent(GlTable);
- const createComponent = (propsData = {}, flagIsOn = false) => {
+ const createComponent = (propsData = {}) => {
wrapper = mount(IntegrationsTable, {
propsData: {
integrations: mockActiveIntegrations,
...propsData,
},
- provide: {
- glFeatures: {
- integrationSlackAppNotifications: flagIsOn,
- },
- },
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe.each([true, false])('when `showUpdatedAt` is %p', (showUpdatedAt) => {
beforeEach(() => {
createComponent({ showUpdatedAt });
@@ -56,51 +46,4 @@ describe('IntegrationsTable', () => {
expect(findTable().findComponent(GlIcon).exists()).toBe(shouldRenderActiveIcon);
});
});
-
- describe('integrations filtering', () => {
- const slackActive = {
- ...mockActiveIntegrations[0],
- name: INTEGRATION_TYPE_SLACK,
- title: 'Slack',
- };
- const slackInactive = {
- ...mockInactiveIntegrations[0],
- name: INTEGRATION_TYPE_SLACK,
- title: 'Slack',
- };
-
- describe.each`
- desc | flagIsOn | integrations | expectedIntegrations
- ${'only active'} | ${false} | ${mockActiveIntegrations} | ${mockActiveIntegrations}
- ${'only active'} | ${true} | ${mockActiveIntegrations} | ${mockActiveIntegrations}
- ${'only inactive'} | ${true} | ${mockInactiveIntegrations} | ${mockInactiveIntegrations}
- ${'only inactive'} | ${false} | ${mockInactiveIntegrations} | ${mockInactiveIntegrations}
- ${'active and inactive'} | ${true} | ${[...mockActiveIntegrations, ...mockInactiveIntegrations]} | ${[...mockActiveIntegrations, ...mockInactiveIntegrations]}
- ${'active and inactive'} | ${false} | ${[...mockActiveIntegrations, ...mockInactiveIntegrations]} | ${[...mockActiveIntegrations, ...mockInactiveIntegrations]}
- ${'Slack active with active'} | ${false} | ${[slackActive, ...mockActiveIntegrations]} | ${[slackActive, ...mockActiveIntegrations]}
- ${'Slack active with active'} | ${true} | ${[slackActive, ...mockActiveIntegrations]} | ${[slackActive, ...mockActiveIntegrations]}
- ${'Slack active with inactive'} | ${false} | ${[slackActive, ...mockInactiveIntegrations]} | ${[slackActive, ...mockInactiveIntegrations]}
- ${'Slack active with inactive'} | ${true} | ${[slackActive, ...mockInactiveIntegrations]} | ${[slackActive, ...mockInactiveIntegrations]}
- ${'Slack inactive with active'} | ${false} | ${[slackInactive, ...mockActiveIntegrations]} | ${[slackInactive, ...mockActiveIntegrations]}
- ${'Slack inactive with active'} | ${true} | ${[slackInactive, ...mockActiveIntegrations]} | ${mockActiveIntegrations}
- ${'Slack inactive with inactive'} | ${false} | ${[slackInactive, ...mockInactiveIntegrations]} | ${[slackInactive, ...mockInactiveIntegrations]}
- ${'Slack inactive with inactive'} | ${true} | ${[slackInactive, ...mockInactiveIntegrations]} | ${mockInactiveIntegrations}
- ${'Slack active with active and inactive'} | ${true} | ${[slackActive, ...mockActiveIntegrations, ...mockInactiveIntegrations]} | ${[slackActive, ...mockActiveIntegrations, ...mockInactiveIntegrations]}
- ${'Slack active with active and inactive'} | ${false} | ${[slackActive, ...mockActiveIntegrations, ...mockInactiveIntegrations]} | ${[slackActive, ...mockActiveIntegrations, ...mockInactiveIntegrations]}
- ${'Slack inactive with active and inactive'} | ${true} | ${[slackInactive, ...mockActiveIntegrations, ...mockInactiveIntegrations]} | ${[...mockActiveIntegrations, ...mockInactiveIntegrations]}
- ${'Slack inactive with active and inactive'} | ${false} | ${[slackInactive, ...mockActiveIntegrations, ...mockInactiveIntegrations]} | ${[slackInactive, ...mockActiveIntegrations, ...mockInactiveIntegrations]}
- `('when $desc and flag "$flagIsOn"', ({ flagIsOn, integrations, expectedIntegrations }) => {
- beforeEach(() => {
- createComponent({ integrations }, flagIsOn);
- });
-
- it('renders correctly', () => {
- const links = wrapper.findAllComponents(GlLink);
- expect(links).toHaveLength(expectedIntegrations.length);
- expectedIntegrations.forEach((integration, index) => {
- expect(links.at(index).text()).toBe(integration.title);
- });
- });
- });
- });
});
diff --git a/spec/frontend/integrations/overrides/components/integration_overrides_spec.js b/spec/frontend/integrations/overrides/components/integration_overrides_spec.js
index fdb728281b5..9e863eaecfd 100644
--- a/spec/frontend/integrations/overrides/components/integration_overrides_spec.js
+++ b/spec/frontend/integrations/overrides/components/integration_overrides_spec.js
@@ -47,7 +47,6 @@ describe('IntegrationOverrides', () => {
afterEach(() => {
mockAxios.restore();
- wrapper.destroy();
});
const findGlTable = () => wrapper.findComponent(GlTable);
diff --git a/spec/frontend/integrations/overrides/components/integration_tabs_spec.js b/spec/frontend/integrations/overrides/components/integration_tabs_spec.js
index a728b4d391f..b35a40d69c1 100644
--- a/spec/frontend/integrations/overrides/components/integration_tabs_spec.js
+++ b/spec/frontend/integrations/overrides/components/integration_tabs_spec.js
@@ -21,10 +21,6 @@ describe('IntegrationTabs', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGlBadge = () => wrapper.findComponent(GlBadge);
const findGlTab = () => wrapper.findComponent(GlTab);
const findSettingsLink = () => wrapper.find('a');
diff --git a/spec/frontend/invite_members/components/confetti_spec.js b/spec/frontend/invite_members/components/confetti_spec.js
index 2f361f1dc1e..382569abfd9 100644
--- a/spec/frontend/invite_members/components/confetti_spec.js
+++ b/spec/frontend/invite_members/components/confetti_spec.js
@@ -6,16 +6,10 @@ jest.mock('canvas-confetti', () => ({
create: jest.fn(),
}));
-let wrapper;
-
const createComponent = () => {
- wrapper = shallowMount(Confetti);
+ shallowMount(Confetti);
};
-afterEach(() => {
- wrapper.destroy();
-});
-
describe('Confetti', () => {
it('initiates confetti', () => {
const basicCannon = jest.spyOn(Confetti.methods, 'basicCannon').mockImplementation(() => {});
diff --git a/spec/frontend/invite_members/components/group_select_spec.js b/spec/frontend/invite_members/components/group_select_spec.js
index e1563a7bb3a..a1ca9a69926 100644
--- a/spec/frontend/invite_members/components/group_select_spec.js
+++ b/spec/frontend/invite_members/components/group_select_spec.js
@@ -26,14 +26,9 @@ describe('GroupSelect', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findSearchBoxByType = () => wrapper.findComponent(GlSearchBoxByType);
const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findDropdownToggle = () => findDropdown().find('button[aria-haspopup="true"]');
+ const findDropdownToggle = () => findDropdown().find('button[aria-haspopup="menu"]');
const findAvatarByLabel = (text) =>
wrapper
.findAllComponents(GlAvatarLabeled)
@@ -66,6 +61,7 @@ describe('GroupSelect', () => {
expect(groupsApi.getGroups).toHaveBeenCalledWith(group1.name, {
exclude_internal: true,
active: true,
+ order_by: 'similarity',
});
});
diff --git a/spec/frontend/invite_members/components/import_project_members_modal_spec.js b/spec/frontend/invite_members/components/import_project_members_modal_spec.js
index d839cde163c..74cb59a9b52 100644
--- a/spec/frontend/invite_members/components/import_project_members_modal_spec.js
+++ b/spec/frontend/invite_members/components/import_project_members_modal_spec.js
@@ -54,7 +54,6 @@ beforeEach(() => {
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
});
diff --git a/spec/frontend/invite_members/components/import_project_members_trigger_spec.js b/spec/frontend/invite_members/components/import_project_members_trigger_spec.js
index b6375fcfa22..0e8243491a8 100644
--- a/spec/frontend/invite_members/components/import_project_members_trigger_spec.js
+++ b/spec/frontend/invite_members/components/import_project_members_trigger_spec.js
@@ -17,10 +17,6 @@ const createComponent = (props = {}) => {
describe('ImportProjectMembersTrigger', () => {
let wrapper;
- afterEach(() => {
- wrapper.destroy();
- });
-
const findButton = () => wrapper.findComponent(GlButton);
describe('displayText', () => {
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 84ddb779a9e..e088dc41a2b 100644
--- a/spec/frontend/invite_members/components/invite_group_trigger_spec.js
+++ b/spec/frontend/invite_members/components/invite_group_trigger_spec.js
@@ -17,11 +17,6 @@ const createComponent = (props = {}) => {
describe('InviteGroupTrigger', () => {
let wrapper;
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findButton = () => wrapper.findComponent(GlButton);
describe('displayText', () => {
diff --git a/spec/frontend/invite_members/components/invite_groups_modal_spec.js b/spec/frontend/invite_members/components/invite_groups_modal_spec.js
index c2a55517405..82b4717fbf1 100644
--- a/spec/frontend/invite_members/components/invite_groups_modal_spec.js
+++ b/spec/frontend/invite_members/components/invite_groups_modal_spec.js
@@ -44,11 +44,6 @@ describe('InviteGroupsModal', () => {
createComponent({ isProject: false });
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findModal = () => wrapper.findComponent(GlModal);
const findGroupSelect = () => wrapper.findComponent(GroupSelect);
const findInviteGroupAlert = () => wrapper.findComponent(InviteGroupNotification);
@@ -58,11 +53,13 @@ describe('InviteGroupsModal', () => {
findMembersFormGroup().attributes('invalid-feedback');
const findBase = () => wrapper.findComponent(InviteModalBase);
const triggerGroupSelect = (val) => findGroupSelect().vm.$emit('input', val);
- const emitEventFromModal = (eventName) => () =>
- findModal().vm.$emit(eventName, { preventDefault: jest.fn() });
- const hideModal = emitEventFromModal('hidden');
- const clickInviteButton = emitEventFromModal('primary');
- const clickCancelButton = emitEventFromModal('cancel');
+ const hideModal = () => findModal().vm.$emit('hidden', { preventDefault: jest.fn() });
+
+ const emitClickFromModal = (testId) => () =>
+ wrapper.findByTestId(testId).vm.$emit('click', { preventDefault: jest.fn() });
+
+ const clickInviteButton = emitClickFromModal('invite-modal-submit');
+ const clickCancelButton = emitClickFromModal('invite-modal-cancel');
describe('displaying the correct introText and form group description', () => {
describe('when inviting to a project', () => {
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 9687d528321..39d5ddee723 100644
--- a/spec/frontend/invite_members/components/invite_members_modal_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_modal_spec.js
@@ -73,6 +73,7 @@ describe('InviteMembersModal', () => {
wrapper = shallowMountExtended(InviteMembersModal, {
provide: {
newProjectPath,
+ name: propsData.name,
},
propsData: {
usersLimitDataset: {},
@@ -116,8 +117,6 @@ describe('InviteMembersModal', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
mock.restore();
});
@@ -134,10 +133,15 @@ describe('InviteMembersModal', () => {
`${Object.keys(invitationsApiResponse.EXPANDED_RESTRICTED.message)[element]}: ${
Object.values(invitationsApiResponse.EXPANDED_RESTRICTED.message)[element]
}`;
- const emitEventFromModal = (eventName) => () =>
- findModal().vm.$emit(eventName, { preventDefault: jest.fn() });
- const clickInviteButton = emitEventFromModal('primary');
- const clickCancelButton = emitEventFromModal('cancel');
+ const findActionButton = () => wrapper.findByTestId('invite-modal-submit');
+ const findCancelButton = () => wrapper.findByTestId('invite-modal-cancel');
+
+ const emitClickFromModal = (findButton) => () =>
+ findButton().vm.$emit('click', { preventDefault: jest.fn() });
+
+ const clickInviteButton = emitClickFromModal(findActionButton);
+ const clickCancelButton = emitClickFromModal(findCancelButton);
+
const findMembersFormGroup = () => wrapper.findByTestId('members-form-group');
const membersFormGroupInvalidFeedback = () =>
findMembersFormGroup().attributes('invalid-feedback');
@@ -368,13 +372,11 @@ describe('InviteMembersModal', () => {
it('tracks actions', async () => {
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- const mockEvent = { preventDefault: jest.fn() };
-
await triggerOpenModal({ mode: 'celebrate', source: ON_CELEBRATION_TRACK_LABEL });
expectTracking('render', ON_CELEBRATION_TRACK_LABEL);
- findModal().vm.$emit('cancel', mockEvent);
+ clickCancelButton();
expectTracking('click_cancel', ON_CELEBRATION_TRACK_LABEL);
findModal().vm.$emit('close');
@@ -411,13 +413,11 @@ describe('InviteMembersModal', () => {
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- const mockEvent = { preventDefault: jest.fn() };
-
await triggerOpenModal(source);
expectTracking('render', label);
- findModal().vm.$emit('cancel', mockEvent);
+ clickCancelButton();
expectTracking('click_cancel', label);
findModal().vm.$emit('close');
@@ -734,7 +734,7 @@ describe('InviteMembersModal', () => {
expect(membersFormGroupInvalidFeedback()).toBe(expectedSyntaxError);
expect(findMembersSelect().props('exceptionState')).toBe(false);
- expect(findModal().props('actionPrimary').attributes.loading).toBe(false);
+ expect(findActionButton().props('loading')).toBe(false);
});
it('clears the error when the modal is hidden', async () => {
@@ -746,7 +746,7 @@ describe('InviteMembersModal', () => {
expect(membersFormGroupInvalidFeedback()).toBe(expectedSyntaxError);
expect(findMembersSelect().props('exceptionState')).toBe(false);
- expect(findModal().props('actionPrimary').attributes.loading).toBe(false);
+ expect(findActionButton().props('loading')).toBe(false);
findModal().vm.$emit('hidden');
@@ -768,7 +768,7 @@ describe('InviteMembersModal', () => {
expect(findMemberErrorAlert().text()).toContain(expectedEmailRestrictedError);
expect(membersFormGroupInvalidFeedback()).toBe('');
expect(findMembersSelect().props('exceptionState')).not.toBe(false);
- expect(findModal().props('actionPrimary').attributes.loading).toBe(false);
+ expect(findActionButton().props('loading')).toBe(false);
});
it('displays all errors when there are multiple emails that return a restricted error message', async () => {
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 c522abe63c5..cdb6182e2ae 100644
--- a/spec/frontend/invite_members/components/invite_members_trigger_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_trigger_spec.js
@@ -1,12 +1,14 @@
-import { GlButton, GlLink, GlIcon } from '@gitlab/ui';
+import { GlButton, GlLink, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue';
import eventHub from '~/invite_members/event_hub';
import {
TRIGGER_ELEMENT_BUTTON,
- TRIGGER_ELEMENT_SIDE_NAV,
TRIGGER_DEFAULT_QA_SELECTOR,
+ TRIGGER_ELEMENT_WITH_EMOJI,
+ TRIGGER_ELEMENT_DROPDOWN_WITH_EMOJI,
} from '~/invite_members/constants';
+import { GlEmoji } from '../mock_data/member_modal';
jest.mock('~/experimentation/experiment_tracking');
@@ -19,7 +21,8 @@ let findButton;
const triggerComponent = {
button: GlButton,
anchor: GlLink,
- 'side-nav': GlLink,
+ 'text-emoji': GlLink,
+ 'dropdown-text-emoji': GlDropdownItem,
};
const createComponent = (props = {}) => {
@@ -29,6 +32,9 @@ const createComponent = (props = {}) => {
...triggerProps,
...props,
},
+ stubs: {
+ GlEmoji,
+ },
});
};
@@ -40,8 +46,8 @@ const triggerItems = [
triggerElement: 'anchor',
},
{
- triggerElement: TRIGGER_ELEMENT_SIDE_NAV,
- icon: 'plus',
+ triggerElement: TRIGGER_ELEMENT_WITH_EMOJI,
+ icon: 'shaking_hands',
},
];
@@ -50,10 +56,6 @@ describe.each(triggerItems)('with triggerElement as %s', (triggerItem) => {
findButton = () => wrapper.findComponent(triggerComponent[triggerItem.triggerElement]);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('configurable attributes', () => {
it('includes the correct displayText for the button', () => {
createComponent();
@@ -91,31 +93,26 @@ describe.each(triggerItems)('with triggerElement as %s', (triggerItem) => {
});
});
});
+});
- describe('tracking', () => {
- it('does not add tracking attributes', () => {
- createComponent();
-
- expect(findButton().attributes('data-track-action')).toBeUndefined();
- expect(findButton().attributes('data-track-label')).toBeUndefined();
- });
+describe('link with emoji', () => {
+ it('includes the specified icon with correct size when triggerElement is link', () => {
+ const findEmoji = () => wrapper.findComponent(GlEmoji);
- it('adds tracking attributes', () => {
- createComponent({ label: '_label_', event: '_event_' });
+ createComponent({ triggerElement: TRIGGER_ELEMENT_WITH_EMOJI, icon: 'shaking_hands' });
- expect(findButton().attributes('data-track-action')).toBe('_event_');
- expect(findButton().attributes('data-track-label')).toBe('_label_');
- });
+ expect(findEmoji().exists()).toBe(true);
+ expect(findEmoji().attributes('data-name')).toBe('shaking_hands');
});
});
-describe('side-nav with icon', () => {
+describe('dropdown item with emoji', () => {
it('includes the specified icon with correct size when triggerElement is link', () => {
- const findIcon = () => wrapper.findComponent(GlIcon);
+ const findEmoji = () => wrapper.findComponent(GlEmoji);
- createComponent({ triggerElement: TRIGGER_ELEMENT_SIDE_NAV, icon: 'plus' });
+ createComponent({ triggerElement: TRIGGER_ELEMENT_DROPDOWN_WITH_EMOJI, icon: 'shaking_hands' });
- expect(findIcon().exists()).toBe(true);
- expect(findIcon().props('name')).toBe('plus');
+ expect(findEmoji().exists()).toBe(true);
+ expect(findEmoji().attributes('data-name')).toBe('shaking_hands');
});
});
diff --git a/spec/frontend/invite_members/components/invite_modal_base_spec.js b/spec/frontend/invite_members/components/invite_modal_base_spec.js
index f34f9902514..e70c83a424e 100644
--- a/spec/frontend/invite_members/components/invite_modal_base_spec.js
+++ b/spec/frontend/invite_members/components/invite_modal_base_spec.js
@@ -54,10 +54,6 @@ describe('InviteModalBase', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findFormSelect = () => wrapper.findComponent(GlFormSelect);
const findFormSelectOptions = () => findFormSelect().findAllComponents('option');
const findDatepicker = () => wrapper.findComponent(GlDatepicker);
@@ -66,8 +62,8 @@ describe('InviteModalBase', () => {
const findIntroText = () => wrapper.findByTestId('modal-base-intro-text').text();
const findMembersFormGroup = () => wrapper.findByTestId('members-form-group');
const findDisabledInput = () => wrapper.findByTestId('disabled-input');
- const findCancelButton = () => wrapper.find('.js-modal-action-cancel');
- const findActionButton = () => wrapper.find('.js-modal-action-primary');
+ const findCancelButton = () => wrapper.findByTestId('invite-modal-cancel');
+ const findActionButton = () => wrapper.findByTestId('invite-modal-submit');
describe('rendering the modal', () => {
let trackingSpy;
@@ -88,20 +84,19 @@ describe('InviteModalBase', () => {
});
it('renders the Cancel button text correctly', () => {
- expect(wrapper.findComponent(GlModal).props('actionCancel')).toMatchObject({
- text: CANCEL_BUTTON_TEXT,
- });
+ expect(findCancelButton().text()).toBe(CANCEL_BUTTON_TEXT);
});
it('renders the Invite button correctly', () => {
- expect(wrapper.findComponent(GlModal).props('actionPrimary')).toMatchObject({
- text: INVITE_BUTTON_TEXT,
- attributes: {
- variant: 'confirm',
- disabled: false,
- loading: false,
- 'data-qa-selector': 'invite_button',
- },
+ const actionButton = findActionButton();
+
+ expect(actionButton.text()).toBe(INVITE_BUTTON_TEXT);
+ expect(actionButton.attributes('data-qa-selector')).toBe('invite_button');
+
+ expect(actionButton.props()).toMatchObject({
+ variant: 'confirm',
+ disabled: false,
+ loading: false,
});
});
@@ -235,7 +230,7 @@ describe('InviteModalBase', () => {
},
});
- expect(wrapper.findComponent(GlModal).props('actionPrimary').attributes.loading).toBe(true);
+ expect(findActionButton().props('loading')).toBe(true);
});
it('with invalidFeedbackMessage, set members form group exception state', () => {
diff --git a/spec/frontend/invite_members/components/members_token_select_spec.js b/spec/frontend/invite_members/components/members_token_select_spec.js
index 0455460918c..c7e9905dee3 100644
--- a/spec/frontend/invite_members/components/members_token_select_spec.js
+++ b/spec/frontend/invite_members/components/members_token_select_spec.js
@@ -30,11 +30,6 @@ const createComponent = (props) => {
describe('MembersTokenSelect', () => {
let wrapper;
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findTokenSelector = () => wrapper.findComponent(GlTokenSelector);
describe('rendering the token-selector component', () => {
diff --git a/spec/frontend/invite_members/components/project_select_spec.js b/spec/frontend/invite_members/components/project_select_spec.js
index 6fbf95362fa..20db4f20408 100644
--- a/spec/frontend/invite_members/components/project_select_spec.js
+++ b/spec/frontend/invite_members/components/project_select_spec.js
@@ -23,10 +23,6 @@ describe('ProjectSelect', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGlCollapsibleListbox = () => wrapper.findComponent(GlCollapsibleListbox);
const findAvatarLabeled = (index) => wrapper.findAllComponents(GlAvatarLabeled).at(index);
diff --git a/spec/frontend/invite_members/utils/trigger_successful_invite_alert_spec.js b/spec/frontend/invite_members/utils/trigger_successful_invite_alert_spec.js
index 38b16dd0c2c..fd011658f95 100644
--- a/spec/frontend/invite_members/utils/trigger_successful_invite_alert_spec.js
+++ b/spec/frontend/invite_members/utils/trigger_successful_invite_alert_spec.js
@@ -6,10 +6,10 @@ import {
TOAST_MESSAGE_LOCALSTORAGE_KEY,
TOAST_MESSAGE_SUCCESSFUL,
} from '~/invite_members/constants';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
-jest.mock('~/flash');
+jest.mock('~/alert');
useLocalStorageSpy();
describe('Display Successful Invitation Alert', () => {
diff --git a/spec/frontend/issuable/components/csv_export_modal_spec.js b/spec/frontend/issuable/components/csv_export_modal_spec.js
index f798f87b6b2..ccd53e64c4d 100644
--- a/spec/frontend/issuable/components/csv_export_modal_spec.js
+++ b/spec/frontend/issuable/components/csv_export_modal_spec.js
@@ -17,7 +17,7 @@ describe('CsvExportModal', () => {
...props,
},
provide: {
- issuableType: 'issues',
+ issuableType: 'issue',
...injectedProperties,
},
stubs: {
@@ -29,19 +29,15 @@ describe('CsvExportModal', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
const findModal = () => wrapper.findComponent(GlModal);
const findIcon = () => wrapper.findComponent(GlIcon);
describe('template', () => {
describe.each`
- issuableType | modalTitle
- ${'issues'} | ${'Export issues'}
- ${'merge-requests'} | ${'Export merge requests'}
- `('with the issuableType "$issuableType"', ({ issuableType, modalTitle }) => {
+ issuableType | modalTitle | dataTrackLabel
+ ${'issue'} | ${'Export issues'} | ${'export_issues_csv'}
+ ${'merge_request'} | ${'Export merge requests'} | ${'export_merge-requests_csv'}
+ `('with the issuableType "$issuableType"', ({ issuableType, modalTitle, dataTrackLabel }) => {
beforeEach(() => {
wrapper = createComponent({ injectedProperties: { issuableType } });
});
@@ -57,9 +53,9 @@ describe('CsvExportModal', () => {
href: 'export/csv/path',
variant: 'confirm',
'data-method': 'post',
- 'data-qa-selector': `export_${issuableType}_button`,
+ 'data-qa-selector': `export_issues_button`,
'data-track-action': 'click_button',
- 'data-track-label': `export_${issuableType}_csv`,
+ 'data-track-label': dataTrackLabel,
},
});
});
diff --git a/spec/frontend/issuable/components/csv_import_export_buttons_spec.js b/spec/frontend/issuable/components/csv_import_export_buttons_spec.js
index 118c12d968b..a861148abb6 100644
--- a/spec/frontend/issuable/components/csv_import_export_buttons_spec.js
+++ b/spec/frontend/issuable/components/csv_import_export_buttons_spec.js
@@ -16,7 +16,7 @@ describe('CsvImportExportButtons', () => {
glModalDirective = jest.fn();
return mountExtended(CsvImportExportButtons, {
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
glModal: {
bind(_, { value }) {
glModalDirective(value);
@@ -33,10 +33,6 @@ describe('CsvImportExportButtons', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
const findExportCsvButton = () => wrapper.findComponent(GlButton);
const findImportDropdown = () => wrapper.findComponent(GlDropdown);
const findImportCsvButton = () => wrapper.findByRole('menuitem', { name: 'Import CSV' });
diff --git a/spec/frontend/issuable/components/csv_import_modal_spec.js b/spec/frontend/issuable/components/csv_import_modal_spec.js
index 6e954c91f46..9069d2b3ab3 100644
--- a/spec/frontend/issuable/components/csv_import_modal_spec.js
+++ b/spec/frontend/issuable/components/csv_import_modal_spec.js
@@ -32,10 +32,6 @@ describe('CsvImportModal', () => {
formSubmitSpy = jest.spyOn(HTMLFormElement.prototype, 'submit').mockImplementation();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findModal = () => wrapper.findComponent(GlModal);
const findForm = () => wrapper.find('form');
const findFileInput = () => wrapper.findByLabelText('Upload CSV file');
diff --git a/spec/frontend/issuable/components/issuable_by_email_spec.js b/spec/frontend/issuable/components/issuable_by_email_spec.js
index b04a6c0b8fd..4cc5775b54e 100644
--- a/spec/frontend/issuable/components/issuable_by_email_spec.js
+++ b/spec/frontend/issuable/components/issuable_by_email_spec.js
@@ -53,8 +53,6 @@ describe('IssuableByEmail', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
mockAxios.restore();
});
diff --git a/spec/frontend/issuable/components/issuable_header_warnings_spec.js b/spec/frontend/issuable/components/issuable_header_warnings_spec.js
index 99aa6778e1e..ff772040d22 100644
--- a/spec/frontend/issuable/components/issuable_header_warnings_spec.js
+++ b/spec/frontend/issuable/components/issuable_header_warnings_spec.js
@@ -25,16 +25,11 @@ describe('IssuableHeaderWarnings', () => {
store,
provide,
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe.each`
issuableType
${ISSUABLE_TYPE_ISSUE} | ${ISSUABLE_TYPE_MR}
diff --git a/spec/frontend/issuable/components/issue_assignees_spec.js b/spec/frontend/issuable/components/issue_assignees_spec.js
index 9a33bfae240..8ed51120508 100644
--- a/spec/frontend/issuable/components/issue_assignees_spec.js
+++ b/spec/frontend/issuable/components/issue_assignees_spec.js
@@ -21,11 +21,6 @@ describe('IssueAssigneesComponent', () => {
vm = wrapper.vm;
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findTooltipText = () => wrapper.find('.js-assignee-tooltip').text();
const findAvatars = () => wrapper.findAllComponents(UserAvatarLink);
const findOverflowCounter = () => wrapper.find('.avatar-counter');
diff --git a/spec/frontend/issuable/components/issue_milestone_spec.js b/spec/frontend/issuable/components/issue_milestone_spec.js
index eac53c5f761..232d6177862 100644
--- a/spec/frontend/issuable/components/issue_milestone_spec.js
+++ b/spec/frontend/issuable/components/issue_milestone_spec.js
@@ -1,160 +1,61 @@
-import { GlIcon } from '@gitlab/ui';
+import { GlIcon, GlTooltip } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import Vue, { nextTick } from 'vue';
-
import { mockMilestone } from 'jest/boards/mock_data';
import IssueMilestone from '~/issuable/components/issue_milestone.vue';
-const createComponent = (milestone = mockMilestone) => {
- const Component = Vue.extend(IssueMilestone);
-
- return shallowMount(Component, {
- propsData: {
- milestone,
- },
- });
-};
-
-describe('IssueMilestoneComponent', () => {
+describe('IssueMilestone component', () => {
let wrapper;
- let vm;
- beforeEach(async () => {
- wrapper = createComponent();
+ const findTooltip = () => wrapper.findComponent(GlTooltip);
- ({ vm } = wrapper);
+ const createComponent = (milestone = mockMilestone) =>
+ shallowMount(IssueMilestone, { propsData: { milestone } });
- await nextTick();
+ beforeEach(() => {
+ wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
+ it('renders milestone icon', () => {
+ expect(wrapper.findComponent(GlIcon).props('name')).toBe('clock');
});
- describe('computed', () => {
- describe('isMilestoneStarted', () => {
- it('should return `false` when milestoneStart prop is not defined', async () => {
- wrapper.setProps({
- milestone: { ...mockMilestone, start_date: '' },
- });
- await nextTick();
-
- expect(wrapper.vm.isMilestoneStarted).toBe(false);
- });
-
- it('should return `true` when milestone start date is past current date', async () => {
- await wrapper.setProps({
- milestone: { ...mockMilestone, start_date: '1990-07-22' },
- });
- await nextTick();
+ it('renders milestone title', () => {
+ expect(wrapper.find('.milestone-title').text()).toBe(mockMilestone.title);
+ });
- expect(wrapper.vm.isMilestoneStarted).toBe(true);
- });
+ describe('tooltip', () => {
+ it('renders `Milestone`', () => {
+ expect(findTooltip().text()).toContain('Milestone');
});
- describe('isMilestonePastDue', () => {
- it('should return `false` when milestoneDue prop is not defined', async () => {
- wrapper.setProps({
- milestone: { ...mockMilestone, due_date: '' },
- });
- await nextTick();
-
- expect(wrapper.vm.isMilestonePastDue).toBe(false);
- });
-
- it('should return `true` when milestone due is past current date', () => {
- wrapper.setProps({
- milestone: { ...mockMilestone, due_date: '1990-07-22' },
- });
-
- expect(wrapper.vm.isMilestonePastDue).toBe(true);
- });
+ it('renders milestone title', () => {
+ expect(findTooltip().text()).toContain(mockMilestone.title);
});
- describe('milestoneDatesAbsolute', () => {
- it('returns string containing absolute milestone due date', () => {
- expect(vm.milestoneDatesAbsolute).toBe('(December 31, 2019)');
- });
+ describe('humanized dates', () => {
+ it('renders `Expired` when there is a due date in the past', () => {
+ wrapper = createComponent({ ...mockMilestone, due_date: '2019-12-31', start_date: '' });
- it('returns string containing absolute milestone start date when due date is not present', async () => {
- wrapper.setProps({
- milestone: { ...mockMilestone, due_date: '' },
- });
- await nextTick();
-
- expect(wrapper.vm.milestoneDatesAbsolute).toBe('(January 1, 2018)');
+ expect(findTooltip().text()).toContain('Expired 6 months ago(December 31, 2019)');
});
- it('returns empty string when both milestone start and due dates are not present', async () => {
- wrapper.setProps({
- milestone: { ...mockMilestone, start_date: '', due_date: '' },
- });
- await nextTick();
+ it('renders `remaining` when there is a due date in the future', () => {
+ wrapper = createComponent({ ...mockMilestone, due_date: '2020-12-31', start_date: '' });
- expect(wrapper.vm.milestoneDatesAbsolute).toBe('');
+ expect(findTooltip().text()).toContain('5 months remaining(December 31, 2020)');
});
- });
- describe('milestoneDatesHuman', () => {
- it('returns string containing milestone due date when date is yet to be due', async () => {
- wrapper.setProps({
- milestone: { ...mockMilestone, due_date: `${new Date().getFullYear() + 10}-01-01` },
- });
- await nextTick();
+ it('renders `Started` when there is a start date in the past', () => {
+ wrapper = createComponent({ ...mockMilestone, due_date: '', start_date: '2019-12-31' });
- expect(wrapper.vm.milestoneDatesHuman).toContain('years remaining');
+ expect(findTooltip().text()).toContain('Started 6 months ago(December 31, 2019)');
});
- it('returns string containing milestone start date when date has already started and due date is not present', async () => {
- wrapper.setProps({
- milestone: { ...mockMilestone, start_date: '1990-07-22', due_date: '' },
- });
- await nextTick();
+ it('renders `Starts` when there is a start date in the future', () => {
+ wrapper = createComponent({ ...mockMilestone, due_date: '', start_date: '2020-12-31' });
- expect(wrapper.vm.milestoneDatesHuman).toContain('Started');
+ expect(findTooltip().text()).toContain('Starts in 5 months(December 31, 2020)');
});
-
- it('returns string containing milestone start date when date is yet to start and due date is not present', async () => {
- wrapper.setProps({
- milestone: {
- ...mockMilestone,
- start_date: `${new Date().getFullYear() + 10}-01-01`,
- due_date: '',
- },
- });
- await nextTick();
-
- expect(wrapper.vm.milestoneDatesHuman).toContain('Starts');
- });
-
- it('returns empty string when milestone start and due dates are not present', async () => {
- wrapper.setProps({
- milestone: { ...mockMilestone, start_date: '', due_date: '' },
- });
- await nextTick();
-
- expect(wrapper.vm.milestoneDatesHuman).toBe('');
- });
- });
- });
-
- describe('template', () => {
- it('renders component root element with class `issue-milestone-details`', () => {
- expect(vm.$el.classList.contains('issue-milestone-details')).toBe(true);
- });
-
- it('renders milestone icon', () => {
- expect(wrapper.findComponent(GlIcon).props('name')).toBe('clock');
- });
-
- it('renders milestone title', () => {
- expect(vm.$el.querySelector('.milestone-title').innerText.trim()).toBe(mockMilestone.title);
- });
-
- it('renders milestone tooltip', () => {
- expect(vm.$el.querySelector('.js-item-milestone').innerText.trim()).toContain(
- mockMilestone.title,
- );
});
});
});
diff --git a/spec/frontend/issuable/components/related_issuable_item_spec.js b/spec/frontend/issuable/components/related_issuable_item_spec.js
index 3f9f048605a..3e23558ceb4 100644
--- a/spec/frontend/issuable/components/related_issuable_item_spec.js
+++ b/spec/frontend/issuable/components/related_issuable_item_spec.js
@@ -53,10 +53,6 @@ describe('RelatedIssuableItem', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('contains issuable-info-container class when canReorder is false', () => {
mountComponent({ props: { canReorder: false } });
diff --git a/spec/frontend/issuable/components/status_box_spec.js b/spec/frontend/issuable/components/status_box_spec.js
index 728b8958b9b..d26f287d90c 100644
--- a/spec/frontend/issuable/components/status_box_spec.js
+++ b/spec/frontend/issuable/components/status_box_spec.js
@@ -11,11 +11,6 @@ function factory(propsData) {
describe('Merge request status box component', () => {
const findBadge = () => wrapper.findComponent(GlBadge);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe.each`
issuableType | badgeText | initialState | badgeClass | badgeVariant | badgeIcon
${'merge_request'} | ${'Open'} | ${'opened'} | ${'issuable-status-badge-open'} | ${'success'} | ${'merge-request-open'}
diff --git a/spec/frontend/issuable/popover/components/issue_popover_spec.js b/spec/frontend/issuable/popover/components/issue_popover_spec.js
index 444165f61c7..a7605016039 100644
--- a/spec/frontend/issuable/popover/components/issue_popover_spec.js
+++ b/spec/frontend/issuable/popover/components/issue_popover_spec.js
@@ -33,10 +33,6 @@ describe('Issue Popover', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('shows skeleton-loader while apollo is loading', () => {
mountComponent();
diff --git a/spec/frontend/issuable/popover/components/mr_popover_spec.js b/spec/frontend/issuable/popover/components/mr_popover_spec.js
index 5fdd1e6e8fc..d9e113eeaae 100644
--- a/spec/frontend/issuable/popover/components/mr_popover_spec.js
+++ b/spec/frontend/issuable/popover/components/mr_popover_spec.js
@@ -71,10 +71,6 @@ describe('MR Popover', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('shows skeleton-loader while apollo is loading', () => {
mountComponent();
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 72fcab63ba7..f8e47bc0a4b 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,9 +1,9 @@
-import { GlFormGroup } from '@gitlab/ui';
+import { GlButton, GlFormGroup, GlFormRadioGroup, GlFormRadio } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
import { TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
import AddIssuableForm from '~/related_issues/components/add_issuable_form.vue';
import IssueToken from '~/related_issues/components/issue_token.vue';
+import RelatedIssuableInput from '~/related_issues/components/related_issuable_input.vue';
import { linkedIssueTypesMap, PathIdSeparator } from '~/related_issues/constants';
const issuable1 = {
@@ -26,71 +26,60 @@ const issuable2 = {
const pathIdSeparator = PathIdSeparator.Issue;
-const findFormInput = (wrapper) => wrapper.find('input').element;
-
-const findRadioInput = (inputs, value) =>
- inputs.filter((input) => input.element.value === value)[0];
-
-const findRadioInputs = (wrapper) => wrapper.findAll('[name="linked-issue-type-radio"]');
-
-const constructWrapper = (props) => {
- return shallowMount(AddIssuableForm, {
- propsData: {
- inputValue: '',
- pendingReferences: [],
- pathIdSeparator,
- ...props,
- },
- });
-};
-
describe('AddIssuableForm', () => {
let wrapper;
- afterEach(() => {
- // Jest doesn't blur an item even if it is destroyed,
- // so blur the input manually after each test
- const input = findFormInput(wrapper);
- if (input) input.blur();
+ const createComponent = (props = {}, mountFn = shallowMount) => {
+ wrapper = mountFn(AddIssuableForm, {
+ propsData: {
+ inputValue: '',
+ pendingReferences: [],
+ pathIdSeparator,
+ ...props,
+ },
+ stubs: {
+ RelatedIssuableInput,
+ },
+ });
+ };
- if (wrapper) {
- wrapper.destroy();
- wrapper = null;
- }
- });
+ const findAddIssuableForm = () => wrapper.find('form');
+ const findFormInput = () => wrapper.find('input').element;
+ const findRadioInput = (inputs, value) =>
+ inputs.filter((input) => input.element.value === value)[0];
+ const findAllIssueTokens = () => wrapper.findAllComponents(IssueToken);
+ const findRadioGroup = () => wrapper.findComponent(GlFormRadioGroup);
+ const findRadioInputs = () => wrapper.findAllComponents(GlFormRadio);
+
+ const findFormGroup = () => wrapper.findComponent(GlFormGroup);
+ const findFormButtons = () => wrapper.findAllComponents(GlButton);
+ const findSubmitButton = () => findFormButtons().at(0);
+ const findRelatedIssuableInput = () => wrapper.findComponent(RelatedIssuableInput);
describe('with data', () => {
describe('without references', () => {
describe('without any input text', () => {
beforeEach(() => {
- wrapper = shallowMount(AddIssuableForm, {
- propsData: {
- inputValue: '',
- pendingReferences: [],
- pathIdSeparator,
- },
- });
+ createComponent();
});
it('should have disabled submit button', () => {
- expect(wrapper.vm.$refs.addButton.disabled).toBe(true);
- expect(wrapper.vm.$refs.loadingIcon).toBeUndefined();
+ expect(findSubmitButton().props('disabled')).toBe(true);
+ expect(findSubmitButton().props('loading')).toBe(false);
});
});
describe('with input text', () => {
beforeEach(() => {
- wrapper = shallowMount(AddIssuableForm, {
- propsData: {
- inputValue: 'foo',
- pendingReferences: [],
- pathIdSeparator,
- },
+ createComponent({
+ inputValue: 'foo',
+ pendingReferences: [],
+ pathIdSeparator,
});
});
it('should not have disabled submit button', () => {
- expect(wrapper.vm.$refs.addButton.disabled).toBe(false);
+ expect(findSubmitButton().props('disabled')).toBe(false);
});
});
});
@@ -99,59 +88,56 @@ describe('AddIssuableForm', () => {
const inputValue = 'foo #123';
beforeEach(() => {
- wrapper = mount(AddIssuableForm, {
- propsData: {
- inputValue,
- pendingReferences: [issuable1.reference, issuable2.reference],
- pathIdSeparator,
- },
+ createComponent({
+ inputValue,
+ pendingReferences: [issuable1.reference, issuable2.reference],
+ pathIdSeparator,
});
- });
+ }, mount);
it('should put input value in place', () => {
expect(findFormInput(wrapper).value).toBe(inputValue);
});
it('should render pending issuables items', () => {
- expect(wrapper.findAllComponents(IssueToken)).toHaveLength(2);
+ expect(findAllIssueTokens()).toHaveLength(2);
});
it('should not have disabled submit button', () => {
- expect(wrapper.vm.$refs.addButton.disabled).toBe(false);
+ expect(findSubmitButton().props('disabled')).toBe(false);
});
});
describe('when issuable type is "issue"', () => {
beforeEach(() => {
- wrapper = mount(AddIssuableForm, {
- propsData: {
+ createComponent(
+ {
inputValue: '',
issuableType: TYPE_ISSUE,
pathIdSeparator,
pendingReferences: [],
},
- });
+ mount,
+ );
});
it('does not show radio inputs', () => {
- expect(findRadioInputs(wrapper).length).toBe(0);
+ expect(findRadioInputs()).toHaveLength(0);
});
});
describe('when issuable type is "epic"', () => {
beforeEach(() => {
- wrapper = shallowMount(AddIssuableForm, {
- propsData: {
- inputValue: '',
- issuableType: TYPE_EPIC,
- pathIdSeparator,
- pendingReferences: [],
- },
+ createComponent({
+ inputValue: '',
+ issuableType: TYPE_EPIC,
+ pathIdSeparator,
+ pendingReferences: [],
});
});
it('does not show radio inputs', () => {
- expect(findRadioInputs(wrapper).length).toBe(0);
+ expect(findRadioInputs()).toHaveLength(0);
});
});
@@ -163,17 +149,15 @@ describe('AddIssuableForm', () => {
`(
'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: [],
- },
+ createComponent({
+ issuableType,
+ inputValue: '',
+ showCategorizedIssues: true,
+ pathIdSeparator,
+ pendingReferences: [],
});
- expect(wrapper.findComponent(GlFormGroup).attributes('label')).toBe(contextHeader);
+ expect(findFormGroup().attributes('label')).toBe(contextHeader);
expect(wrapper.find('p.bold').text()).toContain(contextFooter);
},
);
@@ -181,26 +165,24 @@ describe('AddIssuableForm', () => {
describe('when it is a Linked Issues form', () => {
beforeEach(() => {
- wrapper = mount(AddIssuableForm, {
- propsData: {
- inputValue: '',
- showCategorizedIssues: true,
- issuableType: TYPE_ISSUE,
- pathIdSeparator,
- pendingReferences: [],
- },
+ createComponent({
+ inputValue: '',
+ showCategorizedIssues: true,
+ issuableType: TYPE_ISSUE,
+ pathIdSeparator,
+ pendingReferences: [],
});
});
it('shows radio inputs to allow categorisation of blocking issues', () => {
- expect(findRadioInputs(wrapper).length).toBeGreaterThan(0);
+ expect(findRadioGroup().props('options').length).toBeGreaterThan(0);
});
describe('form radio buttons', () => {
let radioInputs;
beforeEach(() => {
- radioInputs = findRadioInputs(wrapper);
+ radioInputs = findRadioInputs();
});
it('shows "relates to" option', () => {
@@ -216,58 +198,59 @@ describe('AddIssuableForm', () => {
});
it('shows 3 options in total', () => {
- expect(radioInputs.length).toBe(3);
+ expect(findRadioGroup().props('options')).toHaveLength(3);
});
});
describe('when the form is submitted', () => {
- 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();
-
- await nextTick();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('addIssuableFormSubmit', {
- pendingReferences: '',
- linkedIssueType: linkedIssueTypesMap.RELATES_TO,
- });
+ it('emits an event with a "relates_to" link type when the "relates to" radio input selected', () => {
+ findAddIssuableForm().trigger('submit');
+
+ expect(wrapper.emitted('addIssuableFormSubmit')).toEqual([
+ [
+ {
+ pendingReferences: '',
+ linkedIssueType: linkedIssueTypesMap.RELATES_TO,
+ },
+ ],
+ ]);
});
- 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();
-
- await nextTick();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('addIssuableFormSubmit', {
- pendingReferences: '',
- linkedIssueType: linkedIssueTypesMap.BLOCKS,
- });
+ it('emits an event with a "blocks" link type when the "blocks" radio input selected', () => {
+ findRadioGroup().vm.$emit('input', linkedIssueTypesMap.BLOCKS);
+ findAddIssuableForm().trigger('submit');
+
+ expect(wrapper.emitted('addIssuableFormSubmit')).toEqual([
+ [
+ {
+ pendingReferences: '',
+ linkedIssueType: linkedIssueTypesMap.BLOCKS,
+ },
+ ],
+ ]);
});
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();
-
- await nextTick();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('addIssuableFormSubmit', {
- pendingReferences: '',
- linkedIssueType: linkedIssueTypesMap.IS_BLOCKED_BY,
- });
+ findRadioGroup().vm.$emit('input', linkedIssueTypesMap.IS_BLOCKED_BY);
+ findAddIssuableForm().trigger('submit');
+
+ expect(wrapper.emitted('addIssuableFormSubmit')).toEqual([
+ [
+ {
+ pendingReferences: '',
+ linkedIssueType: linkedIssueTypesMap.IS_BLOCKED_BY,
+ },
+ ],
+ ]);
});
- it('shows error message when error is present', async () => {
+ it('shows error message when error is present', () => {
const itemAddFailureMessage = 'Something went wrong while submitting.';
- wrapper.setProps({
+ createComponent({
hasError: true,
itemAddFailureMessage,
});
- await nextTick();
expect(wrapper.find('.gl-field-error').exists()).toBe(true);
expect(wrapper.find('.gl-field-error').text()).toContain(itemAddFailureMessage);
});
@@ -283,27 +266,31 @@ describe('AddIssuableForm', () => {
};
it('returns autocomplete object', () => {
- wrapper = constructWrapper({
+ createComponent({
autoCompleteSources,
});
- expect(wrapper.vm.transformedAutocompleteSources).toBe(autoCompleteSources);
+ expect(findRelatedIssuableInput().props('autoCompleteSources')).toEqual(
+ autoCompleteSources,
+ );
- wrapper = constructWrapper({
+ createComponent({
autoCompleteSources,
confidential: false,
});
- expect(wrapper.vm.transformedAutocompleteSources).toBe(autoCompleteSources);
+ expect(findRelatedIssuableInput().props('autoCompleteSources')).toEqual(
+ autoCompleteSources,
+ );
});
it('returns autocomplete sources with query `confidential_only`, when it is confidential', () => {
- wrapper = constructWrapper({
+ createComponent({
autoCompleteSources,
confidential: true,
});
- const actualSources = wrapper.vm.transformedAutocompleteSources;
+ const actualSources = findRelatedIssuableInput().props('autoCompleteSources');
expect(actualSources.epics).toContain('?confidential_only=true');
expect(actualSources.issues).toContain('?confidential_only=true');
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 ff8d5073005..b9580b90c12 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
@@ -1,5 +1,5 @@
import { nextTick } from 'vue';
-import { GlIcon } from '@gitlab/ui';
+import { GlIcon, GlCard } from '@gitlab/ui';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import {
issuable1,
@@ -78,6 +78,9 @@ describe('RelatedIssuesBlock', () => {
pathIdSeparator: PathIdSeparator.Issue,
issuableType: 'issue',
},
+ stubs: {
+ GlCard,
+ },
slots: { 'header-text': headerText },
});
@@ -94,6 +97,9 @@ describe('RelatedIssuesBlock', () => {
pathIdSeparator: PathIdSeparator.Issue,
issuableType: 'issue',
},
+ stubs: {
+ GlCard,
+ },
slots: { 'header-actions': headerActions },
});
@@ -222,6 +228,9 @@ describe('RelatedIssuesBlock', () => {
pathIdSeparator: PathIdSeparator.Issue,
issuableType,
},
+ stubs: {
+ GlCard,
+ },
});
const iconComponent = wrapper.findComponent(GlIcon);
@@ -239,6 +248,9 @@ describe('RelatedIssuesBlock', () => {
relatedIssues: [issuable1, issuable2, issuable3],
issuableType: TYPE_ISSUE,
},
+ stubs: {
+ GlCard,
+ },
});
});
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 96c0b87e2cb..1383013aedb 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
@@ -7,7 +7,7 @@ import {
issuable1,
issuable2,
} from 'jest/issuable/components/related_issuable_mock_data';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import {
HTTP_STATUS_CONFLICT,
@@ -19,7 +19,7 @@ import RelatedIssuesBlock from '~/related_issues/components/related_issues_block
import RelatedIssuesRoot from '~/related_issues/components/related_issues_root.vue';
import relatedIssuesService from '~/related_issues/services/related_issues_service';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('RelatedIssuesRoot', () => {
let wrapper;
@@ -34,7 +34,6 @@ describe('RelatedIssuesRoot', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
const createComponent = ({ props = {}, data = {} } = {}) => {
diff --git a/spec/frontend/issues/create_merge_request_dropdown_spec.js b/spec/frontend/issues/create_merge_request_dropdown_spec.js
index cc2ee84348a..21ae844e2dd 100644
--- a/spec/frontend/issues/create_merge_request_dropdown_spec.js
+++ b/spec/frontend/issues/create_merge_request_dropdown_spec.js
@@ -65,6 +65,14 @@ describe('CreateMergeRequestDropdown', () => {
expect(dropdown.createMrPath).toBe(
`${TEST_HOST}/create_merge_request?merge_request%5Bsource_branch%5D=contains%23hash&merge_request%5Btarget_branch%5D=master&merge_request%5Bissue_iid%5D=42`,
);
+
+ expect(dropdown.wrapperEl.dataset.createBranchPath).toBe(
+ `${TEST_HOST}/branches?branch_name=contains%23hash&issue=42`,
+ );
+
+ expect(dropdown.wrapperEl.dataset.createMrPath).toBe(
+ `${TEST_HOST}/create_merge_request?merge_request%5Bsource_branch%5D=contains%23hash&merge_request%5Btarget_branch%5D=master&merge_request%5Bissue_iid%5D=42`,
+ );
});
});
diff --git a/spec/frontend/issues/dashboard/components/issues_dashboard_app_spec.js b/spec/frontend/issues/dashboard/components/issues_dashboard_app_spec.js
index 77d5a0579a4..ebf4771e97f 100644
--- a/spec/frontend/issues/dashboard/components/issues_dashboard_app_spec.js
+++ b/spec/frontend/issues/dashboard/components/issues_dashboard_app_spec.js
@@ -18,6 +18,7 @@ import {
setSortPreferenceMutationResponse,
setSortPreferenceMutationResponseWithErrors,
} from 'jest/issues/list/mock_data';
+import { STATUS_ALL, STATUS_CLOSED, STATUS_OPEN } from '~/issues/constants';
import IssuesDashboardApp from '~/issues/dashboard/components/issues_dashboard_app.vue';
import getIssuesCountsQuery from '~/issues/dashboard/queries/get_issues_counts.query.graphql';
import { CREATED_DESC, i18n, UPDATED_DESC, urlSortParams } from '~/issues/list/constants';
@@ -36,7 +37,6 @@ import {
TOKEN_TYPE_TYPE,
} from '~/vue_shared/components/filtered_search_bar/constants';
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
-import { IssuableStates } from '~/vue_shared/issuable/list/constants';
import {
emptyIssuesQueryResponse,
issuesCountsQueryResponse,
@@ -124,7 +124,7 @@ describe('IssuesDashboardApp component', () => {
// eslint-disable-next-line jest/no-disabled-tests
it.skip('renders IssuableList component', () => {
expect(findIssuableList().props()).toMatchObject({
- currentTab: IssuableStates.Opened,
+ currentTab: STATUS_OPEN,
hasNextPage: true,
hasPreviousPage: false,
hasScopedLabelsFeature: defaultProvide.hasScopedLabelsFeature,
@@ -148,7 +148,7 @@ describe('IssuesDashboardApp component', () => {
tabs: IssuesDashboardApp.IssuableListTabs,
urlParams: {
sort: urlSortParams[CREATED_DESC],
- state: IssuableStates.Opened,
+ state: STATUS_OPEN,
},
useKeysetPagination: true,
});
@@ -283,7 +283,7 @@ describe('IssuesDashboardApp component', () => {
describe('state', () => {
it('is set from the url params', () => {
- const initialState = IssuableStates.All;
+ const initialState = STATUS_ALL;
setWindowLocation(`?state=${initialState}`);
mountComponent();
@@ -337,11 +337,9 @@ describe('IssuesDashboardApp component', () => {
username: 'root',
avatar_url: 'avatar/url',
};
- const originalGon = window.gon;
beforeEach(() => {
window.gon = {
- ...originalGon,
current_user_id: mockCurrentUser.id,
current_user_fullname: mockCurrentUser.name,
current_username: mockCurrentUser.username,
@@ -350,10 +348,6 @@ describe('IssuesDashboardApp component', () => {
mountComponent();
});
- afterEach(() => {
- window.gon = originalGon;
- });
-
it('renders all tokens alphabetically', () => {
const preloadedUsers = [{ ...mockCurrentUser, id: mockCurrentUser.id }];
@@ -375,16 +369,16 @@ describe('IssuesDashboardApp component', () => {
beforeEach(() => {
mountComponent();
- findIssuableList().vm.$emit('click-tab', IssuableStates.Closed);
+ findIssuableList().vm.$emit('click-tab', STATUS_CLOSED);
});
it('updates ui to the new tab', () => {
- expect(findIssuableList().props('currentTab')).toBe(IssuableStates.Closed);
+ expect(findIssuableList().props('currentTab')).toBe(STATUS_CLOSED);
});
it('updates url to the new tab', () => {
expect(findIssuableList().props('urlParams')).toMatchObject({
- state: IssuableStates.Closed,
+ state: STATUS_CLOSED,
});
});
});
diff --git a/spec/frontend/issues/list/components/issue_card_time_info_spec.js b/spec/frontend/issues/list/components/issue_card_time_info_spec.js
index ab4d023ee39..e80ffea0591 100644
--- a/spec/frontend/issues/list/components/issue_card_time_info_spec.js
+++ b/spec/frontend/issues/list/components/issue_card_time_info_spec.js
@@ -45,10 +45,6 @@ describe('CE IssueCardTimeInfo component', () => {
},
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('milestone', () => {
it('renders', () => {
wrapper = mountComponent();
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 8281ce0ed1a..b28a08e2fce 100644
--- a/spec/frontend/issues/list/components/issues_list_app_spec.js
+++ b/spec/frontend/issues/list/components/issues_list_app_spec.js
@@ -15,19 +15,21 @@ import waitForPromises from 'helpers/wait_for_promises';
import {
getIssuesCountsQueryResponse,
getIssuesQueryResponse,
+ getIssuesQueryEmptyResponse,
filteredTokens,
locationSearch,
setSortPreferenceMutationResponse,
setSortPreferenceMutationResponseWithErrors,
urlParams,
} from 'jest/issues/list/mock_data';
-import { createAlert, VARIANT_INFO } from '~/flash';
+import { createAlert, VARIANT_INFO } from '~/alert';
import { TYPENAME_USER } from '~/graphql_shared/constants';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { STATUS_ALL, STATUS_CLOSED, STATUS_OPEN } from '~/issues/constants';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
-import { IssuableListTabs, IssuableStates } from '~/vue_shared/issuable/list/constants';
+import { IssuableListTabs } from '~/vue_shared/issuable/list/constants';
import EmptyStateWithAnyIssues from '~/issues/list/components/empty_state_with_any_issues.vue';
import EmptyStateWithoutAnyIssues from '~/issues/list/components/empty_state_without_any_issues.vue';
import IssuesListApp from '~/issues/list/components/issues_list_app.vue';
@@ -70,7 +72,7 @@ import('~/issuable');
import('~/users_select');
jest.mock('@sentry/browser');
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/scroll_utils', () => ({ scrollUp: jest.fn() }));
describe('CE IssuesListApp component', () => {
@@ -154,7 +156,24 @@ describe('CE IssuesListApp component', () => {
router = new VueRouter({ mode: 'history' });
return mountFn(IssuesListApp, {
- apolloProvider: createMockApollo(requestHandlers),
+ apolloProvider: createMockApollo(
+ requestHandlers,
+ {},
+ {
+ typePolicies: {
+ Query: {
+ fields: {
+ project: {
+ merge: true,
+ },
+ group: {
+ merge: true,
+ },
+ },
+ },
+ },
+ },
+ ),
router,
provide: {
...defaultProvide,
@@ -174,13 +193,11 @@ describe('CE IssuesListApp component', () => {
afterEach(() => {
axiosMock.reset();
- wrapper.destroy();
});
describe('IssuableList', () => {
beforeEach(() => {
wrapper = mountComponent();
- jest.runOnlyPendingTimers();
return waitForPromises();
});
@@ -197,7 +214,7 @@ describe('CE IssuesListApp component', () => {
initialSortBy: CREATED_DESC,
issuables: getIssuesQueryResponse.data.project.issues.nodes,
tabs: IssuableListTabs,
- currentTab: IssuableStates.Opened,
+ currentTab: STATUS_OPEN,
tabCounts: {
opened: 1,
closed: 1,
@@ -247,7 +264,6 @@ describe('CE IssuesListApp component', () => {
mountFn: mount,
});
- jest.runOnlyPendingTimers();
return waitForPromises();
});
@@ -416,7 +432,7 @@ describe('CE IssuesListApp component', () => {
describe('state', () => {
it('is set from the url params', () => {
- const initialState = IssuableStates.All;
+ const initialState = STATUS_ALL;
setWindowLocation(`?state=${initialState}`);
wrapper = mountComponent();
@@ -477,7 +493,12 @@ describe('CE IssuesListApp component', () => {
describe('empty states', () => {
describe('when there are issues', () => {
beforeEach(() => {
- wrapper = mountComponent({ provide: { hasAnyIssues: true }, mountFn: mount });
+ wrapper = mountComponent({
+ provide: { hasAnyIssues: true },
+ mountFn: mount,
+ issuesQueryResponse: getIssuesQueryEmptyResponse,
+ });
+ return waitForPromises();
});
it('shows EmptyStateWithAnyIssues empty state', () => {
@@ -543,11 +564,8 @@ describe('CE IssuesListApp component', () => {
});
describe('when all tokens are available', () => {
- const originalGon = window.gon;
-
beforeEach(() => {
window.gon = {
- ...originalGon,
current_user_id: mockCurrentUser.id,
current_user_fullname: mockCurrentUser.name,
current_username: mockCurrentUser.username,
@@ -563,10 +581,6 @@ describe('CE IssuesListApp component', () => {
});
});
- afterEach(() => {
- window.gon = originalGon;
- });
-
it('renders all tokens alphabetically', () => {
const preloadedUsers = [
{ ...mockCurrentUser, id: convertToGraphQLId(TYPENAME_USER, mockCurrentUser.id) },
@@ -599,7 +613,6 @@ describe('CE IssuesListApp component', () => {
wrapper = mountComponent({
[mountOption]: jest.fn().mockRejectedValue(new Error('ERROR')),
});
- jest.runOnlyPendingTimers();
return waitForPromises();
});
@@ -620,20 +633,21 @@ describe('CE IssuesListApp component', () => {
describe('events', () => {
describe('when "click-tab" event is emitted by IssuableList', () => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper = mountComponent();
+ await waitForPromises();
router.push = jest.fn();
- findIssuableList().vm.$emit('click-tab', IssuableStates.Closed);
+ findIssuableList().vm.$emit('click-tab', STATUS_CLOSED);
});
it('updates ui to the new tab', () => {
- expect(findIssuableList().props('currentTab')).toBe(IssuableStates.Closed);
+ expect(findIssuableList().props('currentTab')).toBe(STATUS_CLOSED);
});
it('updates url to the new tab', () => {
expect(router.push).toHaveBeenCalledWith({
- query: expect.objectContaining({ state: IssuableStates.Closed }),
+ query: expect.objectContaining({ state: STATUS_CLOSED }),
});
});
});
@@ -641,19 +655,25 @@ describe('CE IssuesListApp component', () => {
describe.each`
event | params
${'next-page'} | ${{
- page_after: 'endCursor',
+ page_after: 'endcursor',
page_before: undefined,
first_page_size: 20,
last_page_size: undefined,
+ search: undefined,
+ sort: 'created_date',
+ state: 'opened',
}}
${'previous-page'} | ${{
page_after: undefined,
- page_before: 'startCursor',
+ page_before: 'startcursor',
first_page_size: undefined,
last_page_size: 20,
+ search: undefined,
+ sort: 'created_date',
+ state: 'opened',
}}
`('when "$event" event is emitted by IssuableList', ({ event, params }) => {
- beforeEach(() => {
+ beforeEach(async () => {
wrapper = mountComponent({
data: {
pageInfo: {
@@ -662,6 +682,7 @@ describe('CE IssuesListApp component', () => {
},
},
});
+ await waitForPromises();
router.push = jest.fn();
findIssuableList().vm.$emit(event);
@@ -735,7 +756,6 @@ describe('CE IssuesListApp component', () => {
provide: { isProject },
issuesQueryResponse: jest.fn().mockResolvedValue(response(isProject)),
});
- jest.runOnlyPendingTimers();
return waitForPromises();
});
@@ -761,7 +781,6 @@ describe('CE IssuesListApp component', () => {
wrapper = mountComponent({
issuesQueryResponse: jest.fn().mockResolvedValue(response()),
});
- jest.runOnlyPendingTimers();
return waitForPromises();
});
@@ -793,8 +812,6 @@ describe('CE IssuesListApp component', () => {
router.push = jest.fn();
findIssuableList().vm.$emit('sort', sortKey);
- jest.runOnlyPendingTimers();
- await nextTick();
expect(router.push).toHaveBeenCalledWith({
query: expect.objectContaining({ sort: urlSortParams[sortKey] }),
@@ -914,13 +931,13 @@ describe('CE IssuesListApp component', () => {
${'shows users when public visibility is not restricted and is signed in'} | ${false} | ${true} | ${false}
${'hides users when public visibility is restricted and is not signed in'} | ${true} | ${false} | ${true}
${'shows users when public visibility is restricted and is signed in'} | ${true} | ${true} | ${false}
- `('$description', ({ isPublicVisibilityRestricted, isSignedIn, hideUsers }) => {
+ `('$description', async ({ isPublicVisibilityRestricted, isSignedIn, hideUsers }) => {
const mockQuery = jest.fn().mockResolvedValue(defaultQueryResponse);
wrapper = mountComponent({
provide: { isPublicVisibilityRestricted, isSignedIn },
issuesQueryResponse: mockQuery,
});
- jest.runOnlyPendingTimers();
+ await waitForPromises();
expect(mockQuery).toHaveBeenCalledWith(expect.objectContaining({ hideUsers }));
});
@@ -929,7 +946,6 @@ describe('CE IssuesListApp component', () => {
describe('fetching issues', () => {
beforeEach(() => {
wrapper = mountComponent();
- jest.runOnlyPendingTimers();
});
it('fetches issue, incident, test case, and task types', () => {
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 406b1fbc1af..7bbb5a954ae 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
@@ -38,11 +38,6 @@ describe('JiraIssuesImportStatus', () => {
},
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when Jira import is neither in progress nor finished', () => {
beforeEach(() => {
wrapper = mountComponent();
diff --git a/spec/frontend/issues/list/mock_data.js b/spec/frontend/issues/list/mock_data.js
index 1e8a81116f3..0332f68ddb6 100644
--- a/spec/frontend/issues/list/mock_data.js
+++ b/spec/frontend/issues/list/mock_data.js
@@ -101,6 +101,26 @@ export const getIssuesQueryResponse = {
},
};
+export const getIssuesQueryEmptyResponse = {
+ data: {
+ project: {
+ id: '1',
+ __typename: 'Project',
+ issues: {
+ __persist: true,
+ pageInfo: {
+ __typename: 'PageInfo',
+ hasNextPage: true,
+ hasPreviousPage: false,
+ startCursor: 'startcursor',
+ endCursor: 'endcursor',
+ },
+ nodes: [],
+ },
+ },
+ },
+};
+
export const getIssuesCountsQueryResponse = {
data: {
project: {
diff --git a/spec/frontend/issues/list/utils_spec.js b/spec/frontend/issues/list/utils_spec.js
index a281ed1c989..e4ecdc6c29e 100644
--- a/spec/frontend/issues/list/utils_spec.js
+++ b/spec/frontend/issues/list/utils_spec.js
@@ -10,7 +10,7 @@ import {
urlParams,
urlParamsWithSpecialValues,
} from 'jest/issues/list/mock_data';
-import { PAGE_SIZE, urlSortParams } from '~/issues/list/constants';
+import { urlSortParams } from '~/issues/list/constants';
import {
convertToApiParams,
convertToSearchQuery,
@@ -22,10 +22,11 @@ import {
isSortKey,
} from '~/issues/list/utils';
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
+import { DEFAULT_PAGE_SIZE } from '~/vue_shared/issuable/list/constants';
describe('getInitialPageParams', () => {
it('returns page params with a default page size when no arguments are given', () => {
- expect(getInitialPageParams()).toEqual({ firstPageSize: PAGE_SIZE });
+ expect(getInitialPageParams()).toEqual({ firstPageSize: DEFAULT_PAGE_SIZE });
});
it('returns page params with the given page size', () => {
diff --git a/spec/frontend/issues/new/components/title_suggestions_item_spec.js b/spec/frontend/issues/new/components/title_suggestions_item_spec.js
index c54a762440f..4454ef81416 100644
--- a/spec/frontend/issues/new/components/title_suggestions_item_spec.js
+++ b/spec/frontend/issues/new/components/title_suggestions_item_spec.js
@@ -25,10 +25,6 @@ describe('Issue title suggestions item component', () => {
const findTooltip = () => wrapper.findComponent(GlTooltip);
const findUserAvatar = () => wrapper.findComponent(UserAvatarImage);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders title', () => {
createComponent();
diff --git a/spec/frontend/issues/new/components/title_suggestions_spec.js b/spec/frontend/issues/new/components/title_suggestions_spec.js
index 1cd6576967a..343bdbba301 100644
--- a/spec/frontend/issues/new/components/title_suggestions_spec.js
+++ b/spec/frontend/issues/new/components/title_suggestions_spec.js
@@ -1,106 +1,95 @@
import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import TitleSuggestions from '~/issues/new/components/title_suggestions.vue';
import TitleSuggestionsItem from '~/issues/new/components/title_suggestions_item.vue';
+import getIssueSuggestionsQuery from '~/issues/new/queries/issues.query.graphql';
+import { mockIssueSuggestionResponse } from '../mock_data';
+
+Vue.use(VueApollo);
+
+const MOCK_PROJECT_PATH = 'project';
+const MOCK_ISSUES_COUNT = mockIssueSuggestionResponse.data.project.issues.edges.length;
describe('Issue title suggestions component', () => {
let wrapper;
+ let mockApollo;
+
+ function createComponent({
+ search = 'search',
+ queryResponse = jest.fn().mockResolvedValue(mockIssueSuggestionResponse),
+ } = {}) {
+ mockApollo = createMockApollo([[getIssueSuggestionsQuery, queryResponse]]);
- function createComponent(search = 'search') {
wrapper = shallowMount(TitleSuggestions, {
propsData: {
search,
- projectPath: 'project',
+ projectPath: MOCK_PROJECT_PATH,
},
+ apolloProvider: mockApollo,
});
}
- beforeEach(() => {
- createComponent();
- });
+ const waitForDebounce = () => {
+ jest.runOnlyPendingTimers();
+ return waitForPromises();
+ };
afterEach(() => {
- wrapper.destroy();
+ mockApollo = null;
});
it('does not render with empty search', async () => {
- wrapper.setProps({ search: '' });
+ createComponent({ search: '' });
+ await waitForDebounce();
- await nextTick();
expect(wrapper.isVisible()).toBe(false);
});
- describe('with data', () => {
- let data;
-
- beforeEach(() => {
- data = { issues: [{ id: 1 }, { id: 2 }] };
- });
-
- 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);
-
- await nextTick();
- expect(wrapper.findAll('li').length).toBe(data.issues.length);
- });
+ it('does not render when loading', () => {
+ createComponent();
+ expect(wrapper.isVisible()).toBe(false);
+ });
- 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);
+ it('does not render with empty issues data', async () => {
+ const emptyIssuesResponse = {
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/1',
+ issues: {
+ edges: [],
+ },
+ },
+ },
+ };
- await nextTick();
- expect(wrapper.isVisible()).toBe(false);
- });
+ createComponent({ queryResponse: jest.fn().mockResolvedValue(emptyIssuesResponse) });
+ await waitForDebounce();
- 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({
- ...data,
- loading: 1,
- });
+ expect(wrapper.isVisible()).toBe(false);
+ });
- await nextTick();
- expect(wrapper.isVisible()).toBe(false);
+ describe('with data', () => {
+ beforeEach(async () => {
+ createComponent();
+ await waitForDebounce();
});
- 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: [] });
-
- await nextTick();
- expect(wrapper.isVisible()).toBe(false);
+ it('renders component', () => {
+ expect(wrapper.findAll('li').length).toBe(MOCK_ISSUES_COUNT);
});
- 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);
-
- await nextTick();
- expect(wrapper.findAllComponents(TitleSuggestionsItem).length).toBe(2);
+ it('renders list of issues', () => {
+ expect(wrapper.findAllComponents(TitleSuggestionsItem).length).toBe(MOCK_ISSUES_COUNT);
});
- 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);
-
- await nextTick();
+ it('adds margin class to first item', () => {
expect(wrapper.findAll('li').at(0).classes()).toContain('gl-mb-3');
});
- 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);
-
- await nextTick();
+ it('does not add margin class to last item', () => {
expect(wrapper.findAll('li').at(1).classes()).not.toContain('gl-mb-3');
});
});
diff --git a/spec/frontend/issues/new/components/type_popover_spec.js b/spec/frontend/issues/new/components/type_popover_spec.js
index fe3d5207516..1ae150797c3 100644
--- a/spec/frontend/issues/new/components/type_popover_spec.js
+++ b/spec/frontend/issues/new/components/type_popover_spec.js
@@ -8,10 +8,6 @@ describe('Issue type info popover', () => {
wrapper = shallowMount(TypePopover);
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders', () => {
createComponent();
diff --git a/spec/frontend/issues/new/mock_data.js b/spec/frontend/issues/new/mock_data.js
index 74b569d9833..0d2a388cd86 100644
--- a/spec/frontend/issues/new/mock_data.js
+++ b/spec/frontend/issues/new/mock_data.js
@@ -26,3 +26,67 @@ export default () => ({
webUrl: `${TEST_HOST}/author`,
},
});
+
+export const mockIssueSuggestionResponse = {
+ data: {
+ project: {
+ id: 'gid://gitlab/Project/278964',
+ issues: {
+ edges: [
+ {
+ node: {
+ id: 'gid://gitlab/Issue/123725957',
+ iid: '696',
+ title: 'Remove unused MR widget extension expand success, failed, warning events',
+ confidential: false,
+ userNotesCount: 16,
+ upvotes: 0,
+ webUrl: 'https://gitlab.com/gitlab-org/gitlab/-/issues/696',
+ state: 'opened',
+ closedAt: null,
+ createdAt: '2023-02-15T12:29:59Z',
+ updatedAt: '2023-03-01T19:38:22Z',
+ author: {
+ id: 'gid://gitlab/User/325',
+ name: 'User Name',
+ username: 'user-name',
+ avatarUrl: '/uploads/-/system/user/avatar/325/avatar.png',
+ webUrl: 'https://gitlab.com/user-name',
+ __typename: 'UserCore',
+ },
+ __typename: 'Issue',
+ },
+ __typename: 'IssueEdge',
+ },
+ {
+ node: {
+ id: 'gid://gitlab/Issue/123',
+ iid: '391',
+ title: 'Remove unused MR widget extension expand success, failed, warning events',
+ confidential: false,
+ userNotesCount: 16,
+ upvotes: 0,
+ webUrl: 'https://gitlab.com/gitlab-org/gitlab/-/issues/391',
+ state: 'opened',
+ closedAt: null,
+ createdAt: '2023-02-15T12:29:59Z',
+ updatedAt: '2023-03-01T19:38:22Z',
+ author: {
+ id: 'gid://gitlab/User/2080',
+ name: 'User Name',
+ username: 'user-name',
+ avatarUrl: '/uploads/-/system/user/avatar/2080/avatar.png',
+ webUrl: 'https://gitlab.com/user-name',
+ __typename: 'UserCore',
+ },
+ __typename: 'Issue',
+ },
+ __typename: 'IssueEdge',
+ },
+ ],
+ __typename: 'IssueConnection',
+ },
+ __typename: 'Project',
+ },
+ },
+};
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 010c719bd84..c5507c88fd7 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
@@ -34,7 +34,6 @@ describe('RelatedMergeRequests', () => {
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
});
diff --git a/spec/frontend/issues/related_merge_requests/store/actions_spec.js b/spec/frontend/issues/related_merge_requests/store/actions_spec.js
index 7339372a8d1..31c96265f8d 100644
--- a/spec/frontend/issues/related_merge_requests/store/actions_spec.js
+++ b/spec/frontend/issues/related_merge_requests/store/actions_spec.js
@@ -1,12 +1,12 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import * as actions from '~/issues/related_merge_requests/store/actions';
import * as types from '~/issues/related_merge_requests/store/mutation_types';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('RelatedMergeRequest store actions', () => {
let state;
diff --git a/spec/frontend/issues/show/components/app_spec.js b/spec/frontend/issues/show/components/app_spec.js
index 9fa0ce6f93d..1006f54eeaf 100644
--- a/spec/frontend/issues/show/components/app_spec.js
+++ b/spec/frontend/issues/show/components/app_spec.js
@@ -1,11 +1,10 @@
import { GlIcon, GlIntersectionObserver } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
-import { nextTick } from 'vue';
-import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
+import { setHTMLFixture } from 'helpers/fixtures';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import {
IssuableStatusText,
STATUS_CLOSED,
@@ -21,29 +20,27 @@ import FormComponent from '~/issues/show/components/form.vue';
import TitleComponent from '~/issues/show/components/title.vue';
import IncidentTabs from '~/issues/show/components/incidents/incident_tabs.vue';
import PinnedLinks from '~/issues/show/components/pinned_links.vue';
-import { POLLING_DELAY } from '~/issues/show/constants';
import eventHub from '~/issues/show/event_hub';
import axios from '~/lib/utils/axios_utils';
-import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
+import { HTTP_STATUS_OK, HTTP_STATUS_UNAUTHORIZED } from '~/lib/utils/http_status';
import { visitUrl } from '~/lib/utils/url_utility';
import {
appProps,
initialRequest,
publishedIncidentUrl,
+ putRequest,
secondRequest,
zoomMeetingUrl,
} from '../mock_data/mock_data';
-jest.mock('~/flash');
-jest.mock('~/issues/show/event_hub');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility');
jest.mock('~/behaviors/markdown/render_gfm');
const REALTIME_REQUEST_STACK = [initialRequest, secondRequest];
describe('Issuable output', () => {
- let mock;
- let realtimeRequestCount = 0;
+ let axiosMock;
let wrapper;
const findStickyHeader = () => wrapper.findByTestId('issue-sticky-header');
@@ -57,15 +54,14 @@ describe('Issuable output', () => {
const findForm = () => wrapper.findComponent(FormComponent);
const findPinnedLinks = () => wrapper.findComponent(PinnedLinks);
- const mountComponent = (props = {}, options = {}, data = {}) => {
+ const createComponent = ({ props = {}, options = {}, data = {} } = {}) => {
wrapper = shallowMountExtended(IssuableApp, {
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
propsData: { ...appProps, ...props },
provide: {
fullPath: 'gitlab-org/incidents',
- iid: '19',
uploadMetricsFeatureAvailable: false,
},
stubs: {
@@ -79,6 +75,28 @@ describe('Issuable output', () => {
},
...options,
});
+
+ jest.advanceTimersToNextTimer(2);
+ return waitForPromises();
+ };
+
+ const emitHubEvent = (event) => {
+ eventHub.$emit(event);
+ return waitForPromises();
+ };
+
+ const openForm = () => {
+ return emitHubEvent('open.form');
+ };
+
+ const updateIssuable = () => {
+ return emitHubEvent('update.issuable');
+ };
+
+ const advanceToNextPoll = () => {
+ // We get new data through the HTTP request.
+ jest.advanceTimersToNextTimer();
+ return waitForPromises();
};
beforeEach(() => {
@@ -98,79 +116,100 @@ describe('Issuable output', () => {
</div>
`);
- mock = new MockAdapter(axios);
- mock
- .onGet('/gitlab-org/gitlab-shell/-/issues/9/realtime_changes/realtime_changes')
- .reply(() => {
- const res = Promise.resolve([HTTP_STATUS_OK, REALTIME_REQUEST_STACK[realtimeRequestCount]]);
- realtimeRequestCount += 1;
- return res;
- });
+ jest.spyOn(eventHub, '$emit');
- mountComponent();
+ axiosMock = new MockAdapter(axios);
+ const endpoint = '/gitlab-org/gitlab-shell/-/issues/9/realtime_changes/realtime_changes';
- jest.advanceTimersByTime(2);
+ axiosMock.onGet(endpoint).replyOnce(HTTP_STATUS_OK, REALTIME_REQUEST_STACK[0], {
+ 'POLL-INTERVAL': '1',
+ });
+ axiosMock.onGet(endpoint).reply(HTTP_STATUS_OK, REALTIME_REQUEST_STACK[1], {
+ 'POLL-INTERVAL': '-1',
+ });
+ axiosMock.onPut().reply(HTTP_STATUS_OK, putRequest);
});
- afterEach(() => {
- mock.restore();
- realtimeRequestCount = 0;
- wrapper.vm.poll.stop();
- wrapper.destroy();
- resetHTMLFixture();
- });
+ describe('update', () => {
+ beforeEach(async () => {
+ await createComponent();
+ });
- it('should render a title/description/edited and update title/description/edited on update', () => {
- return axios
- .waitForAll()
- .then(() => {
- expect(findTitle().props('titleText')).toContain('this is a title');
- expect(findDescription().props('descriptionText')).toContain('this is a description');
-
- expect(findEdited().exists()).toBe(true);
- expect(findEdited().props('updatedByPath')).toMatch(/\/some_user$/);
- expect(findEdited().props('updatedAt')).toBe(initialRequest.updated_at);
- expect(wrapper.vm.state.lock_version).toBe(initialRequest.lock_version);
- })
- .then(() => {
- wrapper.vm.poll.makeRequest();
- return axios.waitForAll();
- })
- .then(() => {
- expect(findTitle().props('titleText')).toContain('2');
- expect(findDescription().props('descriptionText')).toContain('42');
-
- expect(findEdited().exists()).toBe(true);
- expect(findEdited().props('updatedByName')).toBe('Other User');
- expect(findEdited().props('updatedByPath')).toMatch(/\/other_user$/);
- expect(findEdited().props('updatedAt')).toBe(secondRequest.updated_at);
- });
- });
+ it('should render a title/description/edited and update title/description/edited on update', async () => {
+ expect(findTitle().props('titleText')).toContain(initialRequest.title_text);
+ expect(findDescription().props('descriptionText')).toContain('this is a description');
- it('shows actions if permissions are correct', async () => {
- wrapper.vm.showForm = true;
+ expect(findEdited().exists()).toBe(true);
+ expect(findEdited().props('updatedByPath')).toMatch(/\/some_user$/);
+ expect(findEdited().props('updatedAt')).toBe(initialRequest.updated_at);
+ expect(findDescription().props().lockVersion).toBe(initialRequest.lock_version);
- await nextTick();
- expect(findForm().exists()).toBe(true);
- });
+ await advanceToNextPoll();
- it('does not show actions if permissions are incorrect', async () => {
- wrapper.vm.showForm = true;
- wrapper.setProps({ canUpdate: false });
+ expect(findTitle().props('titleText')).toContain('2');
+ expect(findDescription().props('descriptionText')).toContain('42');
- await nextTick();
- expect(findForm().exists()).toBe(false);
+ expect(findEdited().exists()).toBe(true);
+ expect(findEdited().props('updatedByName')).toBe('Other User');
+ expect(findEdited().props('updatedByPath')).toMatch(/\/other_user$/);
+ expect(findEdited().props('updatedAt')).toBe(secondRequest.updated_at);
+ });
});
- it('does not update formState if form is already open', async () => {
- wrapper.vm.updateAndShowForm();
+ describe('with permissions', () => {
+ beforeEach(async () => {
+ await createComponent();
+ });
- wrapper.vm.state.titleText = 'testing 123';
+ it('shows actions on `open.form` event', async () => {
+ expect(findForm().exists()).toBe(false);
- wrapper.vm.updateAndShowForm();
+ await openForm();
- await nextTick();
- expect(wrapper.vm.store.formState.title).not.toBe('testing 123');
+ expect(findForm().exists()).toBe(true);
+ });
+
+ it('update formState if form is not open', async () => {
+ const titleValue = initialRequest.title_text;
+
+ expect(findTitle().exists()).toBe(true);
+ expect(findTitle().props('titleText')).toBe(titleValue);
+
+ await advanceToNextPoll();
+
+ // The title component has the new data, so the state was updated
+ expect(findTitle().exists()).toBe(true);
+ expect(findTitle().props('titleText')).toBe(secondRequest.title_text);
+ });
+
+ it('does not update formState if form is already open', async () => {
+ const titleValue = initialRequest.title_text;
+
+ expect(findTitle().exists()).toBe(true);
+ expect(findTitle().props('titleText')).toBe(titleValue);
+
+ await openForm();
+
+ // Opening the form, the data has not changed
+ expect(findForm().props().formState.title).toBe(titleValue);
+
+ await advanceToNextPoll();
+
+ // We expect the prop value not to have changed after another API call
+ expect(findForm().props().formState.title).toBe(titleValue);
+ });
+ });
+
+ describe('without permissions', () => {
+ beforeEach(async () => {
+ await createComponent({ props: { canUpdate: false } });
+ });
+
+ it('does not show actions if permissions are incorrect', async () => {
+ await openForm();
+
+ expect(findForm().exists()).toBe(false);
+ });
});
describe('Pinned links propagated', () => {
@@ -178,288 +217,130 @@ describe('Issuable output', () => {
prop | value
${'zoomMeetingUrl'} | ${zoomMeetingUrl}
${'publishedIncidentUrl'} | ${publishedIncidentUrl}
- `('sets the $prop correctly on underlying pinned links', ({ prop, value }) => {
+ `('sets the $prop correctly on underlying pinned links', async ({ prop, value }) => {
+ await createComponent();
+
expect(findPinnedLinks().props(prop)).toBe(value);
});
});
- describe('updateIssuable', () => {
+ describe('updating an issue', () => {
+ beforeEach(async () => {
+ await createComponent();
+ });
+
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 },
- });
+ await advanceToNextPoll();
- await wrapper.vm.updateIssuable();
- expect(updateStoreSpy).toHaveBeenCalled();
- expect(getDataSpy).toHaveBeenCalled();
+ await updateIssuable();
+
+ expect(axiosMock.history.put).toHaveLength(1);
+ // The call was made with the new data
+ expect(axiosMock.history.put[0].data.title).toEqual(findTitle().props().title);
});
- it('correctly updates issuable data', async () => {
- const spy = jest.spyOn(wrapper.vm.service, 'updateIssuable').mockResolvedValue({
- data: { web_url: window.location.pathname },
- });
+ it('closes the form after fetching data', async () => {
+ await updateIssuable();
- 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', async () => {
- jest.spyOn(wrapper.vm.service, 'updateIssuable').mockResolvedValue({
- data: {
- web_url: window.location.pathname,
- confidential: wrapper.vm.isConfidential,
- },
+ axiosMock.onPut().reply(HTTP_STATUS_OK, {
+ ...putRequest,
+ confidential: appProps.isConfidential,
});
- await wrapper.vm.updateIssuable();
+ await updateIssuable();
+
expect(visitUrl).not.toHaveBeenCalled();
});
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: '',
- confidential: wrapper.vm.isConfidential,
- },
+ axiosMock.onPut().reply(HTTP_STATUS_OK, {
+ ...putRequest,
+ web_url: '',
+ confidential: appProps.isConfidential,
});
- await wrapper.vm.updateIssuable();
+ await updateIssuable();
+
expect(visitUrl).not.toHaveBeenCalled();
});
it('redirects if returned web_url has changed', async () => {
- jest.spyOn(wrapper.vm.service, 'updateIssuable').mockResolvedValue({
- data: {
- web_url: '/testing-issue-move',
- confidential: wrapper.vm.isConfidential,
- },
- });
-
- wrapper.vm.updateIssuable();
-
- await wrapper.vm.updateIssuable();
- expect(visitUrl).toHaveBeenCalledWith('/testing-issue-move');
- });
-
- describe('shows dialog when issue has unsaved changed', () => {
- 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);
-
- await nextTick();
- expect(e.returnValue).not.toBeNull();
- });
-
- 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);
+ const webUrl = '/testing-issue-move';
- await nextTick();
- expect(e.returnValue).not.toBeNull();
+ axiosMock.onPut().reply(HTTP_STATUS_OK, {
+ ...putRequest,
+ web_url: webUrl,
+ confidential: appProps.isConfidential,
});
- it('does nothing when nothing has changed', async () => {
- const e = { returnValue: null };
- wrapper.vm.handleBeforeUnloadEvent(e);
+ await updateIssuable();
- await nextTick();
- expect(e.returnValue).toBeNull();
- });
+ expect(visitUrl).toHaveBeenCalledWith(webUrl);
});
describe('error when updating', () => {
- it('closes form on error', async () => {
- jest.spyOn(wrapper.vm.service, 'updateIssuable').mockRejectedValue();
+ it('closes form', async () => {
+ axiosMock.onPut().reply(HTTP_STATUS_UNAUTHORIZED);
- await wrapper.vm.updateIssuable();
- expect(eventHub.$emit).not.toHaveBeenCalledWith('close.form');
- expect(createAlert).toHaveBeenCalledWith({ message: `Error updating issue` });
- });
+ await updateIssuable();
- it('returns the correct error message for issuableType', async () => {
- jest.spyOn(wrapper.vm.service, 'updateIssuable').mockRejectedValue();
- wrapper.setProps({ issuableType: 'merge request' });
-
- await nextTick();
- await wrapper.vm.updateIssuable();
expect(eventHub.$emit).not.toHaveBeenCalledWith('close.form');
- expect(createAlert).toHaveBeenCalledWith({ message: `Error updating merge request` });
- });
-
- 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] } } });
-
- await wrapper.vm.updateIssuable();
expect(createAlert).toHaveBeenCalledWith({
- message: `${wrapper.vm.defaultErrorMessage}. ${msg}`,
+ message: `Error updating issue. Request failed with status code 401`,
});
});
- });
- });
-
- describe('updateAndShowForm', () => {
- 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);
- });
- });
-
- describe('requestTemplatesAndShowForm', () => {
- let formSpy;
-
- beforeEach(() => {
- formSpy = jest.spyOn(wrapper.vm, 'updateAndShowForm');
- });
-
- it('shows the form if template names as hash request is successful', () => {
- const mockData = {
- test: [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }],
- };
- mock
- .onGet('/issuable-templates-path')
- .reply(() => Promise.resolve([HTTP_STATUS_OK, mockData]));
-
- return wrapper.vm.requestTemplatesAndShowForm().then(() => {
- expect(formSpy).toHaveBeenCalledWith(mockData);
- });
- });
+ it('returns the correct error message for issuableType', async () => {
+ axiosMock.onPut().reply(HTTP_STATUS_UNAUTHORIZED);
- it('shows the form if template names as array request is successful', () => {
- const mockData = [{ name: 'test', id: 'test', project_path: '/', namespace_path: '/' }];
- mock
- .onGet('/issuable-templates-path')
- .reply(() => Promise.resolve([HTTP_STATUS_OK, mockData]));
+ await updateIssuable();
- return wrapper.vm.requestTemplatesAndShowForm().then(() => {
- expect(formSpy).toHaveBeenCalledWith(mockData);
- });
- });
-
- it('shows the form if template names request failed', () => {
- mock
- .onGet('/issuable-templates-path')
- .reply(() => Promise.reject(new Error('something went wrong')));
+ wrapper.setProps({ issuableType: 'merge request' });
- return wrapper.vm.requestTemplatesAndShowForm().then(() => {
- expect(createAlert).toHaveBeenCalledWith({ message: 'Error updating issue' });
+ await updateIssuable();
- expect(formSpy).toHaveBeenCalledWith();
+ expect(eventHub.$emit).not.toHaveBeenCalledWith('close.form');
+ expect(createAlert).toHaveBeenCalledWith({
+ message: `Error updating merge request. Request failed with status code 401`,
+ });
});
- });
- });
-
- describe('show inline edit button', () => {
- it('should render by default', () => {
- expect(findTitle().props('showInlineEditButton')).toBe(true);
- });
-
- it('should render if showInlineEditButton', async () => {
- wrapper.setProps({ showInlineEditButton: true });
-
- await nextTick();
- expect(findTitle().props('showInlineEditButton')).toBe(true);
- });
- it('should not render if showInlineEditButton is false', async () => {
- wrapper.setProps({ showInlineEditButton: false });
-
- await nextTick();
- expect(findTitle().props('showInlineEditButton')).toBe(false);
- });
- });
-
- describe('updateStoreState', () => {
- it('should make a request and update the state of the store', () => {
- const data = { foo: 1 };
- const getDataSpy = jest.spyOn(wrapper.vm.service, 'getData').mockResolvedValue({ data });
- const updateStateSpy = jest
- .spyOn(wrapper.vm.store, 'updateState')
- .mockImplementation(jest.fn);
-
- return wrapper.vm.updateStoreState().then(() => {
- expect(getDataSpy).toHaveBeenCalled();
- expect(updateStateSpy).toHaveBeenCalledWith(data);
- });
- });
+ it('shows error message from backend if exists', async () => {
+ const msg = 'Custom error message from backend';
+ axiosMock.onPut().reply(HTTP_STATUS_UNAUTHORIZED, { errors: [msg] });
- it('should show error message if store update fails', () => {
- jest.spyOn(wrapper.vm.service, 'getData').mockRejectedValue();
- wrapper.setProps({ issuableType: 'merge request' });
+ await updateIssuable();
- return wrapper.vm.updateStoreState().then(() => {
expect(createAlert).toHaveBeenCalledWith({
- message: `Error updating ${wrapper.vm.issuableType}`,
+ message: `Error updating issue. ${msg}`,
});
});
});
});
- describe('issueChanged', () => {
- beforeEach(() => {
- wrapper.vm.store.formState.title = '';
- wrapper.vm.store.formState.description = '';
- wrapper.setProps({
- initialDescriptionText: '',
- initialTitleText: '',
- });
- });
-
- it('returns true when title is changed', () => {
- wrapper.vm.store.formState.title = 'RandomText';
-
- expect(wrapper.vm.issueChanged).toBe(true);
- });
-
- it('returns false when title is empty null', () => {
- wrapper.vm.store.formState.title = null;
-
- expect(wrapper.vm.issueChanged).toBe(false);
- });
-
- it('returns true when description is changed', () => {
- wrapper.vm.store.formState.description = 'RandomText';
-
- expect(wrapper.vm.issueChanged).toBe(true);
+ describe('Locked warning', () => {
+ beforeEach(async () => {
+ await createComponent();
});
- it('returns false when description is empty null', () => {
- wrapper.vm.store.formState.description = null;
-
- expect(wrapper.vm.issueChanged).toBe(false);
- });
-
- it('returns false when `initialDescriptionText` is null and `formState.description` is empty string', () => {
- wrapper.vm.store.formState.description = '';
- wrapper.setProps({ initialDescriptionText: null });
+ it('shows locked warning if form is open & data is different', async () => {
+ await openForm();
+ await advanceToNextPoll();
- expect(wrapper.vm.issueChanged).toBe(false);
+ expect(findForm().props().formState.lockedWarningVisible).toBe(true);
+ expect(findForm().props().formState.lock_version).toBe(1);
});
});
describe('sticky header', () => {
+ beforeEach(async () => {
+ await createComponent();
+ });
+
describe('when title is in view', () => {
it('is not shown', () => {
expect(findStickyHeader().exists()).toBe(false);
@@ -467,21 +348,18 @@ describe('Issuable output', () => {
});
describe('when title is not in view', () => {
- beforeEach(() => {
- wrapper.vm.state.titleText = 'Sticky header title';
+ beforeEach(async () => {
wrapper.findComponent(GlIntersectionObserver).vm.$emit('disappear');
});
it('shows with title', () => {
- expect(findStickyHeader().text()).toContain('Sticky header title');
+ expect(findStickyHeader().text()).toContain(initialRequest.title_text);
});
it('shows with title for an epic', async () => {
- wrapper.setProps({ issuableType: 'epic' });
-
- await nextTick();
+ await wrapper.setProps({ issuableType: 'epic' });
- expect(findStickyHeader().text()).toContain('Sticky header title');
+ expect(findStickyHeader().text()).toContain(' this is a title');
});
it.each`
@@ -493,9 +371,7 @@ describe('Issuable output', () => {
`(
'shows with state icon "$statusIcon" for $issuableType when status is $issuableStatus',
async ({ issuableType, issuableStatus, statusIcon }) => {
- wrapper.setProps({ issuableType, issuableStatus });
-
- await nextTick();
+ await wrapper.setProps({ issuableType, issuableStatus });
expect(findStickyHeader().findComponent(GlIcon).props('name')).toBe(statusIcon);
},
@@ -507,9 +383,7 @@ describe('Issuable output', () => {
${'shows with Closed when status is closed'} | ${STATUS_CLOSED}
${'shows with Open when status is reopened'} | ${STATUS_REOPENED}
`('$title', async ({ state }) => {
- wrapper.setProps({ issuableStatus: state });
-
- await nextTick();
+ await wrapper.setProps({ issuableStatus: state });
expect(findStickyHeader().text()).toContain(IssuableStatusText[state]);
});
@@ -519,9 +393,7 @@ describe('Issuable output', () => {
${'does not show confidential badge when issue is not confidential'} | ${false}
${'shows confidential badge when issue is confidential'} | ${true}
`('$title', async ({ isConfidential }) => {
- wrapper.setProps({ isConfidential });
-
- await nextTick();
+ await wrapper.setProps({ isConfidential });
const confidentialEl = findConfidentialBadge();
expect(confidentialEl.exists()).toBe(isConfidential);
@@ -538,9 +410,7 @@ describe('Issuable output', () => {
${'does not show locked badge when issue is not locked'} | ${false}
${'shows locked badge when issue is locked'} | ${true}
`('$title', async ({ isLocked }) => {
- wrapper.setProps({ isLocked });
-
- await nextTick();
+ await wrapper.setProps({ isLocked });
expect(findLockedBadge().exists()).toBe(isLocked);
});
@@ -550,9 +420,7 @@ describe('Issuable output', () => {
${'does not show hidden badge when issue is not hidden'} | ${false}
${'shows hidden badge when issue is hidden'} | ${true}
`('$title', async ({ isHidden }) => {
- wrapper.setProps({ isHidden });
-
- await nextTick();
+ await wrapper.setProps({ isHidden });
const hiddenBadge = findHiddenBadge();
@@ -569,6 +437,10 @@ describe('Issuable output', () => {
});
describe('Composable description component', () => {
+ beforeEach(async () => {
+ await createComponent();
+ });
+
const findIncidentTabs = () => wrapper.findComponent(IncidentTabs);
const borderClass = 'gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid gl-mb-6';
@@ -587,13 +459,13 @@ describe('Issuable output', () => {
});
describe('when using incident tabs description wrapper', () => {
- beforeEach(() => {
- mountComponent(
- {
+ beforeEach(async () => {
+ await createComponent({
+ props: {
descriptionComponent: IncidentTabs,
showTitleBorder: false,
},
- {
+ options: {
mocks: {
$apollo: {
queries: {
@@ -604,7 +476,7 @@ describe('Issuable output', () => {
},
},
},
- );
+ });
});
it('does not the description component', () => {
@@ -622,48 +494,77 @@ describe('Issuable output', () => {
});
describe('taskListUpdateStarted', () => {
- it('stops polling', () => {
- jest.spyOn(wrapper.vm.poll, 'stop');
+ beforeEach(async () => {
+ await createComponent();
+ });
+
+ it('stops polling', async () => {
+ expect(findTitle().props().titleText).toBe(initialRequest.title_text);
+
+ findDescription().vm.$emit('taskListUpdateStarted');
- wrapper.vm.taskListUpdateStarted();
+ await advanceToNextPoll();
- expect(wrapper.vm.poll.stop).toHaveBeenCalled();
+ expect(findTitle().props().titleText).toBe(initialRequest.title_text);
});
});
describe('taskListUpdateSucceeded', () => {
- it('enables polling', () => {
- jest.spyOn(wrapper.vm.poll, 'enable');
- jest.spyOn(wrapper.vm.poll, 'makeDelayedRequest');
+ beforeEach(async () => {
+ await createComponent();
+ findDescription().vm.$emit('taskListUpdateStarted');
+ });
+
+ it('enables polling', async () => {
+ // Ensure that polling is not working before
+ expect(findTitle().props().titleText).toBe(initialRequest.title_text);
+ await advanceToNextPoll();
+
+ expect(findTitle().props().titleText).toBe(initialRequest.title_text);
- wrapper.vm.taskListUpdateSucceeded();
+ // Enable Polling an move forward
+ findDescription().vm.$emit('taskListUpdateSucceeded');
+ await advanceToNextPoll();
- expect(wrapper.vm.poll.enable).toHaveBeenCalled();
- expect(wrapper.vm.poll.makeDelayedRequest).toHaveBeenCalledWith(POLLING_DELAY);
+ // Title has changed: polling works!
+ expect(findTitle().props().titleText).toBe(secondRequest.title_text);
});
});
describe('taskListUpdateFailed', () => {
- it('enables polling and calls updateStoreState', () => {
- jest.spyOn(wrapper.vm.poll, 'enable');
- jest.spyOn(wrapper.vm.poll, 'makeDelayedRequest');
- jest.spyOn(wrapper.vm, 'updateStoreState');
+ beforeEach(async () => {
+ await createComponent();
+ findDescription().vm.$emit('taskListUpdateStarted');
+ });
+
+ it('enables polling and calls updateStoreState', async () => {
+ // Ensure that polling is not working before
+ expect(findTitle().props().titleText).toBe(initialRequest.title_text);
+ await advanceToNextPoll();
- wrapper.vm.taskListUpdateFailed();
+ expect(findTitle().props().titleText).toBe(initialRequest.title_text);
- expect(wrapper.vm.poll.enable).toHaveBeenCalled();
- expect(wrapper.vm.poll.makeDelayedRequest).toHaveBeenCalledWith(POLLING_DELAY);
- expect(wrapper.vm.updateStoreState).toHaveBeenCalled();
+ // Enable Polling an move forward
+ findDescription().vm.$emit('taskListUpdateFailed');
+ await advanceToNextPoll();
+
+ // Title has changed: polling works!
+ expect(findTitle().props().titleText).toBe(secondRequest.title_text);
});
});
describe('saveDescription event', () => {
+ beforeEach(async () => {
+ await createComponent();
+ });
+
it('makes request to update issue', async () => {
const description = 'I have been updated!';
findDescription().vm.$emit('saveDescription', description);
+
await waitForPromises();
- expect(mock.history.put[0].data).toContain(description);
+ expect(axiosMock.history.put[0].data).toContain(description);
});
});
});
diff --git a/spec/frontend/issues/show/components/delete_issue_modal_spec.js b/spec/frontend/issues/show/components/delete_issue_modal_spec.js
index 97a091a1748..b8adeb24005 100644
--- a/spec/frontend/issues/show/components/delete_issue_modal_spec.js
+++ b/spec/frontend/issues/show/components/delete_issue_modal_spec.js
@@ -20,10 +20,6 @@ describe('DeleteIssueModal component', () => {
const mountComponent = (props = {}) =>
shallowMount(DeleteIssueModal, { propsData: { ...defaultProps, ...props } });
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('modal', () => {
it('renders', () => {
wrapper = mountComponent();
diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js
index da51372dd3d..740b2f782e4 100644
--- a/spec/frontend/issues/show/components/description_spec.js
+++ b/spec/frontend/issues/show/components/description_spec.js
@@ -1,25 +1,19 @@
import $ from 'jquery';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-import { GlModal } from '@gitlab/ui';
import getIssueDetailsQuery from 'ee_else_ce/work_items/graphql/get_issue_details.query.graphql';
import setWindowLocation from 'helpers/set_window_location_helper';
-import { stubComponent } from 'helpers/stub_component';
import { TEST_HOST } from 'helpers/test_constants';
-import { mockTracking } from 'helpers/tracking_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import Description from '~/issues/show/components/description.vue';
import eventHub from '~/issues/show/event_hub';
-import { updateHistory } from '~/lib/utils/url_utility';
import workItemQuery from '~/work_items/graphql/work_item.query.graphql';
import createWorkItemMutation from '~/work_items/graphql/create_work_item.mutation.graphql';
import workItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
import TaskList from '~/task_list';
-import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue';
-import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
import { renderGFM } from '~/behaviors/markdown/render_gfm';
import {
createWorkItemMutationErrorResponse,
@@ -27,14 +21,9 @@ import {
getIssueDetailsResponse,
projectWorkItemTypesQueryResponse,
} from 'jest/work_items/mock_data';
-import {
- descriptionProps as initialProps,
- descriptionHtmlWithList,
- descriptionHtmlWithCheckboxes,
- descriptionHtmlWithTask,
-} from '../mock_data/mock_data';
+import { descriptionProps as initialProps, descriptionHtmlWithList } from '../mock_data/mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
updateHistory: jest.fn(),
@@ -43,9 +32,6 @@ jest.mock('~/task_list');
jest.mock('~/behaviors/markdown/render_gfm');
const mockSpriteIcons = '/icons.svg';
-const showModal = jest.fn();
-const hideModal = jest.fn();
-const showDetailsModal = jest.fn();
const $toast = {
show: jest.fn(),
};
@@ -62,7 +48,6 @@ const workItemTypesQueryHandler = jest.fn().mockResolvedValue(projectWorkItemTyp
describe('Description component', () => {
let wrapper;
- let originalGon;
Vue.use(VueApollo);
@@ -70,21 +55,16 @@ describe('Description component', () => {
const findTextarea = () => wrapper.find('[data-testid="textarea"]');
const findListItems = () => findGfmContent().findAll('ul > li');
const findTaskActionButtons = () => wrapper.findAll('.task-list-item-actions');
- const findTaskLink = () => wrapper.find('a.gfm-issue');
- const findModal = () => wrapper.findComponent(GlModal);
- const findWorkItemDetailModal = () => wrapper.findComponent(WorkItemDetailModal);
function createComponent({
props = {},
provide,
issueDetailsQueryHandler = jest.fn().mockResolvedValue(issueDetailsResponse),
createWorkItemMutationHandler,
- ...options
} = {}) {
wrapper = shallowMountExtended(Description, {
propsData: {
issueId: 1,
- issueIid: 1,
...initialProps,
...props,
},
@@ -102,25 +82,10 @@ describe('Description component', () => {
mocks: {
$toast,
},
- stubs: {
- GlModal: stubComponent(GlModal, {
- methods: {
- show: showModal,
- hide: hideModal,
- },
- }),
- WorkItemDetailModal: stubComponent(WorkItemDetailModal, {
- methods: {
- show: showDetailsModal,
- },
- }),
- },
- ...options,
});
}
beforeEach(() => {
- originalGon = window.gon;
window.gon = { sprite_icons: mockSpriteIcons };
setWindowLocation(TEST_HOST);
@@ -136,8 +101,6 @@ describe('Description component', () => {
});
afterAll(() => {
- window.gon = originalGon;
-
$('.issuable-meta .flash-container').remove();
});
@@ -285,7 +248,6 @@ describe('Description component', () => {
props: {
descriptionHtml: descriptionHtmlWithList,
},
- attachTo: document.body,
});
await nextTick();
});
@@ -325,33 +287,6 @@ describe('Description component', () => {
});
});
- describe('description with checkboxes', () => {
- beforeEach(() => {
- createComponent({
- props: {
- descriptionHtml: descriptionHtmlWithCheckboxes,
- },
- });
- return nextTick();
- });
-
- it('renders a list of hidden buttons corresponding to checkboxes in description HTML', () => {
- expect(findTaskActionButtons()).toHaveLength(3);
- });
-
- it('does not show a modal by default', () => {
- expect(findModal().exists()).toBe(false);
- });
-
- it('shows toast after delete success', async () => {
- const newDesc = 'description';
- findWorkItemDetailModal().vm.$emit('workItemDeleted', newDesc);
-
- expect(wrapper.emitted('updateDescription')).toEqual([[newDesc]]);
- expect($toast.show).toHaveBeenCalledWith('Task deleted');
- });
- });
-
describe('task list item actions', () => {
describe('converting the task list item to a task', () => {
describe('when successful', () => {
@@ -391,11 +326,7 @@ describe('Description component', () => {
});
it('calls a mutation to create a task', () => {
- const {
- confidential,
- iteration,
- milestone,
- } = issueDetailsResponse.data.workspace.issuable;
+ const { confidential, iteration, milestone } = issueDetailsResponse.data.issue;
expect(createWorkItemMutationHandler).toHaveBeenCalledWith({
input: {
confidential,
@@ -468,109 +399,4 @@ describe('Description component', () => {
});
});
});
-
- describe('work items detail', () => {
- describe('when opening and closing', () => {
- beforeEach(() => {
- createComponent({
- props: {
- descriptionHtml: descriptionHtmlWithTask,
- },
- });
- return nextTick();
- });
-
- it('opens when task button is clicked', async () => {
- await findTaskLink().trigger('click');
-
- expect(showDetailsModal).toHaveBeenCalled();
- expect(updateHistory).toHaveBeenCalledWith({
- url: `${TEST_HOST}/?work_item_id=2`,
- replace: true,
- });
- });
-
- it('closes from an open state', async () => {
- await findTaskLink().trigger('click');
-
- findWorkItemDetailModal().vm.$emit('close');
- await nextTick();
-
- expect(updateHistory).toHaveBeenLastCalledWith({
- url: `${TEST_HOST}/`,
- replace: true,
- });
- });
-
- it('tracks when opened', async () => {
- const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
-
- await findTaskLink().trigger('click');
-
- expect(trackingSpy).toHaveBeenCalledWith(
- TRACKING_CATEGORY_SHOW,
- 'viewed_work_item_from_modal',
- {
- category: TRACKING_CATEGORY_SHOW,
- label: 'work_item_view',
- property: 'type_task',
- },
- );
- });
- });
-
- describe('when url query `work_item_id` exists', () => {
- it.each`
- behavior | workItemId | modalOpened
- ${'opens'} | ${'2'} | ${1}
- ${'does not open'} | ${'123'} | ${0}
- ${'does not open'} | ${'123e'} | ${0}
- ${'does not open'} | ${'12e3'} | ${0}
- ${'does not open'} | ${'1e23'} | ${0}
- ${'does not open'} | ${'x'} | ${0}
- ${'does not open'} | ${'undefined'} | ${0}
- `(
- '$behavior when url contains `work_item_id=$workItemId`',
- async ({ workItemId, modalOpened }) => {
- setWindowLocation(`?work_item_id=${workItemId}`);
-
- createComponent({
- props: { descriptionHtml: descriptionHtmlWithTask },
- });
-
- expect(showDetailsModal).toHaveBeenCalledTimes(modalOpened);
- },
- );
- });
- });
-
- describe('when hovering task links', () => {
- beforeEach(() => {
- createComponent({
- props: {
- descriptionHtml: descriptionHtmlWithTask,
- },
- });
- return nextTick();
- });
-
- it('prefetches work item detail after work item link is hovered for 150ms', async () => {
- await findTaskLink().trigger('mouseover');
- jest.advanceTimersByTime(150);
- await waitForPromises();
-
- expect(queryHandler).toHaveBeenCalledWith({
- id: 'gid://gitlab/WorkItem/2',
- });
- });
-
- it('does not work item detail after work item link is hovered for less than 150ms', async () => {
- await findTaskLink().trigger('mouseover');
- await findTaskLink().trigger('mouseout');
- jest.advanceTimersByTime(150);
- await waitForPromises();
-
- expect(queryHandler).not.toHaveBeenCalled();
- });
- });
});
diff --git a/spec/frontend/issues/show/components/edit_actions_spec.js b/spec/frontend/issues/show/components/edit_actions_spec.js
index 11c43ea4388..ca561149806 100644
--- a/spec/frontend/issues/show/components/edit_actions_spec.js
+++ b/spec/frontend/issues/show/components/edit_actions_spec.js
@@ -56,10 +56,6 @@ describe('Edit Actions component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders all buttons as enabled', () => {
const buttons = findEditButtons().wrappers;
buttons.forEach((button) => {
diff --git a/spec/frontend/issues/show/components/edited_spec.js b/spec/frontend/issues/show/components/edited_spec.js
index aa6e0a9dceb..a509627c347 100644
--- a/spec/frontend/issues/show/components/edited_spec.js
+++ b/spec/frontend/issues/show/components/edited_spec.js
@@ -15,10 +15,6 @@ describe('Edited component', () => {
const mountComponent = (propsData) => mount(Edited, { propsData });
const updatedAt = '2017-05-15T12:31:04.428Z';
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders an edited at+by string', () => {
wrapper = mountComponent({
updatedAt,
diff --git a/spec/frontend/issues/show/components/fields/description_spec.js b/spec/frontend/issues/show/components/fields/description_spec.js
index 273ddfdd5d4..5c145ed4707 100644
--- a/spec/frontend/issues/show/components/fields/description_spec.js
+++ b/spec/frontend/issues/show/components/fields/description_spec.js
@@ -33,11 +33,6 @@ describe('Description field component', () => {
jest.spyOn(eventHub, '$emit');
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders markdown field with description', () => {
wrapper = mountComponent();
@@ -80,17 +75,18 @@ describe('Description field component', () => {
});
it('uses the MarkdownEditor component to edit markdown', () => {
- expect(findMarkdownEditor().props()).toEqual(
- expect.objectContaining({
- value: 'test',
- renderMarkdownPath: '/',
- markdownDocsPath: '/',
- quickActionsDocsPath: expect.any(String),
- autofocus: true,
- supportsQuickActions: true,
- enableAutocomplete: true,
- }),
- );
+ expect(findMarkdownEditor().props()).toMatchObject({
+ value: 'test',
+ renderMarkdownPath: '/',
+ autofocus: true,
+ supportsQuickActions: true,
+ quickActionsDocsPath: expect.any(String),
+ });
+
+ expect(findMarkdownEditor().vm.$attrs).toMatchObject({
+ 'enable-autocomplete': true,
+ 'markdown-docs-path': '/',
+ });
});
it('triggers update with meta+enter', () => {
diff --git a/spec/frontend/issues/show/components/fields/description_template_spec.js b/spec/frontend/issues/show/components/fields/description_template_spec.js
index 79a3bfa9840..1e8d5e2dd95 100644
--- a/spec/frontend/issues/show/components/fields/description_template_spec.js
+++ b/spec/frontend/issues/show/components/fields/description_template_spec.js
@@ -22,10 +22,6 @@ describe('Issue description template component with templates as hash', () => {
wrapper = shallowMount(descriptionTemplate, options);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders templates as JSON hash in data attribute', () => {
createComponent();
expect(findIssuableSelector().attributes('data-data')).toBe(
diff --git a/spec/frontend/issues/show/components/fields/title_spec.js b/spec/frontend/issues/show/components/fields/title_spec.js
index a5fa96d8d64..b28762f1520 100644
--- a/spec/frontend/issues/show/components/fields/title_spec.js
+++ b/spec/frontend/issues/show/components/fields/title_spec.js
@@ -17,11 +17,6 @@ describe('Title field component', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders form control with formState title', () => {
expect(findInput().element.value).toBe('test');
});
diff --git a/spec/frontend/issues/show/components/fields/type_spec.js b/spec/frontend/issues/show/components/fields/type_spec.js
index 27ac0e1baf3..e655cf3b37d 100644
--- a/spec/frontend/issues/show/components/fields/type_spec.js
+++ b/spec/frontend/issues/show/components/fields/type_spec.js
@@ -1,4 +1,4 @@
-import { GlFormGroup, GlListbox, GlIcon } from '@gitlab/ui';
+import { GlFormGroup, GlCollapsibleListbox, GlIcon } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
@@ -32,7 +32,7 @@ describe('Issue type field component', () => {
},
};
- const findListBox = () => wrapper.findComponent(GlListbox);
+ const findListBox = () => wrapper.findComponent(GlCollapsibleListbox);
const findFormGroup = () => wrapper.findComponent(GlFormGroup);
const findAllIssueItems = () => wrapper.findAll('[data-testid="issue-type-list-item"]');
const findIssueItemAt = (at) => findAllIssueItems().at(at);
@@ -60,10 +60,6 @@ describe('Issue type field component', () => {
mockIssueStateData = jest.fn();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
at | text | icon
${0} | ${issuableTypes[0].text} | ${issuableTypes[0].icon}
diff --git a/spec/frontend/issues/show/components/form_spec.js b/spec/frontend/issues/show/components/form_spec.js
index aedb974cbd0..b8ed33801f2 100644
--- a/spec/frontend/issues/show/components/form_spec.js
+++ b/spec/frontend/issues/show/components/form_spec.js
@@ -30,10 +30,6 @@ describe('Inline edit form component', () => {
projectNamespace: '/',
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const createComponent = (props) => {
wrapper = shallowMount(formComponent, {
propsData: {
diff --git a/spec/frontend/issues/show/components/header_actions_spec.js b/spec/frontend/issues/show/components/header_actions_spec.js
index 3d9dad3a721..58ec7387851 100644
--- a/spec/frontend/issues/show/components/header_actions_spec.js
+++ b/spec/frontend/issues/show/components/header_actions_spec.js
@@ -1,20 +1,22 @@
import Vue, { nextTick } from 'vue';
-import { GlButton, GlDropdownItem, GlLink, GlModal } from '@gitlab/ui';
+import { GlDropdownItem, GlLink, GlModal, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import { mockTracking } from 'helpers/tracking_helper';
-import { createAlert, VARIANT_SUCCESS } from '~/flash';
-import { IssueType, STATUS_CLOSED, STATUS_OPEN } from '~/issues/constants';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
+import { STATUS_CLOSED, STATUS_OPEN, TYPE_INCIDENT, TYPE_ISSUE } from '~/issues/constants';
import DeleteIssueModal from '~/issues/show/components/delete_issue_modal.vue';
import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue';
import HeaderActions from '~/issues/show/components/header_actions.vue';
import { ISSUE_STATE_EVENT_CLOSE, ISSUE_STATE_EVENT_REOPEN } from '~/issues/show/constants';
+import issuesEventHub from '~/issues/show/event_hub';
import promoteToEpicMutation from '~/issues/show/queries/promote_to_epic.mutation.graphql';
import * as urlUtility from '~/lib/utils/url_utility';
import eventHub from '~/notes/event_hub';
import createStore from '~/notes/stores';
-jest.mock('~/flash');
+jest.mock('~/alert');
+jest.mock('~/issues/show/event_hub', () => ({ $emit: jest.fn() }));
describe('HeaderActions component', () => {
let dispatchEventSpy;
@@ -36,7 +38,7 @@ describe('HeaderActions component', () => {
iid: '32',
isIssueAuthor: true,
issuePath: 'gitlab-org/gitlab-test/-/issues/1',
- issueType: IssueType.Issue,
+ issueType: TYPE_ISSUE,
newIssuePath: 'gitlab-org/gitlab-test/-/issues/new',
projectPath: 'gitlab-org/gitlab-test',
reportAbusePath: '-/abuse_reports/add_category',
@@ -67,7 +69,8 @@ describe('HeaderActions component', () => {
},
};
- const findToggleIssueStateButton = () => wrapper.findComponent(GlButton);
+ const findToggleIssueStateButton = () => wrapper.find(`[data-testid="toggle-button"]`);
+ const findEditButton = () => wrapper.find(`[data-testid="edit-button"]`);
const findDropdownBy = (dataTestId) => wrapper.find(`[data-testid="${dataTestId}"]`);
const findMobileDropdown = () => findDropdownBy('mobile-dropdown');
@@ -103,6 +106,9 @@ describe('HeaderActions component', () => {
mutate: mutateMock,
},
},
+ stubs: {
+ GlButton,
+ },
});
};
@@ -113,13 +119,12 @@ describe('HeaderActions component', () => {
if (visitUrlSpy) {
visitUrlSpy.mockRestore();
}
- wrapper.destroy();
});
describe.each`
issueType
- ${IssueType.Issue}
- ${IssueType.Incident}
+ ${TYPE_ISSUE}
+ ${TYPE_INCIDENT}
`('when issue type is $issueType', ({ issueType }) => {
describe('close/reopen button', () => {
describe.each`
@@ -240,6 +245,30 @@ describe('HeaderActions component', () => {
});
});
});
+
+ describe(`show edit button ${issueType}`, () => {
+ beforeEach(() => {
+ wrapper = mountComponent({
+ props: {
+ canUpdateIssue: true,
+ canCreateIssue: false,
+ isIssueAuthor: true,
+ issueType,
+ canReportSpam: false,
+ canPromoteToEpic: false,
+ },
+ });
+ });
+ it(`shows the edit button`, () => {
+ expect(findEditButton().exists()).toBe(true);
+ });
+
+ it('should trigger "open.form" event when clicked', async () => {
+ expect(issuesEventHub.$emit).not.toHaveBeenCalled();
+ await findEditButton().trigger('click');
+ expect(issuesEventHub.$emit).toHaveBeenCalledWith('open.form');
+ });
+ });
});
describe('delete issue button', () => {
diff --git a/spec/frontend/issues/show/components/incidents/create_timeline_events_form_spec.js b/spec/frontend/issues/show/components/incidents/create_timeline_events_form_spec.js
index 6c923cae0cc..6b68e7a0da6 100644
--- a/spec/frontend/issues/show/components/incidents/create_timeline_events_form_spec.js
+++ b/spec/frontend/issues/show/components/incidents/create_timeline_events_form_spec.js
@@ -9,7 +9,7 @@ import createTimelineEventMutation from '~/issues/show/components/incidents/grap
import getTimelineEvents from '~/issues/show/components/incidents/graphql/queries/get_timeline_events.query.graphql';
import { timelineFormI18n } from '~/issues/show/components/incidents/constants';
import createMockApollo from 'helpers/mock_apollo_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { useFakeDate } from 'helpers/fake_date';
import {
timelineEventsCreateEventResponse,
@@ -19,7 +19,7 @@ import {
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
const fakeDate = '2020-07-08T00:00:00.000Z';
@@ -99,7 +99,6 @@ describe('Create Timeline events', () => {
afterEach(() => {
createAlert.mockReset();
- wrapper.destroy();
});
describe('createIncidentTimelineEvent', () => {
diff --git a/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js b/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js
index 33a3a6eddfc..0f4fb02a40b 100644
--- a/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js
+++ b/spec/frontend/issues/show/components/incidents/incident_tabs_spec.js
@@ -1,4 +1,5 @@
import merge from 'lodash/merge';
+import { nextTick } from 'vue';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { trackIncidentDetailsViewsOptions } from '~/incidents/constants';
import DescriptionComponent from '~/issues/show/components/description.vue';
@@ -11,6 +12,11 @@ import Tracking from '~/tracking';
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
import { descriptionProps } from '../../mock_data/mock_data';
+const push = jest.fn();
+const $router = {
+ push,
+};
+
const mockAlert = {
__typename: 'AlertManagementAlert',
detailsUrl: INVALID_URL,
@@ -28,12 +34,20 @@ const defaultMocks = {
},
},
},
+ $route: { params: {} },
+ $router,
};
describe('Incident Tabs component', () => {
let wrapper;
- const mountComponent = ({ data = {}, options = {}, mount = shallowMountExtended } = {}) => {
+ const mountComponent = ({
+ data = {},
+ options = {},
+ mount = shallowMountExtended,
+ hasLinkedAlerts = false,
+ mocks = {},
+ } = {}) => {
wrapper = mount(
IncidentTabs,
merge(
@@ -54,11 +68,12 @@ describe('Incident Tabs component', () => {
slaFeatureAvailable: true,
canUpdate: true,
canUpdateTimelineEvent: true,
+ hasLinkedAlerts,
},
data() {
return { alert: mockAlert, ...data };
},
- mocks: defaultMocks,
+ mocks: { ...defaultMocks, ...mocks },
},
options,
),
@@ -102,11 +117,13 @@ describe('Incident Tabs component', () => {
});
it('renders the alert details tab', () => {
+ mountComponent({ hasLinkedAlerts: true });
expect(findAlertDetailsTab().exists()).toBe(true);
expect(findAlertDetailsTab().attributes('title')).toBe('Alert details');
});
it('renders the alert details table with the correct props', () => {
+ mountComponent({ hasLinkedAlerts: true });
const alert = { iid: mockAlert.iid };
expect(findAlertDetailsComponent().props('alert')).toMatchObject(alert);
@@ -156,6 +173,40 @@ describe('Incident Tabs component', () => {
expect(findActiveTabs()).toHaveLength(1);
expect(findActiveTabs().at(0).text()).toBe(incidentTabsI18n.timelineTitle);
+ expect(push).toHaveBeenCalledWith('/timeline');
+ });
+ });
+
+ describe('loading page with tab', () => {
+ it('shows the timeline tab when timeline path is passed', async () => {
+ mountComponent({
+ mount: mountExtended,
+ mocks: { $route: { params: { tabId: 'timeline' } } },
+ });
+ await nextTick();
+ expect(findActiveTabs()).toHaveLength(1);
+ expect(findActiveTabs().at(0).text()).toBe(incidentTabsI18n.timelineTitle);
+ });
+
+ it('shows the alerts tab when timeline path is passed', async () => {
+ mountComponent({
+ mount: mountExtended,
+ mocks: { $route: { params: { tabId: 'alerts' } } },
+ hasLinkedAlerts: true,
+ });
+ await nextTick();
+ expect(findActiveTabs()).toHaveLength(1);
+ expect(findActiveTabs().at(0).text()).toBe(incidentTabsI18n.alertsTitle);
+ });
+
+ it('shows the metrics tab when metrics path is passed', async () => {
+ mountComponent({
+ mount: mountExtended,
+ mocks: { $route: { params: { tabId: 'metrics' } } },
+ });
+ await nextTick();
+ expect(findActiveTabs()).toHaveLength(1);
+ expect(findActiveTabs().at(0).text()).toBe(incidentTabsI18n.metricsTitle);
});
});
});
diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js
index e352f9708e4..af01fd34336 100644
--- a/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js
+++ b/spec/frontend/issues/show/components/incidents/timeline_events_form_spec.js
@@ -10,12 +10,12 @@ import {
TIMELINE_EVENT_TAGS,
timelineEventTagsI18n,
} from '~/issues/show/components/incidents/constants';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { useFakeDate } from 'helpers/fake_date';
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
const fakeDate = '2020-07-08T00:00:00.000Z';
@@ -51,7 +51,6 @@ describe('Timeline events form', () => {
afterEach(() => {
createAlert.mockReset();
- wrapper.destroy();
});
const findMarkdownField = () => wrapper.findComponent(MarkdownField);
diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js
index 26fda877089..8d79dece888 100644
--- a/spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js
+++ b/spec/frontend/issues/show/components/incidents/timeline_events_list_spec.js
@@ -11,7 +11,7 @@ import deleteTimelineEventMutation from '~/issues/show/components/incidents/grap
import editTimelineEventMutation from '~/issues/show/components/incidents/graphql/queries/edit_timeline_event.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import { useFakeDate } from 'helpers/fake_date';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import {
mockEvents,
timelineEventsDeleteEventResponse,
@@ -26,7 +26,7 @@ import {
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal');
const mockConfirmAction = ({ confirmed }) => {
@@ -77,10 +77,6 @@ describe('IncidentTimelineEventList', () => {
mountComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
it('groups items correctly', () => {
expect(findTimelineEventGroups()).toHaveLength(2);
diff --git a/spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js b/spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js
index 63474070701..48c3f0984a0 100644
--- a/spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js
+++ b/spec/frontend/issues/show/components/incidents/timeline_events_tab_spec.js
@@ -8,13 +8,13 @@ import IncidentTimelineEventsList from '~/issues/show/components/incidents/timel
import CreateTimelineEvent from '~/issues/show/components/incidents/create_timeline_event.vue';
import timelineEventsQuery from '~/issues/show/components/incidents/graphql/queries/get_timeline_events.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { timelineTabI18n } from '~/issues/show/components/incidents/constants';
import { timelineEventsQueryListResponse, timelineEventsQueryEmptyResponse } from './mock_data';
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
const graphQLError = new Error('GraphQL error');
const listResponse = jest.fn().mockResolvedValue(timelineEventsQueryListResponse);
diff --git a/spec/frontend/issues/show/components/incidents/utils_spec.js b/spec/frontend/issues/show/components/incidents/utils_spec.js
index 75be17f9889..8ee0d906dd4 100644
--- a/spec/frontend/issues/show/components/incidents/utils_spec.js
+++ b/spec/frontend/issues/show/components/incidents/utils_spec.js
@@ -5,10 +5,10 @@ import {
getUtcShiftedDate,
getPreviousEventTags,
} from '~/issues/show/components/incidents/utils';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { mockTimelineEventTags } from './mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('incident utils', () => {
describe('display and log error', () => {
diff --git a/spec/frontend/issues/show/components/locked_warning_spec.js b/spec/frontend/issues/show/components/locked_warning_spec.js
index dd3c7c58380..f8a8c999632 100644
--- a/spec/frontend/issues/show/components/locked_warning_spec.js
+++ b/spec/frontend/issues/show/components/locked_warning_spec.js
@@ -13,11 +13,6 @@ describe('LockedWarning component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findAlert = () => wrapper.findComponent(GlAlert);
const findLink = () => wrapper.findComponent(GlLink);
diff --git a/spec/frontend/issues/show/components/task_list_item_actions_spec.js b/spec/frontend/issues/show/components/task_list_item_actions_spec.js
index d52f9d57453..8caa5236796 100644
--- a/spec/frontend/issues/show/components/task_list_item_actions_spec.js
+++ b/spec/frontend/issues/show/components/task_list_item_actions_spec.js
@@ -17,7 +17,7 @@ describe('TaskListItemActions component', () => {
document.body.appendChild(li);
wrapper = shallowMount(TaskListItemActions, {
- provide: { canUpdate: true, toggleClass: 'task-list-item-actions' },
+ provide: { canUpdate: true },
attachTo: document.querySelector('div'),
});
};
diff --git a/spec/frontend/issues/show/components/title_spec.js b/spec/frontend/issues/show/components/title_spec.js
index 7560b733ae6..16ac675e12c 100644
--- a/spec/frontend/issues/show/components/title_spec.js
+++ b/spec/frontend/issues/show/components/title_spec.js
@@ -1,96 +1,59 @@
-import Vue, { nextTick } from 'vue';
+import { nextTick } from 'vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import titleComponent from '~/issues/show/components/title.vue';
-import eventHub from '~/issues/show/event_hub';
-import Store from '~/issues/show/stores';
+import Title from '~/issues/show/components/title.vue';
describe('Title component', () => {
- let vm;
- beforeEach(() => {
+ let wrapper;
+
+ const getTitleHeader = () => wrapper.findByTestId('issue-title');
+
+ const createWrapper = (props) => {
setHTMLFixture(`<title />`);
- const Component = Vue.extend(titleComponent);
- const store = new Store({
- titleHtml: '',
- descriptionHtml: '',
- issuableRef: '',
- });
- vm = new Component({
+ wrapper = shallowMountExtended(Title, {
propsData: {
issuableRef: '#1',
titleHtml: 'Testing <img />',
titleText: 'Testing',
- showForm: false,
- formState: store.formState,
+ ...props,
},
- }).$mount();
- });
+ });
+ };
afterEach(() => {
resetHTMLFixture();
});
it('renders title HTML', () => {
- expect(vm.$el.querySelector('.title').innerHTML.trim()).toBe('Testing <img>');
- });
-
- it('updates page title when changing titleHtml', async () => {
- const spy = jest.spyOn(vm, 'setPageTitle');
- vm.titleHtml = 'test';
+ createWrapper();
- await nextTick();
- expect(spy).toHaveBeenCalled();
+ expect(getTitleHeader().element.innerHTML.trim()).toBe('Testing <img>');
});
it('animates title changes', async () => {
- vm.titleHtml = 'test';
+ createWrapper();
- await nextTick();
+ await wrapper.setProps({
+ titleHtml: 'test',
+ });
- expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-pre-pulse');
- jest.runAllTimers();
+ expect(getTitleHeader().classes('issue-realtime-pre-pulse')).toBe(true);
+ jest.runAllTimers();
await nextTick();
- expect(vm.$el.querySelector('.title').classList).toContain('issue-realtime-trigger-pulse');
+ expect(getTitleHeader().classes('issue-realtime-trigger-pulse')).toBe(true);
});
it('updates page title after changing title', async () => {
- vm.titleHtml = 'changed';
- vm.titleText = 'changed';
-
- await nextTick();
- expect(document.querySelector('title').textContent.trim()).toContain('changed');
- });
+ createWrapper();
- describe('inline edit button', () => {
- it('should not show by default', () => {
- expect(vm.$el.querySelector('.btn-edit')).toBeNull();
+ await wrapper.setProps({
+ titleHtml: 'changed',
+ titleText: 'changed',
});
- it('should not show if canUpdate is false', () => {
- vm.showInlineEditButton = true;
- vm.canUpdate = false;
-
- expect(vm.$el.querySelector('.btn-edit')).toBeNull();
- });
-
- it('should show if showInlineEditButton and canUpdate', () => {
- vm.showInlineEditButton = true;
- vm.canUpdate = true;
-
- expect(vm.$el.querySelector('.btn-edit')).toBeDefined();
- });
-
- it('should trigger open.form event when clicked', async () => {
- jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- vm.showInlineEditButton = true;
- vm.canUpdate = true;
-
- await nextTick();
- vm.$el.querySelector('.btn-edit').click();
-
- expect(eventHub.$emit).toHaveBeenCalledWith('open.form');
- });
+ expect(document.querySelector('title').textContent.trim()).toContain('changed');
});
});
diff --git a/spec/frontend/issues/show/mock_data/mock_data.js b/spec/frontend/issues/show/mock_data/mock_data.js
index 9f0b6fb1148..86d09665947 100644
--- a/spec/frontend/issues/show/mock_data/mock_data.js
+++ b/spec/frontend/issues/show/mock_data/mock_data.js
@@ -24,6 +24,19 @@ export const secondRequest = {
lock_version: 2,
};
+export const putRequest = {
+ web_url: window.location.pathname,
+ title: '<p>PUT</p>',
+ title_text: 'PUT',
+ description: '<p>PUT_DESC</p>',
+ description_text: 'PUT_DESC',
+ task_status: '0 of 0 completed',
+ updated_at: '2016-05-15T12:31:04.428Z',
+ updated_by_name: 'Other User',
+ updated_by_path: '/other_user',
+ lock_version: 2,
+};
+
export const descriptionProps = {
canUpdate: true,
descriptionHtml: 'test',
@@ -66,47 +79,3 @@ export const descriptionHtmlWithList = `
<li data-sourcepos="3:1-3:8">todo 3</li>
</ul>
`;
-
-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>
-`;
-
-export const descriptionHtmlWithTask = `
- <ul data-sourcepos="1:1-3:7" class="task-list" dir="auto">
- <li data-sourcepos="1:1-1:10" class="task-list-item">
- <input type="checkbox" class="task-list-item-checkbox" disabled>
- <a href="/gitlab-org/gitlab-test/-/issues/48" data-original="#48+" data-link="false" data-link-reference="false" data-project="1" data-issue="2" data-reference-format="+" data-reference-type="task" data-container="body" data-placement="top" title="1" class="gfm gfm-issue has-tooltip" data-issue-type="task">1 (#48)</a>
- </li>
- <li data-sourcepos="2:1-2:7" class="task-list-item">
- <input type="checkbox" class="task-list-item-checkbox" disabled> 2
- </li>
- <li data-sourcepos="3:1-3:7" class="task-list-item">
- <input type="checkbox" class="task-list-item-checkbox" disabled> 3
- </li>
- </ul>
-`;
-
-export const descriptionHtmlWithIssue = `
- <ul data-sourcepos="1:1-3:7" class="task-list" dir="auto">
- <li data-sourcepos="1:1-1:10" class="task-list-item">
- <input type="checkbox" class="task-list-item-checkbox" disabled>
- <a href="/gitlab-org/gitlab-test/-/issues/48" data-original="#48+" data-link="false" data-link-reference="false" data-project="1" data-issue="2" data-reference-format="+" data-reference-type="task" data-container="body" data-placement="top" title="1" class="gfm gfm-issue has-tooltip" data-issue-type="issue">1 (#48)</a>
- </li>
- <li data-sourcepos="2:1-2:7" class="task-list-item">
- <input type="checkbox" class="task-list-item-checkbox" disabled> 2
- </li>
- <li data-sourcepos="3:1-3:7" class="task-list-item">
- <input type="checkbox" class="task-list-item-checkbox" disabled> 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 d41031f9eaa..5e6b67aec40 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
@@ -78,10 +78,6 @@ describe('NewBranchForm', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when selecting items from dropdowns', () => {
describe('when no project selected', () => {
beforeEach(() => {
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 944854faab3..0a887efee4b 100644
--- a/spec/frontend/jira_connect/branches/components/project_dropdown_spec.js
+++ b/spec/frontend/jira_connect/branches/components/project_dropdown_spec.js
@@ -49,10 +49,6 @@ describe('ProjectDropdown', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when loading projects', () => {
beforeEach(() => {
createComponent({
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 56e425fa4eb..701512953df 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
@@ -54,10 +54,6 @@ describe('SourceBranchDropdown', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when `selectedProject` prop is not specified', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/jira_connect/branches/pages/index_spec.js b/spec/frontend/jira_connect/branches/pages/index_spec.js
index 92976dd28da..4b79d5feab5 100644
--- a/spec/frontend/jira_connect/branches/pages/index_spec.js
+++ b/spec/frontend/jira_connect/branches/pages/index_spec.js
@@ -25,10 +25,6 @@ describe('NewBranchForm', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('page title', () => {
it.each`
initialBranchName | pageTitle
diff --git a/spec/frontend/jira_connect/subscriptions/api_spec.js b/spec/frontend/jira_connect/subscriptions/api_spec.js
index e2a14a9102f..5a28c6d9789 100644
--- a/spec/frontend/jira_connect/subscriptions/api_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/api_spec.js
@@ -17,7 +17,6 @@ jest.mock('~/jira_connect/subscriptions/utils', () => ({
describe('JiraConnect API', () => {
let axiosMock;
- let originalGon;
let response;
const mockAddPath = 'addPath';
@@ -29,13 +28,11 @@ describe('JiraConnect API', () => {
beforeEach(() => {
axiosMock = new MockAdapter(axiosInstance);
- originalGon = window.gon;
window.gon = { api_version: 'v4' };
});
afterEach(() => {
axiosMock.restore();
- window.gon = originalGon;
response = null;
});
diff --git a/spec/frontend/jira_connect/subscriptions/components/add_namespace_button_spec.js b/spec/frontend/jira_connect/subscriptions/components/add_namespace_button_spec.js
index 9f92ad2adc1..934473c15ba 100644
--- a/spec/frontend/jira_connect/subscriptions/components/add_namespace_button_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/add_namespace_button_spec.js
@@ -11,7 +11,7 @@ describe('AddNamespaceButton', () => {
const createComponent = () => {
wrapper = shallowMount(AddNamespaceButton, {
directives: {
- glModal: createMockDirective(),
+ glModal: createMockDirective('gl-modal'),
},
});
};
@@ -23,10 +23,6 @@ describe('AddNamespaceButton', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays a button', () => {
expect(findButton().exists()).toBe(true);
});
diff --git a/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/add_namespace_modal_spec.js b/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/add_namespace_modal_spec.js
index d80381107f2..dbe8a734bb4 100644
--- a/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/add_namespace_modal_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/add_namespace_modal_spec.js
@@ -17,10 +17,6 @@ describe('AddNamespaceModal', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays modal with correct props', () => {
const modal = findModal();
expect(modal.exists()).toBe(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 5df54abfc05..e437e6e0398 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
@@ -40,10 +40,6 @@ describe('GroupsListItem', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGroupItemName = () => wrapper.findComponent(GroupItemName);
const findLinkButton = () => wrapper.findComponent(GlButton);
const clickLinkButton = () => findLinkButton().trigger('click');
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 97038a2a231..9d5bc8dff2a 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
@@ -48,10 +48,6 @@ describe('GroupsList', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGlAlert = () => wrapper.findComponent(GlAlert);
const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findAllItems = () => wrapper.findAllComponents(GroupsListItem);
diff --git a/spec/frontend/jira_connect/subscriptions/components/app_spec.js b/spec/frontend/jira_connect/subscriptions/components/app_spec.js
index 369ddda8dbe..aa4feaa5261 100644
--- a/spec/frontend/jira_connect/subscriptions/components/app_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/app_spec.js
@@ -41,10 +41,6 @@ describe('JiraConnectApp', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
describe.each`
scenario | usersPath | shouldRenderSignInPage | shouldRenderSubscriptionsPage
diff --git a/spec/frontend/jira_connect/subscriptions/components/browser_support_alert_spec.js b/spec/frontend/jira_connect/subscriptions/components/browser_support_alert_spec.js
index aa93a6be3c8..a8aa383d917 100644
--- a/spec/frontend/jira_connect/subscriptions/components/browser_support_alert_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/browser_support_alert_spec.js
@@ -12,10 +12,6 @@ describe('BrowserSupportAlert', () => {
const findAlert = () => wrapper.findComponent(GlAlert);
const findLink = () => wrapper.findComponent(GlLink);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays a non-dismissible alert', () => {
createComponent();
diff --git a/spec/frontend/jira_connect/subscriptions/components/group_item_name_spec.js b/spec/frontend/jira_connect/subscriptions/components/group_item_name_spec.js
index b5fe08486b1..e4da10569f3 100644
--- a/spec/frontend/jira_connect/subscriptions/components/group_item_name_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/group_item_name_spec.js
@@ -14,10 +14,6 @@ describe('GroupItemName', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
it('matches the snapshot', () => {
createComponent();
diff --git a/spec/frontend/jira_connect/subscriptions/components/sign_in_legacy_button_spec.js b/spec/frontend/jira_connect/subscriptions/components/sign_in_legacy_button_spec.js
index 4ebfaed261e..0dadec598e4 100644
--- a/spec/frontend/jira_connect/subscriptions/components/sign_in_legacy_button_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/sign_in_legacy_button_spec.js
@@ -22,10 +22,6 @@ describe('SignInLegacyButton', () => {
const findButton = () => wrapper.findComponent(GlButton);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays a button', () => {
createComponent();
diff --git a/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js b/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js
index e20c4b62e77..9d39b82c05f 100644
--- a/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js
@@ -56,10 +56,6 @@ describe('SignInOauthButton', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findButton = () => wrapper.findComponent(GlButton);
describe('when `gitlabBasePath` is GitLab.com', () => {
it('displays a button', () => {
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 2d7c58fc278..5337575d5ef 100644
--- a/spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js
@@ -29,10 +29,6 @@ describe('SubscriptionsList', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findUnlinkButton = () => wrapper.findComponent(GlButton);
const clickUnlinkButton = () => findUnlinkButton().trigger('click');
diff --git a/spec/frontend/jira_connect/subscriptions/components/user_link_spec.js b/spec/frontend/jira_connect/subscriptions/components/user_link_spec.js
index e16121243a0..c0bd908da0f 100644
--- a/spec/frontend/jira_connect/subscriptions/components/user_link_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/user_link_spec.js
@@ -28,10 +28,6 @@ describe('UserLink', () => {
const findSprintf = () => wrapper.findComponent(GlSprintf);
const findOauthButton = () => wrapper.findComponent(SignInOauthButton);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe.each`
userSignedIn | hasSubscriptions | expectGlSprintf | expectGlLink | expectOauthButton | jiraConnectOauthEnabled
${true} | ${false} | ${true} | ${false} | ${false} | ${false}
diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com_spec.js
index b9a8451f3b3..be46c1d1609 100644
--- a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com_spec.js
@@ -42,10 +42,6 @@ describe('SignInGitlabCom', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
describe.each`
scenario | hasSubscriptions | signInButtonText
diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js
index e98c6ff1054..d99d8986296 100644
--- a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index_spec.js
@@ -32,10 +32,6 @@ describe('SignInGitlabMultiversion', () => {
wrapper = shallowMountExtended(SignInGitlabMultiversion);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when version is not selected', () => {
describe('VersionSelectForm', () => {
it('renders version select form', () => {
diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form_spec.js
index 29e7fe7a5b2..428aa1d734b 100644
--- a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form_spec.js
@@ -17,10 +17,6 @@ describe('VersionSelectForm', () => {
wrapper = shallowMountExtended(VersionSelectForm);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default state', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_page_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_page_spec.js
index b27eba6b040..7639c3a9c3f 100644
--- a/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_page_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in/sign_in_page_spec.js
@@ -34,10 +34,6 @@ describe('SignInPage', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
jiraConnectOauthEnabled | publicKeyStorageEnabled | shouldRenderDotCom | shouldRenderMultiversion
${false} | ${true} | ${true} | ${false}
diff --git a/spec/frontend/jira_connect/subscriptions/pages/subscriptions_page_spec.js b/spec/frontend/jira_connect/subscriptions/pages/subscriptions_page_spec.js
index 4956af76ead..d262f4b2735 100644
--- a/spec/frontend/jira_connect/subscriptions/pages/subscriptions_page_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/pages/subscriptions_page_spec.js
@@ -26,10 +26,6 @@ describe('SubscriptionsPage', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
describe.each`
scenario | subscriptionsLoading | hasSubscriptions | expectSubscriptionsList | expectEmptyState
diff --git a/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap b/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap
index 40e627262db..6766456d09c 100644
--- a/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap
+++ b/spec/frontend/jira_import/components/__snapshots__/jira_import_form_spec.js.snap
@@ -2,7 +2,7 @@
exports[`JiraImportForm table body shows correct information in each cell 1`] = `
<table
- aria-busy="false"
+ aria-busy=""
aria-colcount="3"
class="table b-table gl-table b-table-fixed"
role="table"
@@ -92,7 +92,7 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] =
<!---->
<button
aria-expanded="false"
- aria-haspopup="true"
+ aria-haspopup="menu"
class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle"
type="button"
>
@@ -217,7 +217,7 @@ exports[`JiraImportForm table body shows correct information in each cell 1`] =
<!---->
<button
aria-expanded="false"
- aria-haspopup="true"
+ aria-haspopup="menu"
class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle"
type="button"
>
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 022a0f81aaa..dc1b75f5d9e 100644
--- a/spec/frontend/jira_import/components/jira_import_app_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_app_spec.js
@@ -67,11 +67,6 @@ describe('JiraImportApp', () => {
},
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when Jira integration is not configured', () => {
beforeEach(() => {
wrapper = mountComponent({ isJiraConfigured: false });
diff --git a/spec/frontend/jira_import/components/jira_import_form_spec.js b/spec/frontend/jira_import/components/jira_import_form_spec.js
index d43a9f8a145..c7db9f429de 100644
--- a/spec/frontend/jira_import/components/jira_import_form_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_form_spec.js
@@ -106,7 +106,6 @@ describe('JiraImportForm', () => {
axiosMock.restore();
mutateSpy.mockRestore();
querySpy.mockRestore();
- wrapper.destroy();
});
describe('select dropdown project selection', () => {
diff --git a/spec/frontend/jira_import/components/jira_import_progress_spec.js b/spec/frontend/jira_import/components/jira_import_progress_spec.js
index 42356763492..c0d415a2130 100644
--- a/spec/frontend/jira_import/components/jira_import_progress_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_progress_spec.js
@@ -25,11 +25,6 @@ describe('JiraImportProgress', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('empty state', () => {
beforeEach(() => {
wrapper = mountComponent();
diff --git a/spec/frontend/jira_import/components/jira_import_setup_spec.js b/spec/frontend/jira_import/components/jira_import_setup_spec.js
index 0085a2b5572..5331467d669 100644
--- a/spec/frontend/jira_import/components/jira_import_setup_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_setup_spec.js
@@ -17,11 +17,6 @@ describe('JiraImportSetup', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('contains illustration', () => {
expect(getGlEmptyStateProp('svgPath')).toBe(illustration);
});
diff --git a/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js b/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js
index 14613775791..5ecddc7efd6 100644
--- a/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js
+++ b/spec/frontend/jobs/components/filtered_search/jobs_filtered_search_spec.js
@@ -27,10 +27,6 @@ describe('Jobs filtered search', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays filtered search', () => {
createComponent();
diff --git a/spec/frontend/jobs/components/filtered_search/tokens/job_status_token_spec.js b/spec/frontend/jobs/components/filtered_search/tokens/job_status_token_spec.js
index fbe5f6a2e11..6755b854f01 100644
--- a/spec/frontend/jobs/components/filtered_search/tokens/job_status_token_spec.js
+++ b/spec/frontend/jobs/components/filtered_search/tokens/job_status_token_spec.js
@@ -45,10 +45,6 @@ describe('Job Status Token', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('passes config correctly', () => {
expect(findFilteredSearchToken().props('config')).toEqual(defaultProps.config);
});
diff --git a/spec/frontend/jobs/components/job/artifacts_block_spec.js b/spec/frontend/jobs/components/job/artifacts_block_spec.js
index c75deb64d84..ea5d727bd08 100644
--- a/spec/frontend/jobs/components/job/artifacts_block_spec.js
+++ b/spec/frontend/jobs/components/job/artifacts_block_spec.js
@@ -55,11 +55,6 @@ describe('Artifacts block', () => {
locked: true,
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('with expired artifacts that are not locked', () => {
beforeEach(() => {
wrapper = createWrapper({
diff --git a/spec/frontend/jobs/components/job/commit_block_spec.js b/spec/frontend/jobs/components/job/commit_block_spec.js
index 4fcc754c82c..1c28b5079d7 100644
--- a/spec/frontend/jobs/components/job/commit_block_spec.js
+++ b/spec/frontend/jobs/components/job/commit_block_spec.js
@@ -32,10 +32,6 @@ describe('Commit block', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('without merge request', () => {
beforeEach(() => {
mountComponent();
diff --git a/spec/frontend/jobs/components/job/environments_block_spec.js b/spec/frontend/jobs/components/job/environments_block_spec.js
index 134533e2af8..ab36f79ea5e 100644
--- a/spec/frontend/jobs/components/job/environments_block_spec.js
+++ b/spec/frontend/jobs/components/job/environments_block_spec.js
@@ -51,11 +51,6 @@ describe('Environments block', () => {
const findEnvironmentLink = () => wrapper.find('[data-testid="job-environment-link"]');
const findClusterLink = () => wrapper.find('[data-testid="job-cluster-link"]');
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('with last deployment', () => {
it('renders info for most recent deployment', () => {
createComponent({
diff --git a/spec/frontend/jobs/components/job/erased_block_spec.js b/spec/frontend/jobs/components/job/erased_block_spec.js
index c6aba01fa53..aeab676fc7e 100644
--- a/spec/frontend/jobs/components/job/erased_block_spec.js
+++ b/spec/frontend/jobs/components/job/erased_block_spec.js
@@ -18,10 +18,6 @@ describe('Erased block', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with job erased by user', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/jobs/components/job/job_app_spec.js b/spec/frontend/jobs/components/job/job_app_spec.js
index cefedcd82fb..394fc8ad43c 100644
--- a/spec/frontend/jobs/components/job/job_app_spec.js
+++ b/spec/frontend/jobs/components/job/job_app_spec.js
@@ -83,8 +83,9 @@ describe('Job App', () => {
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
+ // eslint-disable-next-line @gitlab/vtu-no-explicit-wrapper-destroy
+ wrapper.destroy();
});
describe('while loading', () => {
diff --git a/spec/frontend/jobs/components/job/job_container_item_spec.js b/spec/frontend/jobs/components/job/job_container_item_spec.js
index 05c38dd74b7..8121aa1172f 100644
--- a/spec/frontend/jobs/components/job/job_container_item_spec.js
+++ b/spec/frontend/jobs/components/job/job_container_item_spec.js
@@ -24,11 +24,6 @@ describe('JobContainerItem', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when a job is not active and not retried', () => {
beforeEach(() => {
createComponent(job);
diff --git a/spec/frontend/jobs/components/job/job_log_controllers_spec.js b/spec/frontend/jobs/components/job/job_log_controllers_spec.js
index 5e9a73b4387..9917c63b2d0 100644
--- a/spec/frontend/jobs/components/job/job_log_controllers_spec.js
+++ b/spec/frontend/jobs/components/job/job_log_controllers_spec.js
@@ -285,6 +285,18 @@ describe('Job log controllers', () => {
expect(findScrollFailure().props('disabled')).toBe(false);
});
});
+
+ describe('on error', () => {
+ beforeEach(() => {
+ jest.spyOn(commonUtils, 'backOff').mockRejectedValueOnce();
+
+ createWrapper({}, { jobLogJumpToFailures: true });
+ });
+
+ it('stays disabled', () => {
+ expect(findScrollFailure().props('disabled')).toBe(true);
+ });
+ });
});
});
diff --git a/spec/frontend/jobs/components/job/job_retry_forward_deployment_modal_spec.js b/spec/frontend/jobs/components/job/job_retry_forward_deployment_modal_spec.js
index d60043f33f7..712269a1e83 100644
--- a/spec/frontend/jobs/components/job/job_retry_forward_deployment_modal_spec.js
+++ b/spec/frontend/jobs/components/job/job_retry_forward_deployment_modal_spec.js
@@ -64,13 +64,11 @@ describe('Job Retry Forward Deployment Modal', () => {
beforeEach(createWrapper);
it('should correctly configure the primary action', () => {
- expect(findModal().props('actionPrimary').attributes).toMatchObject([
- {
- 'data-method': 'post',
- href: job.retry_path,
- variant: 'danger',
- },
- ]);
+ expect(findModal().props('actionPrimary').attributes).toMatchObject({
+ 'data-method': 'post',
+ href: job.retry_path,
+ variant: 'danger',
+ });
});
});
});
diff --git a/spec/frontend/jobs/components/job/jobs_container_spec.js b/spec/frontend/jobs/components/job/jobs_container_spec.js
index 2fde4d3020b..05660880751 100644
--- a/spec/frontend/jobs/components/job/jobs_container_spec.js
+++ b/spec/frontend/jobs/components/job/jobs_container_spec.js
@@ -68,10 +68,6 @@ describe('Jobs List block', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a list of jobs', () => {
createComponent({
jobs: [job, retried, active],
diff --git a/spec/frontend/jobs/components/job/manual_variables_form_spec.js b/spec/frontend/jobs/components/job/manual_variables_form_spec.js
index a5b3b0e3b47..98b9ca78a45 100644
--- a/spec/frontend/jobs/components/job/manual_variables_form_spec.js
+++ b/spec/frontend/jobs/components/job/manual_variables_form_spec.js
@@ -2,24 +2,28 @@ import { GlSprintf, GlLink } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import { nextTick } from 'vue';
+import { createAlert } from '~/alert';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import { TYPENAME_CI_BUILD } from '~/graphql_shared/constants';
+import { JOB_GRAPHQL_ERRORS } from '~/jobs/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import waitForPromises from 'helpers/wait_for_promises';
import { redirectTo } from '~/lib/utils/url_utility';
import ManualVariablesForm from '~/jobs/components/job/manual_variables_form.vue';
import getJobQuery from '~/jobs/components/job/graphql/queries/get_job.query.graphql';
-import retryJobMutation from '~/jobs/components/job/graphql/mutations/job_retry_with_variables.mutation.graphql';
+import playJobMutation from '~/jobs/components/job/graphql/mutations/job_play_with_variables.mutation.graphql';
import {
mockFullPath,
mockId,
mockJobResponse,
mockJobWithVariablesResponse,
- mockJobMutationData,
+ mockJobPlayMutationData,
+ mockJobRetryMutationData,
} from './mock_data';
const localVue = createLocalVue();
+jest.mock('~/alert');
localVue.use(VueApollo);
jest.mock('~/lib/utils/url_utility', () => ({
@@ -39,9 +43,9 @@ describe('Manual Variables Form', () => {
const createComponent = ({ options = {}, props = {} } = {}) => {
wrapper = mountExtended(ManualVariablesForm, {
propsData: {
- ...props,
jobId: mockId,
- isRetryable: true,
+ isRetryable: false,
+ ...props,
},
provide: {
...defaultProvide,
@@ -71,7 +75,7 @@ describe('Manual Variables Form', () => {
const findHelpText = () => wrapper.findComponent(GlSprintf);
const findHelpLink = () => wrapper.findComponent(GlLink);
const findCancelBtn = () => wrapper.findByTestId('cancel-btn');
- const findRerunBtn = () => wrapper.findByTestId('run-manual-job-btn');
+ const findRunBtn = () => wrapper.findByTestId('run-manual-job-btn');
const findDeleteVarBtn = () => wrapper.findByTestId('delete-variable-btn');
const findAllDeleteVarBtns = () => wrapper.findAllByTestId('delete-variable-btn');
const findDeleteVarBtnPlaceholder = () => wrapper.findByTestId('delete-variable-btn-placeholder');
@@ -97,7 +101,7 @@ describe('Manual Variables Form', () => {
});
afterEach(() => {
- wrapper.destroy();
+ createAlert.mockClear();
});
describe('when page renders', () => {
@@ -112,10 +116,30 @@ describe('Manual Variables Form', () => {
'/help/ci/variables/index#add-a-cicd-variable-to-a-project',
);
});
+ });
- it('renders buttons', () => {
- expect(findCancelBtn().exists()).toBe(true);
- expect(findRerunBtn().exists()).toBe(true);
+ describe('when query is unsuccessful', () => {
+ beforeEach(async () => {
+ getJobQueryResponse.mockRejectedValue({});
+ await createComponentWithApollo();
+ });
+
+ it('shows an alert with error', () => {
+ expect(createAlert).toHaveBeenCalledWith({
+ message: JOB_GRAPHQL_ERRORS.jobQueryErrorText,
+ });
+ });
+ });
+
+ describe('when job has not been retried', () => {
+ beforeEach(async () => {
+ getJobQueryResponse.mockResolvedValue(mockJobWithVariablesResponse);
+ await createComponentWithApollo();
+ });
+
+ it('does not render the cancel button', () => {
+ expect(findCancelBtn().exists()).toBe(false);
+ expect(findRunBtn().exists()).toBe(true);
});
});
@@ -135,10 +159,10 @@ describe('Manual Variables Form', () => {
});
});
- describe('when mutation fires', () => {
+ describe('when play mutation fires', () => {
beforeEach(async () => {
await createComponentWithApollo();
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockJobMutationData);
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockJobPlayMutationData);
});
it('passes variables in correct format', async () => {
@@ -146,11 +170,11 @@ describe('Manual Variables Form', () => {
await findCiVariableValue().setValue('new value');
- await findRerunBtn().vm.$emit('click');
+ await findRunBtn().vm.$emit('click');
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: retryJobMutation,
+ mutation: playJobMutation,
variables: {
id: convertToGraphQLId(TYPENAME_CI_BUILD, mockId),
variables: [
@@ -163,13 +187,63 @@ describe('Manual Variables Form', () => {
});
});
- // redirect to job after initial trigger assertion will be added in https://gitlab.com/gitlab-org/gitlab/-/issues/377268
+ it('redirects to job properly after job is run', async () => {
+ findRunBtn().vm.$emit('click');
+ await waitForPromises();
+
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
+ expect(redirectTo).toHaveBeenCalledWith(mockJobPlayMutationData.data.jobPlay.job.webPath);
+ });
+ });
+
+ describe('when play mutation is unsuccessful', () => {
+ beforeEach(async () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue({});
+ await createComponentWithApollo();
+ });
+
+ it('shows an alert with error', async () => {
+ findRunBtn().vm.$emit('click');
+ await waitForPromises();
+
+ expect(createAlert).toHaveBeenCalledWith({
+ message: JOB_GRAPHQL_ERRORS.jobMutationErrorText,
+ });
+ });
+ });
+
+ describe('when job is retryable', () => {
+ beforeEach(async () => {
+ await createComponentWithApollo({ props: { isRetryable: true } });
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockJobRetryMutationData);
+ });
+
+ it('renders cancel button', () => {
+ expect(findCancelBtn().exists()).toBe(true);
+ });
+
it('redirects to job properly after rerun', async () => {
- findRerunBtn().vm.$emit('click');
+ findRunBtn().vm.$emit('click');
await waitForPromises();
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
- expect(redirectTo).toHaveBeenCalledWith(mockJobMutationData.data.jobRetry.job.webPath);
+ expect(redirectTo).toHaveBeenCalledWith(mockJobRetryMutationData.data.jobRetry.job.webPath);
+ });
+ });
+
+ describe('when retry mutation is unsuccessful', () => {
+ beforeEach(async () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue({});
+ await createComponentWithApollo({ props: { isRetryable: true } });
+ });
+
+ it('shows an alert with error', async () => {
+ findRunBtn().vm.$emit('click');
+ await waitForPromises();
+
+ expect(createAlert).toHaveBeenCalledWith({
+ message: JOB_GRAPHQL_ERRORS.jobMutationErrorText,
+ });
});
});
diff --git a/spec/frontend/jobs/components/job/mock_data.js b/spec/frontend/jobs/components/job/mock_data.js
index 8a838acca7a..fb3a361c9c9 100644
--- a/spec/frontend/jobs/components/job/mock_data.js
+++ b/spec/frontend/jobs/components/job/mock_data.js
@@ -50,7 +50,32 @@ export const mockJobWithVariablesResponse = {
},
};
-export const mockJobMutationData = {
+export const mockJobPlayMutationData = {
+ data: {
+ jobPlay: {
+ job: {
+ id: 'gid://gitlab/Ci::Build/401',
+ manualVariables: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Ci::JobVariable/151',
+ key: 'new key',
+ value: 'new value',
+ __typename: 'CiManualVariable',
+ },
+ ],
+ __typename: 'CiManualVariableConnection',
+ },
+ webPath: '/Commit451/lab-coat/-/jobs/401',
+ __typename: 'CiJob',
+ },
+ errors: [],
+ __typename: 'JobPlayPayload',
+ },
+ },
+};
+
+export const mockJobRetryMutationData = {
data: {
jobRetry: {
job: {
diff --git a/spec/frontend/jobs/components/job/sidebar_detail_row_spec.js b/spec/frontend/jobs/components/job/sidebar_detail_row_spec.js
index 5c9c011b4ab..dd5a9e3491d 100644
--- a/spec/frontend/jobs/components/job/sidebar_detail_row_spec.js
+++ b/spec/frontend/jobs/components/job/sidebar_detail_row_spec.js
@@ -19,11 +19,6 @@ describe('Sidebar detail row', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('with title/value and without helpUrl', () => {
beforeEach(() => {
createComponent({ title, value });
diff --git a/spec/frontend/jobs/components/job/sidebar_spec.js b/spec/frontend/jobs/components/job/sidebar_spec.js
index aa9ca932023..cefa4582c15 100644
--- a/spec/frontend/jobs/components/job/sidebar_spec.js
+++ b/spec/frontend/jobs/components/job/sidebar_spec.js
@@ -48,10 +48,6 @@ describe('Sidebar details block', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('without terminal path', () => {
it('does not render terminal link', async () => {
createWrapper();
diff --git a/spec/frontend/jobs/components/job/stages_dropdown_spec.js b/spec/frontend/jobs/components/job/stages_dropdown_spec.js
index 61dec585e82..f782d5600e6 100644
--- a/spec/frontend/jobs/components/job/stages_dropdown_spec.js
+++ b/spec/frontend/jobs/components/job/stages_dropdown_spec.js
@@ -37,10 +37,6 @@ describe('Stages Dropdown', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('without a merge request pipeline', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/jobs/components/job/trigger_block_spec.js b/spec/frontend/jobs/components/job/trigger_block_spec.js
index a1de8fd143f..8bb2c1f3ad8 100644
--- a/spec/frontend/jobs/components/job/trigger_block_spec.js
+++ b/spec/frontend/jobs/components/job/trigger_block_spec.js
@@ -20,10 +20,6 @@ describe('Trigger block', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with short token and no variables', () => {
it('renders short token', () => {
createComponent({
diff --git a/spec/frontend/jobs/components/job/unmet_prerequisites_block_spec.js b/spec/frontend/jobs/components/job/unmet_prerequisites_block_spec.js
index fb7d389c4d6..1072cdd6781 100644
--- a/spec/frontend/jobs/components/job/unmet_prerequisites_block_spec.js
+++ b/spec/frontend/jobs/components/job/unmet_prerequisites_block_spec.js
@@ -18,10 +18,6 @@ describe('Unmet Prerequisites Block Job component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders an alert with the correct message', () => {
const container = wrapper.findComponent(GlAlert);
const alertMessage =
diff --git a/spec/frontend/jobs/components/log/collapsible_section_spec.js b/spec/frontend/jobs/components/log/collapsible_section_spec.js
index 646935568b1..5adedea28a5 100644
--- a/spec/frontend/jobs/components/log/collapsible_section_spec.js
+++ b/spec/frontend/jobs/components/log/collapsible_section_spec.js
@@ -19,10 +19,6 @@ describe('Job Log Collapsible Section', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with closed section', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/jobs/components/log/duration_badge_spec.js b/spec/frontend/jobs/components/log/duration_badge_spec.js
index 84dae386bdb..644d05366a0 100644
--- a/spec/frontend/jobs/components/log/duration_badge_spec.js
+++ b/spec/frontend/jobs/components/log/duration_badge_spec.js
@@ -20,10 +20,6 @@ describe('Job Log Duration Badge', () => {
createComponent(data);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders provided duration', () => {
expect(wrapper.text()).toBe(data.duration);
});
diff --git a/spec/frontend/jobs/components/log/line_header_spec.js b/spec/frontend/jobs/components/log/line_header_spec.js
index ec8e79bba13..16fe753e08a 100644
--- a/spec/frontend/jobs/components/log/line_header_spec.js
+++ b/spec/frontend/jobs/components/log/line_header_spec.js
@@ -29,10 +29,6 @@ describe('Job Log Header Line', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('line', () => {
beforeEach(() => {
createComponent(data);
diff --git a/spec/frontend/jobs/components/log/line_number_spec.js b/spec/frontend/jobs/components/log/line_number_spec.js
index 96aa31baab9..4130c124a30 100644
--- a/spec/frontend/jobs/components/log/line_number_spec.js
+++ b/spec/frontend/jobs/components/log/line_number_spec.js
@@ -21,10 +21,6 @@ describe('Job Log Line Number', () => {
createComponent(data);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders incremented lineNunber by 1', () => {
expect(wrapper.text()).toBe('1');
});
diff --git a/spec/frontend/jobs/components/log/log_spec.js b/spec/frontend/jobs/components/log/log_spec.js
index c933ed5c3e1..265f72ff344 100644
--- a/spec/frontend/jobs/components/log/log_spec.js
+++ b/spec/frontend/jobs/components/log/log_spec.js
@@ -2,6 +2,7 @@ import { mount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import Log from '~/jobs/components/log/log.vue';
+import LogLineHeader from '~/jobs/components/log/line_header.vue';
import { logLinesParser } from '~/jobs/store/utils';
import { jobLog } from './mock_data';
@@ -10,6 +11,7 @@ describe('Job Log', () => {
let actions;
let state;
let store;
+ let toggleCollapsibleLineMock;
Vue.use(Vuex);
@@ -20,8 +22,9 @@ describe('Job Log', () => {
};
beforeEach(() => {
+ toggleCollapsibleLineMock = jest.fn();
actions = {
- toggleCollapsibleLine: () => {},
+ toggleCollapsibleLine: toggleCollapsibleLineMock,
};
state = {
@@ -37,11 +40,7 @@ describe('Job Log', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
- const findCollapsibleLine = () => wrapper.find('.collapsible-line');
+ const findCollapsibleLine = () => wrapper.findComponent(LogLineHeader);
describe('line numbers', () => {
it('renders a line number for each open line', () => {
@@ -68,11 +67,9 @@ describe('Job Log', () => {
describe('on click header section', () => {
it('calls toggleCollapsibleLine', () => {
- jest.spyOn(wrapper.vm, 'toggleCollapsibleLine');
-
findCollapsibleLine().trigger('click');
- expect(wrapper.vm.toggleCollapsibleLine).toHaveBeenCalled();
+ expect(toggleCollapsibleLineMock).toHaveBeenCalled();
});
});
});
diff --git a/spec/frontend/jobs/components/table/cells/duration_cell_spec.js b/spec/frontend/jobs/components/table/cells/duration_cell_spec.js
index 763a4b0eaa2..d015edb0e91 100644
--- a/spec/frontend/jobs/components/table/cells/duration_cell_spec.js
+++ b/spec/frontend/jobs/components/table/cells/duration_cell_spec.js
@@ -22,10 +22,6 @@ describe('Duration Cell', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('does not display duration or finished time when no properties are present', () => {
createComponent();
diff --git a/spec/frontend/jobs/components/table/cells/job_cell_spec.js b/spec/frontend/jobs/components/table/cells/job_cell_spec.js
index ddc196129a7..73e37eed5f1 100644
--- a/spec/frontend/jobs/components/table/cells/job_cell_spec.js
+++ b/spec/frontend/jobs/components/table/cells/job_cell_spec.js
@@ -39,10 +39,6 @@ describe('Job Cell', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Job Id', () => {
it('displays the job id and links to the job', () => {
createComponent();
diff --git a/spec/frontend/jobs/components/table/cells/pipeline_cell_spec.js b/spec/frontend/jobs/components/table/cells/pipeline_cell_spec.js
index 1f5e0a7aa21..3d424b20964 100644
--- a/spec/frontend/jobs/components/table/cells/pipeline_cell_spec.js
+++ b/spec/frontend/jobs/components/table/cells/pipeline_cell_spec.js
@@ -42,10 +42,6 @@ describe('Pipeline Cell', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Pipeline Id', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/jobs/components/table/graphql/cache_config_spec.js b/spec/frontend/jobs/components/table/graphql/cache_config_spec.js
index 88c97285b85..e3b1ca1cce3 100644
--- a/spec/frontend/jobs/components/table/graphql/cache_config_spec.js
+++ b/spec/frontend/jobs/components/table/graphql/cache_config_spec.js
@@ -84,4 +84,23 @@ describe('jobs/components/table/graphql/cache_config', () => {
expect(res.nodes).toHaveLength(CIJobConnectionIncomingCacheRunningStatus.nodes.length);
});
});
+
+ describe('when incoming data has no nodes', () => {
+ it('should return existing cache', () => {
+ const res = cacheConfig.typePolicies.CiJobConnection.merge(
+ CIJobConnectionExistingCache,
+ { __typename: 'CiJobConnection', count: 500 },
+ {
+ args: { statuses: 'SUCCESS' },
+ },
+ );
+
+ const expectedResponse = {
+ ...CIJobConnectionExistingCache,
+ statuses: 'SUCCESS',
+ };
+
+ expect(res).toEqual(expectedResponse);
+ });
+ });
});
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 109cef6f817..6247cfcc640 100644
--- a/spec/frontend/jobs/components/table/job_table_app_spec.js
+++ b/spec/frontend/jobs/components/table/job_table_app_spec.js
@@ -12,8 +12,9 @@ import { s__ } from '~/locale';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { TEST_HOST } from 'spec/test_constants';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import getJobsQuery from '~/jobs/components/table/graphql/queries/get_jobs.query.graphql';
+import getJobsCountQuery from '~/jobs/components/table/graphql/queries/get_jobs_count.query.graphql';
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';
@@ -23,12 +24,13 @@ import {
mockJobsResponsePaginated,
mockJobsResponseEmpty,
mockFailedSearchToken,
+ mockJobsCountResponse,
} from '../../mock_data';
const projectPath = 'gitlab-org/gitlab';
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Job table app', () => {
let wrapper;
@@ -37,6 +39,8 @@ describe('Job table app', () => {
const failedHandler = jest.fn().mockRejectedValue(new Error('GraphQL error'));
const emptyHandler = jest.fn().mockResolvedValue(mockJobsResponseEmpty);
+ const countSuccessHandler = jest.fn().mockResolvedValue(mockJobsCountResponse);
+
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findLoadingSpinner = () => wrapper.findComponent(GlLoadingIcon);
const findTable = () => wrapper.findComponent(JobsTable);
@@ -48,14 +52,18 @@ describe('Job table app', () => {
const triggerInfiniteScroll = () =>
wrapper.findComponent(GlIntersectionObserver).vm.$emit('appear');
- const createMockApolloProvider = (handler) => {
- const requestHandlers = [[getJobsQuery, handler]];
+ const createMockApolloProvider = (handler, countHandler) => {
+ const requestHandlers = [
+ [getJobsQuery, handler],
+ [getJobsCountQuery, countHandler],
+ ];
return createMockApollo(requestHandlers);
};
const createComponent = ({
handler = successHandler,
+ countHandler = countSuccessHandler,
mountFn = shallowMount,
data = {},
} = {}) => {
@@ -68,14 +76,10 @@ describe('Job table app', () => {
provide: {
fullPath: projectPath,
},
- apolloProvider: createMockApolloProvider(handler),
+ apolloProvider: createMockApolloProvider(handler, countHandler),
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('loading state', () => {
it('should display skeleton loader when loading', () => {
createComponent();
@@ -148,12 +152,39 @@ describe('Job table app', () => {
});
describe('error state', () => {
- it('should show an alert if there is an error fetching the data', async () => {
+ it('should show an alert if there is an error fetching the jobs data', async () => {
createComponent({ handler: failedHandler });
await waitForPromises();
- expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe('There was an error fetching the jobs for your project.');
+ expect(findTable().exists()).toBe(false);
+ });
+
+ it('should show an alert if there is an error fetching the jobs count data', async () => {
+ createComponent({ handler: successHandler, countHandler: failedHandler });
+
+ await waitForPromises();
+
+ expect(findAlert().text()).toBe(
+ 'There was an error fetching the number of jobs for your project.',
+ );
+ });
+
+ it('jobs table should still load if count query fails', async () => {
+ createComponent({ handler: successHandler, countHandler: failedHandler });
+
+ await waitForPromises();
+
+ expect(findTable().exists()).toBe(true);
+ });
+
+ it('jobs count should be zero if count query fails', async () => {
+ createComponent({ handler: successHandler, countHandler: failedHandler });
+
+ await waitForPromises();
+
+ expect(findTabs().props('allJobsCount')).toBe(0);
});
});
diff --git a/spec/frontend/jobs/components/table/jobs_table_spec.js b/spec/frontend/jobs/components/table/jobs_table_spec.js
index 3c4f2d624fe..06b13aa4372 100644
--- a/spec/frontend/jobs/components/table/jobs_table_spec.js
+++ b/spec/frontend/jobs/components/table/jobs_table_spec.js
@@ -30,10 +30,6 @@ describe('Jobs Table', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays the jobs table', () => {
expect(findTable().exists()).toBe(true);
});
diff --git a/spec/frontend/jobs/components/table/jobs_table_tabs_spec.js b/spec/frontend/jobs/components/table/jobs_table_tabs_spec.js
index 23632001060..70bcac82a3f 100644
--- a/spec/frontend/jobs/components/table/jobs_table_tabs_spec.js
+++ b/spec/frontend/jobs/components/table/jobs_table_tabs_spec.js
@@ -42,10 +42,6 @@ describe('Jobs Table Tabs', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays All tab with count', () => {
expect(trimText(findAllTab().text())).toBe(`All ${defaultProps.allJobsCount}`);
});
diff --git a/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js b/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js
index 1d3845b19bb..098a63719fe 100644
--- a/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js
+++ b/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js
@@ -16,11 +16,6 @@ describe('DelayedJobMixin', () => {
template: '<div>{{remainingTime}}</div>',
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('if job is empty object', () => {
beforeEach(() => {
wrapper = shallowMount(dummyComponent, {
diff --git a/spec/frontend/jobs/mock_data.js b/spec/frontend/jobs/mock_data.js
index 9abd610c26d..483b4ca711f 100644
--- a/spec/frontend/jobs/mock_data.js
+++ b/spec/frontend/jobs/mock_data.js
@@ -1,3 +1,4 @@
+import mockJobsCount from 'test_fixtures/graphql/jobs/get_jobs_count.query.graphql.json';
import mockJobsEmpty from 'test_fixtures/graphql/jobs/get_jobs.query.graphql.empty.json';
import mockJobsPaginated from 'test_fixtures/graphql/jobs/get_jobs.query.graphql.paginated.json';
import mockJobs from 'test_fixtures/graphql/jobs/get_jobs.query.graphql.json';
@@ -13,6 +14,7 @@ export const mockJobsResponsePaginated = mockJobsPaginated;
export const mockJobsResponseEmpty = mockJobsEmpty;
export const mockJobsNodes = mockJobs.data.project.jobs.nodes;
export const mockJobsNodesAsGuest = mockJobsAsGuest.data.project.jobs.nodes;
+export const mockJobsCountResponse = mockJobsCount;
export const stages = [
{
diff --git a/spec/frontend/labels/components/delete_label_modal_spec.js b/spec/frontend/labels/components/delete_label_modal_spec.js
index 24a803d3f16..7654d218209 100644
--- a/spec/frontend/labels/components/delete_label_modal_spec.js
+++ b/spec/frontend/labels/components/delete_label_modal_spec.js
@@ -30,10 +30,6 @@ describe('~/labels/components/delete_label_modal', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findModal = () => wrapper.findComponent(GlModal);
const findPrimaryModalButton = () => wrapper.findByTestId('delete-button');
diff --git a/spec/frontend/labels/components/promote_label_modal_spec.js b/spec/frontend/labels/components/promote_label_modal_spec.js
index 97913c20229..5983c16a9d1 100644
--- a/spec/frontend/labels/components/promote_label_modal_spec.js
+++ b/spec/frontend/labels/components/promote_label_modal_spec.js
@@ -41,7 +41,6 @@ describe('Promote label modal', () => {
afterEach(() => {
axiosMock.reset();
- wrapper.destroy();
});
describe('Modal title and description', () => {
diff --git a/spec/frontend/language_switcher/components/app_spec.js b/spec/frontend/language_switcher/components/app_spec.js
index 7f6fb138d89..036ff55fef7 100644
--- a/spec/frontend/language_switcher/components/app_spec.js
+++ b/spec/frontend/language_switcher/components/app_spec.js
@@ -24,10 +24,6 @@ describe('<LanguageSwitcher />', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const getPreferredLanguage = () => wrapper.find('.gl-new-dropdown-button-text').text();
const findLanguageDropdownItem = (code) => wrapper.findByTestId(`language_switcher_lang_${code}`);
const findFooter = () => wrapper.findByTestId('footer');
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 5ac7a7985a8..b8847f0fca3 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
@@ -6,13 +6,8 @@ import { isNavigatingAway } from '~/lib/utils/is_navigating_away';
jest.mock('~/lib/utils/is_navigating_away');
describe('getSuppressNetworkErrorsDuringNavigationLink', () => {
- const originalGon = window.gon;
let subscription;
- beforeEach(() => {
- window.gon = originalGon;
- });
-
afterEach(() => {
if (subscription) {
subscription.unsubscribe();
diff --git a/spec/frontend/lib/dompurify_spec.js b/spec/frontend/lib/dompurify_spec.js
index f767a673553..fdc8789c1a8 100644
--- a/spec/frontend/lib/dompurify_spec.js
+++ b/spec/frontend/lib/dompurify_spec.js
@@ -49,8 +49,6 @@ const forbiddenDataAttrs = defaultConfig.FORBID_ATTR;
const acceptedDataAttrs = ['data-random', 'data-custom'];
describe('~/lib/dompurify', () => {
- let originalGon;
-
it('uses local configuration when given', () => {
// As dompurify uses a "Persistent Configuration", it might
// ignore config, this check verifies we respect
@@ -104,15 +102,10 @@ describe('~/lib/dompurify', () => {
${'root'} | ${rootGon}
${'absolute'} | ${absoluteGon}
`('when gon contains $type icon urls', ({ type, gon }) => {
- beforeAll(() => {
- originalGon = window.gon;
+ beforeEach(() => {
window.gon = gon;
});
- afterAll(() => {
- window.gon = originalGon;
- });
-
it('allows no href attrs', () => {
const htmlHref = `<svg><use></use></svg>`;
expect(sanitize(htmlHref)).toBe(htmlHref);
@@ -137,14 +130,9 @@ describe('~/lib/dompurify', () => {
describe('when gon does not contain icon urls', () => {
beforeAll(() => {
- originalGon = window.gon;
window.gon = {};
});
- afterAll(() => {
- window.gon = originalGon;
- });
-
it.each([...safeUrls.root, ...safeUrls.absolute, ...unsafeUrls])('sanitizes URL %s', (url) => {
const htmlHref = `<svg><use href="${url}"></use></svg>`;
const htmlXlink = `<svg><use xlink:href="${url}"></use></svg>`;
diff --git a/spec/frontend/lib/utils/axios_startup_calls_spec.js b/spec/frontend/lib/utils/axios_startup_calls_spec.js
index 4471b781446..3d063ff9b46 100644
--- a/spec/frontend/lib/utils/axios_startup_calls_spec.js
+++ b/spec/frontend/lib/utils/axios_startup_calls_spec.js
@@ -113,17 +113,10 @@ describe('setupAxiosStartupCalls', () => {
});
describe('startup call', () => {
- let oldGon;
-
beforeEach(() => {
- oldGon = window.gon;
window.gon = { gitlab_url: 'https://example.org/gitlab' };
});
- afterEach(() => {
- window.gon = oldGon;
- });
-
it('removes GitLab Base URL from startup call', async () => {
window.gl.startup_calls = {
'/startup': {
diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js
index 7b068f7d248..b4ec00ab766 100644
--- a/spec/frontend/lib/utils/common_utils_spec.js
+++ b/spec/frontend/lib/utils/common_utils_spec.js
@@ -534,18 +534,10 @@ describe('common_utils', () => {
});
describe('spriteIcon', () => {
- let beforeGon;
-
beforeEach(() => {
- window.gon = window.gon || {};
- beforeGon = { ...window.gon };
window.gon.sprite_icons = 'icons.svg';
});
- afterEach(() => {
- window.gon = beforeGon;
- });
-
it('should return the svg for a linked icon', () => {
expect(commonUtils.spriteIcon('test')).toEqual(
'<svg ><use xlink:href="icons.svg#test" /></svg>',
diff --git a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js
index 142c76f7bc0..9b790e739fb 100644
--- a/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js
+++ b/spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js
@@ -44,7 +44,6 @@ describe('confirmAction', () => {
resetHTMLFixture();
Vue.prototype.$mount.mockRestore();
modalWrapper?.destroy();
- modalWrapper = null;
modal?.destroy();
modal = null;
});
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 313e028d861..c135180c9df 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
@@ -28,10 +28,6 @@ describe('Confirm Modal', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGlModal = () => wrapper.findComponent(GlModal);
describe('Modal events', () => {
diff --git a/spec/frontend/lib/utils/datetime/timeago_utility_spec.js b/spec/frontend/lib/utils/datetime/timeago_utility_spec.js
index 1ef7047d959..c13d55f978e 100644
--- a/spec/frontend/lib/utils/datetime/timeago_utility_spec.js
+++ b/spec/frontend/lib/utils/datetime/timeago_utility_spec.js
@@ -3,16 +3,6 @@ import { s__ } from '~/locale';
import '~/commons/bootstrap';
describe('TimeAgo utils', () => {
- let oldGon;
-
- afterEach(() => {
- window.gon = oldGon;
- });
-
- beforeEach(() => {
- oldGon = window.gon;
- });
-
describe('getTimeago', () => {
describe('with User Setting timeDisplayRelative: true', () => {
beforeEach(() => {
diff --git a/spec/frontend/lib/utils/error_message_spec.js b/spec/frontend/lib/utils/error_message_spec.js
new file mode 100644
index 00000000000..17b5168c32f
--- /dev/null
+++ b/spec/frontend/lib/utils/error_message_spec.js
@@ -0,0 +1,65 @@
+import { parseErrorMessage, USER_FACING_ERROR_MESSAGE_PREFIX } from '~/lib/utils/error_message';
+
+const defaultErrorMessage = 'Something caused this error';
+const userFacingErrorMessage = 'User facing error message';
+const nonUserFacingErrorMessage = 'NonUser facing error message';
+const genericErrorMessage = 'Some error message';
+
+describe('error message', () => {
+ describe('when given an errormessage object', () => {
+ const errorMessageObject = {
+ options: {
+ cause: defaultErrorMessage,
+ },
+ filename: 'error.js',
+ linenumber: 7,
+ };
+
+ it('returns the correct values for userfacing errors', () => {
+ const userFacingObject = errorMessageObject;
+ userFacingObject.message = `${USER_FACING_ERROR_MESSAGE_PREFIX} ${userFacingErrorMessage}`;
+
+ expect(parseErrorMessage(userFacingObject)).toEqual({
+ message: userFacingErrorMessage,
+ userFacing: true,
+ });
+ });
+
+ it('returns the correct values for non userfacing errors', () => {
+ const nonUserFacingObject = errorMessageObject;
+ nonUserFacingObject.message = nonUserFacingErrorMessage;
+
+ expect(parseErrorMessage(nonUserFacingObject)).toEqual({
+ message: nonUserFacingErrorMessage,
+ userFacing: false,
+ });
+ });
+ });
+
+ describe('when given an errormessage string', () => {
+ it('returns the correct values for userfacing errors', () => {
+ expect(
+ parseErrorMessage(`${USER_FACING_ERROR_MESSAGE_PREFIX} ${genericErrorMessage}`),
+ ).toEqual({
+ message: genericErrorMessage,
+ userFacing: true,
+ });
+ });
+
+ it('returns the correct values for non userfacing errors', () => {
+ expect(parseErrorMessage(genericErrorMessage)).toEqual({
+ message: genericErrorMessage,
+ userFacing: false,
+ });
+ });
+ });
+
+ describe('when given nothing', () => {
+ it('returns an empty error message', () => {
+ expect(parseErrorMessage()).toEqual({
+ message: '',
+ userFacing: false,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/file_upload_spec.js b/spec/frontend/lib/utils/file_upload_spec.js
index f63af2fe0a4..509ddc7ce86 100644
--- a/spec/frontend/lib/utils/file_upload_spec.js
+++ b/spec/frontend/lib/utils/file_upload_spec.js
@@ -1,5 +1,9 @@
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
-import fileUpload, { getFilename, validateImageName } from '~/lib/utils/file_upload';
+import fileUpload, {
+ getFilename,
+ validateImageName,
+ validateFileFromAllowList,
+} from '~/lib/utils/file_upload';
describe('File upload', () => {
beforeEach(() => {
@@ -89,3 +93,19 @@ describe('file name validator', () => {
expect(validateImageName(file)).toBe('image.png');
});
});
+
+describe('validateFileFromAllowList', () => {
+ it('returns true if the file type is in the allowed list', () => {
+ const allowList = ['.foo', '.bar'];
+ const fileName = 'file.foo';
+
+ expect(validateFileFromAllowList(fileName, allowList)).toBe(true);
+ });
+
+ it('returns false if the file type is in the allowed list', () => {
+ const allowList = ['.foo', '.bar'];
+ const fileName = 'file.baz';
+
+ expect(validateFileFromAllowList(fileName, allowList)).toBe(false);
+ });
+});
diff --git a/spec/frontend/lib/utils/number_utility_spec.js b/spec/frontend/lib/utils/number_utility_spec.js
index dc4aa0ea5ed..d2591cd2328 100644
--- a/spec/frontend/lib/utils/number_utility_spec.js
+++ b/spec/frontend/lib/utils/number_utility_spec.js
@@ -3,6 +3,7 @@ import {
bytesToKiB,
bytesToMiB,
bytesToGiB,
+ numberToHumanSizeSplit,
numberToHumanSize,
numberToMetricPrefix,
sum,
@@ -13,6 +14,12 @@ import {
isNumeric,
isPositiveInteger,
} from '~/lib/utils/number_utils';
+import {
+ BYTES_FORMAT_BYTES,
+ BYTES_FORMAT_KIB,
+ BYTES_FORMAT_MIB,
+ BYTES_FORMAT_GIB,
+} from '~/lib/utils/constants';
describe('Number Utils', () => {
describe('formatRelevantDigits', () => {
@@ -78,6 +85,28 @@ describe('Number Utils', () => {
});
});
+ describe('numberToHumanSizeSplit', () => {
+ it('should return bytes', () => {
+ expect(numberToHumanSizeSplit(654)).toEqual(['654', BYTES_FORMAT_BYTES]);
+ expect(numberToHumanSizeSplit(-654)).toEqual(['-654', BYTES_FORMAT_BYTES]);
+ });
+
+ it('should return KiB', () => {
+ expect(numberToHumanSizeSplit(1079)).toEqual(['1.05', BYTES_FORMAT_KIB]);
+ expect(numberToHumanSizeSplit(-1079)).toEqual(['-1.05', BYTES_FORMAT_KIB]);
+ });
+
+ it('should return MiB', () => {
+ expect(numberToHumanSizeSplit(10485764)).toEqual(['10.00', BYTES_FORMAT_MIB]);
+ expect(numberToHumanSizeSplit(-10485764)).toEqual(['-10.00', BYTES_FORMAT_MIB]);
+ });
+
+ it('should return GiB', () => {
+ expect(numberToHumanSizeSplit(10737418240)).toEqual(['10.00', BYTES_FORMAT_GIB]);
+ expect(numberToHumanSizeSplit(-10737418240)).toEqual(['-10.00', BYTES_FORMAT_GIB]);
+ });
+ });
+
describe('numberToHumanSize', () => {
it('should return bytes', () => {
expect(numberToHumanSize(654)).toEqual('654 bytes');
diff --git a/spec/frontend/lib/utils/ref_validator_spec.js b/spec/frontend/lib/utils/ref_validator_spec.js
new file mode 100644
index 00000000000..7185ebf0a24
--- /dev/null
+++ b/spec/frontend/lib/utils/ref_validator_spec.js
@@ -0,0 +1,79 @@
+import { validateTag, validationMessages } from '~/lib/utils/ref_validator';
+
+describe('~/lib/utils/ref_validator', () => {
+ describe('validateTag', () => {
+ describe.each([
+ ['foo'],
+ ['FOO'],
+ ['foo/a.lockx'],
+ ['foo.123'],
+ ['foo/123'],
+ ['foo/bar/123'],
+ ['foo.bar.123'],
+ ['foo-bar_baz'],
+ ['head'],
+ ['"foo"-'],
+ ['foo@bar'],
+ ['\ud83e\udd8a'],
+ ['ünicöde'],
+ ['\x80}'],
+ ])('tag with the name "%s"', (tagName) => {
+ it('is valid', () => {
+ const result = validateTag(tagName);
+ expect(result.isValid).toBe(true);
+ expect(result.validationErrors).toEqual([]);
+ });
+ });
+
+ describe.each([
+ [' ', validationMessages.EmptyNameValidationMessage],
+
+ ['refs/heads/tagName', validationMessages.DisallowedPrefixesValidationMessage],
+ ['/foo', validationMessages.DisallowedPrefixesValidationMessage],
+ ['-tagName', validationMessages.DisallowedPrefixesValidationMessage],
+
+ ['HEAD', validationMessages.DisallowedNameValidationMessage],
+ ['@', validationMessages.DisallowedNameValidationMessage],
+
+ ['tag name with spaces', validationMessages.DisallowedSubstringsValidationMessage],
+ ['tag\\name', validationMessages.DisallowedSubstringsValidationMessage],
+ ['tag^name', validationMessages.DisallowedSubstringsValidationMessage],
+ ['tag..name', validationMessages.DisallowedSubstringsValidationMessage],
+ ['..', validationMessages.DisallowedSubstringsValidationMessage],
+ ['tag?name', validationMessages.DisallowedSubstringsValidationMessage],
+ ['tag*name', validationMessages.DisallowedSubstringsValidationMessage],
+ ['tag[name', validationMessages.DisallowedSubstringsValidationMessage],
+ ['tag@{name', validationMessages.DisallowedSubstringsValidationMessage],
+ ['tag:name', validationMessages.DisallowedSubstringsValidationMessage],
+ ['tag~name', validationMessages.DisallowedSubstringsValidationMessage],
+
+ ['/', validationMessages.DisallowedSequenceEmptyValidationMessage],
+ ['//', validationMessages.DisallowedSequenceEmptyValidationMessage],
+ ['foo//123', validationMessages.DisallowedSequenceEmptyValidationMessage],
+
+ ['.', validationMessages.DisallowedSequencePrefixesValidationMessage],
+ ['/./', validationMessages.DisallowedSequencePrefixesValidationMessage],
+ ['./.', validationMessages.DisallowedSequencePrefixesValidationMessage],
+ ['.tagName', validationMessages.DisallowedSequencePrefixesValidationMessage],
+ ['tag/.Name', validationMessages.DisallowedSequencePrefixesValidationMessage],
+ ['foo/.123/bar', validationMessages.DisallowedSequencePrefixesValidationMessage],
+
+ ['foo.', validationMessages.DisallowedSequencePostfixesValidationMessage],
+ ['a.lock', validationMessages.DisallowedSequencePostfixesValidationMessage],
+ ['foo/a.lock', validationMessages.DisallowedSequencePostfixesValidationMessage],
+ ['foo/a.lock/b', validationMessages.DisallowedSequencePostfixesValidationMessage],
+ ['foo.123.', validationMessages.DisallowedSequencePostfixesValidationMessage],
+
+ ['foo/', validationMessages.DisallowedPostfixesValidationMessage],
+
+ ['control-character\x7f', validationMessages.ControlCharactersValidationMessage],
+ ['control-character\x15', validationMessages.ControlCharactersValidationMessage],
+ ])('tag with name "%s"', (tagName, validationMessage) => {
+ it(`should be invalid with validation message "${validationMessage}"`, () => {
+ const result = validateTag(tagName);
+ expect(result.isValid).toBe(false);
+ expect(result.validationErrors).toContain(validationMessage);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/text_markdown_spec.js b/spec/frontend/lib/utils/text_markdown_spec.js
index 7aab1013fc0..2180ea7e6c2 100644
--- a/spec/frontend/lib/utils/text_markdown_spec.js
+++ b/spec/frontend/lib/utils/text_markdown_spec.js
@@ -1,12 +1,16 @@
import $ from 'jquery';
+import AxiosMockAdapter from 'axios-mock-adapter';
import {
insertMarkdownText,
keypressNoteText,
compositionStartNoteText,
compositionEndNoteText,
updateTextForToolbarBtn,
+ resolveSelectedImage,
} from '~/lib/utils/text_markdown';
+import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import '~/lib/utils/jquery_at_who';
+import axios from '~/lib/utils/axios_utils';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
describe('init markdown', () => {
@@ -14,6 +18,7 @@ describe('init markdown', () => {
let textArea;
let indentButton;
let outdentButton;
+ let axiosMock;
beforeAll(() => {
setHTMLFixture(
@@ -34,6 +39,14 @@ describe('init markdown', () => {
document.execCommand = jest.fn(() => false);
});
+ beforeEach(() => {
+ axiosMock = new AxiosMockAdapter(axios);
+ });
+
+ afterEach(() => {
+ axiosMock.restore();
+ });
+
afterAll(() => {
resetHTMLFixture();
});
@@ -707,6 +720,55 @@ describe('init markdown', () => {
});
});
+ describe('resolveSelectedImage', () => {
+ const markdownPreviewPath = '/markdown/preview';
+ const imageMarkdown = '![image](/uploads/image.png)';
+ const imageAbsoluteUrl = '/abs/uploads/image.png';
+
+ describe('when textarea cursor is positioned on an image', () => {
+ beforeEach(() => {
+ axiosMock.onPost(markdownPreviewPath, { text: imageMarkdown }).reply(HTTP_STATUS_OK, {
+ body: `
+ <p><a href="${imageAbsoluteUrl}"><img src="${imageAbsoluteUrl}"></a></p>
+ `,
+ });
+ });
+
+ it('returns the image absolute URL, markdown, and filename', async () => {
+ textArea.value = `image ${imageMarkdown}`;
+ textArea.setSelectionRange(8, 8);
+ expect(await resolveSelectedImage(textArea, markdownPreviewPath)).toEqual({
+ imageURL: imageAbsoluteUrl,
+ imageMarkdown,
+ filename: 'image.png',
+ });
+ });
+ });
+
+ describe('when textarea cursor is not positioned on an image', () => {
+ it.each`
+ markdown | selectionRange
+ ${`image ${imageMarkdown}`} | ${[4, 4]}
+ ${`!2 (issue)`} | ${[2, 2]}
+ `('returns null', async ({ markdown, selectionRange }) => {
+ textArea.value = markdown;
+ textArea.setSelectionRange(...selectionRange);
+ expect(await resolveSelectedImage(textArea, markdownPreviewPath)).toBe(null);
+ });
+ });
+
+ describe('when textarea cursor is positioned between images', () => {
+ it('returns null', async () => {
+ const position = imageMarkdown.length + 1;
+
+ textArea.value = `${imageMarkdown}\n\n${imageMarkdown}`;
+ textArea.setSelectionRange(position, position);
+
+ expect(await resolveSelectedImage(textArea, markdownPreviewPath)).toBe(null);
+ });
+ });
+ });
+
describe('Source Editor', () => {
let editor;
diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js
index f2572ca0ad2..71a84d56791 100644
--- a/spec/frontend/lib/utils/text_utility_spec.js
+++ b/spec/frontend/lib/utils/text_utility_spec.js
@@ -398,4 +398,36 @@ describe('text_utility', () => {
expect(textUtils.base64DecodeUnicode('8J+YgA==')).toBe('😀');
});
});
+
+ describe('findInvalidBranchNameCharacters', () => {
+ const invalidChars = [' ', '~', '^', ':', '?', '*', '[', '..', '@{', '\\', '//'];
+ const badBranchName = 'branch-with all these ~ ^ : ? * [ ] \\ // .. @{ } //';
+ const goodBranch = 'branch-with-no-errrors';
+
+ it('returns an array of invalid characters in a branch name', () => {
+ const chars = textUtils.findInvalidBranchNameCharacters(badBranchName);
+ chars.forEach((char) => {
+ expect(invalidChars).toContain(char);
+ });
+ });
+
+ it('returns an empty array with no invalid characters', () => {
+ expect(textUtils.findInvalidBranchNameCharacters(goodBranch)).toEqual([]);
+ });
+ });
+
+ describe('humanizeBranchValidationErrors', () => {
+ it.each`
+ errors | message
+ ${[' ']} | ${"Can't contain spaces"}
+ ${['?', '//', ' ']} | ${"Can't contain spaces, ?, //"}
+ ${['\\', '[', '..']} | ${"Can't contain \\, [, .."}
+ `('returns an $message with $errors', ({ errors, message }) => {
+ expect(textUtils.humanizeBranchValidationErrors(errors)).toEqual(message);
+ });
+
+ it('returns an empty string with no invalid characters', () => {
+ expect(textUtils.humanizeBranchValidationErrors([])).toEqual('');
+ });
+ });
});
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index 6afdab455a6..72556e6bbe2 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -45,10 +45,6 @@ describe('URL utility', () => {
});
describe('webIDEUrl', () => {
- afterEach(() => {
- gon.relative_url_root = '';
- });
-
it('escapes special characters', () => {
expect(urlUtils.webIDEUrl('/gitlab-org/gitlab-#-foss/merge_requests/1')).toBe(
'/-/ide/project/gitlab-org/gitlab-%23-foss/merge_requests/1',
@@ -505,10 +501,6 @@ describe('URL utility', () => {
gon.gitlab_url = gitlabUrl;
});
- afterEach(() => {
- gon.gitlab_url = '';
- });
-
it.each`
url | urlType | external
${'/gitlab-org/gitlab-test/-/issues/2'} | ${'relative'} | ${false}
diff --git a/spec/frontend/lib/utils/vuex_module_mappers_spec.js b/spec/frontend/lib/utils/vuex_module_mappers_spec.js
index d25a692dfea..abd5095c1d2 100644
--- a/spec/frontend/lib/utils/vuex_module_mappers_spec.js
+++ b/spec/frontend/lib/utils/vuex_module_mappers_spec.js
@@ -96,10 +96,6 @@ describe('~/lib/utils/vuex_module_mappers', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('from module defined by prop', () => {
it('maps state', () => {
expect(getMappedState()).toEqual({
diff --git a/spec/frontend/locale/sprintf_spec.js b/spec/frontend/locale/sprintf_spec.js
index e0d0e117ea4..a7e245e2b78 100644
--- a/spec/frontend/locale/sprintf_spec.js
+++ b/spec/frontend/locale/sprintf_spec.js
@@ -84,5 +84,16 @@ describe('locale', () => {
expect(output).toBe('contains duplicated 15%');
});
});
+
+ describe('ignores special replacements in the input', () => {
+ it.each(['$$', '$&', '$`', `$'`])('replacement "%s" is ignored', (replacement) => {
+ const input = 'My odd %{replacement} is preserved';
+
+ const parameters = { replacement };
+
+ const output = sprintf(input, parameters, false);
+ expect(output).toBe(`My odd ${replacement} is preserved`);
+ });
+ });
});
});
diff --git a/spec/frontend/members/components/action_buttons/access_request_action_buttons_spec.js b/spec/frontend/members/components/action_buttons/access_request_action_buttons_spec.js
index b94964dc482..c2e0e44f97f 100644
--- a/spec/frontend/members/components/action_buttons/access_request_action_buttons_spec.js
+++ b/spec/frontend/members/components/action_buttons/access_request_action_buttons_spec.js
@@ -20,10 +20,6 @@ describe('AccessRequestActionButtons', () => {
const findRemoveMemberButton = () => wrapper.findComponent(RemoveMemberButton);
const findApproveButton = () => wrapper.findComponent(ApproveAccessRequestButton);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders remove member button', () => {
createComponent();
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 15bb03480e1..7a4cd844425 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
@@ -38,7 +38,7 @@ describe('ApproveAccessRequestButton', () => {
...propsData,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
@@ -50,10 +50,6 @@ describe('ApproveAccessRequestButton', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays a tooltip', () => {
const button = findButton();
diff --git a/spec/frontend/members/components/action_buttons/invite_action_buttons_spec.js b/spec/frontend/members/components/action_buttons/invite_action_buttons_spec.js
index 68009708c99..a852443844b 100644
--- a/spec/frontend/members/components/action_buttons/invite_action_buttons_spec.js
+++ b/spec/frontend/members/components/action_buttons/invite_action_buttons_spec.js
@@ -19,10 +19,6 @@ describe('InviteActionButtons', () => {
const findRemoveMemberButton = () => wrapper.findComponent(RemoveMemberButton);
const findResendInviteButton = () => wrapper.findComponent(ResendInviteButton);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when user has `canRemove` permissions', () => {
beforeEach(() => {
createComponent({
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 b511cebdf28..1d83a2e0e71 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
@@ -37,7 +37,7 @@ describe('RemoveGroupLinkButton', () => {
groupLink: group,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
@@ -48,11 +48,6 @@ describe('RemoveGroupLinkButton', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('displays a tooltip', () => {
const button = findButton();
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 cca340169b7..3879279b559 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
@@ -47,7 +47,7 @@ describe('RemoveMemberButton', () => {
...propsData,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
@@ -58,10 +58,6 @@ describe('RemoveMemberButton', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('sets attributes on button', () => {
expect(wrapper.attributes()).toMatchObject({
'aria-label': 'Remove member',
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 51cfd47ddf4..a6b5978b566 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
@@ -38,7 +38,7 @@ describe('ResendInviteButton', () => {
...propsData,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
@@ -50,10 +50,6 @@ describe('ResendInviteButton', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays a tooltip', () => {
expect(getBinding(findButton().element, 'gl-tooltip')).not.toBeUndefined();
expect(findButton().attributes('title')).toBe('Resend invite');
diff --git a/spec/frontend/members/components/action_dropdowns/leave_group_dropdown_item_spec.js b/spec/frontend/members/components/action_dropdowns/leave_group_dropdown_item_spec.js
index 90f5b217007..679ad7897ed 100644
--- a/spec/frontend/members/components/action_dropdowns/leave_group_dropdown_item_spec.js
+++ b/spec/frontend/members/components/action_dropdowns/leave_group_dropdown_item_spec.js
@@ -18,7 +18,7 @@ describe('LeaveGroupDropdownItem', () => {
...propsData,
},
directives: {
- GlModal: createMockDirective(),
+ GlModal: createMockDirective('gl-modal'),
},
slots: {
default: text,
@@ -32,10 +32,6 @@ describe('LeaveGroupDropdownItem', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a slot with red text', () => {
expect(findDropdownItem().html()).toContain(`<span class="gl-text-red-500">${text}</span>`);
});
diff --git a/spec/frontend/members/components/action_dropdowns/remove_member_dropdown_item_spec.js b/spec/frontend/members/components/action_dropdowns/remove_member_dropdown_item_spec.js
index e1c498249d7..125f1f8fff3 100644
--- a/spec/frontend/members/components/action_dropdowns/remove_member_dropdown_item_spec.js
+++ b/spec/frontend/members/components/action_dropdowns/remove_member_dropdown_item_spec.js
@@ -58,10 +58,6 @@ describe('RemoveMemberDropdownItem', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a slot with red text', () => {
expect(findDropdownItem().html()).toContain(`<span class="gl-text-red-500">${text}</span>`);
});
diff --git a/spec/frontend/members/components/action_dropdowns/user_action_dropdown_spec.js b/spec/frontend/members/components/action_dropdowns/user_action_dropdown_spec.js
index 5a2de1cac80..448c04bcb69 100644
--- a/spec/frontend/members/components/action_dropdowns/user_action_dropdown_spec.js
+++ b/spec/frontend/members/components/action_dropdowns/user_action_dropdown_spec.js
@@ -24,17 +24,13 @@ describe('UserActionDropdown', () => {
...propsData,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
const findRemoveMemberDropdownItem = () => wrapper.findComponent(RemoveMemberDropdownItem);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when user has `canRemove` permissions', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/members/components/app_spec.js b/spec/frontend/members/components/app_spec.js
index d105a4d9fde..b2147163233 100644
--- a/spec/frontend/members/components/app_spec.js
+++ b/spec/frontend/members/components/app_spec.js
@@ -49,7 +49,6 @@ describe('MembersApp', () => {
});
afterEach(() => {
- wrapper.destroy();
store = null;
});
diff --git a/spec/frontend/members/components/avatars/group_avatar_spec.js b/spec/frontend/members/components/avatars/group_avatar_spec.js
index 13c50de9835..8e4263f88fe 100644
--- a/spec/frontend/members/components/avatars/group_avatar_spec.js
+++ b/spec/frontend/members/components/avatars/group_avatar_spec.js
@@ -25,10 +25,6 @@ describe('MemberList', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders link to group', () => {
const link = wrapper.findComponent(GlAvatarLink);
diff --git a/spec/frontend/members/components/avatars/invite_avatar_spec.js b/spec/frontend/members/components/avatars/invite_avatar_spec.js
index b197a46c0d1..84878fb9be2 100644
--- a/spec/frontend/members/components/avatars/invite_avatar_spec.js
+++ b/spec/frontend/members/components/avatars/invite_avatar_spec.js
@@ -24,10 +24,6 @@ describe('MemberList', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders email as name', () => {
expect(getByText(invite.email).exists()).toBe(true);
});
diff --git a/spec/frontend/members/components/avatars/user_avatar_spec.js b/spec/frontend/members/components/avatars/user_avatar_spec.js
index 9172876e76f..4808bcb9363 100644
--- a/spec/frontend/members/components/avatars/user_avatar_spec.js
+++ b/spec/frontend/members/components/avatars/user_avatar_spec.js
@@ -26,10 +26,6 @@ describe('UserAvatar', () => {
const findStatusEmoji = (emoji) => wrapper.find(`gl-emoji[data-name="${emoji}"]`);
- afterEach(() => {
- wrapper.destroy();
- });
-
it("renders link to user's profile", () => {
createComponent();
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 ef3c8bde3cf..526f839ece8 100644
--- a/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js
+++ b/spec/frontend/members/components/filter_sort/sort_dropdown_spec.js
@@ -46,7 +46,7 @@ describe('SortDropdown', () => {
const findSortingComponent = () => wrapper.findComponent(GlSorting);
const findSortDirectionToggle = () =>
findSortingComponent().find('button[title^="Sort direction"]');
- const findDropdownToggle = () => wrapper.find('button[aria-haspopup="true"]');
+ const findDropdownToggle = () => wrapper.find('button[aria-haspopup="menu"]');
const findDropdownItemByText = (text) =>
wrapper
.findAllComponents(GlSortingItem)
diff --git a/spec/frontend/members/components/members_tabs_spec.js b/spec/frontend/members/components/members_tabs_spec.js
index 77af5e7293e..9078bd87d62 100644
--- a/spec/frontend/members/components/members_tabs_spec.js
+++ b/spec/frontend/members/components/members_tabs_spec.js
@@ -100,10 +100,6 @@ describe('MembersTabs', () => {
setWindowLocation('https://localhost');
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders `GlTabs` with `syncActiveTabWithQueryParams` and `queryParamName` props set', async () => {
await createComponent();
diff --git a/spec/frontend/members/components/modals/leave_modal_spec.js b/spec/frontend/members/components/modals/leave_modal_spec.js
index ba587c6f0b3..cec5f192e59 100644
--- a/spec/frontend/members/components/modals/leave_modal_spec.js
+++ b/spec/frontend/members/components/modals/leave_modal_spec.js
@@ -60,10 +60,6 @@ describe('LeaveModal', () => {
const findForm = () => findModal().findComponent(GlForm);
const findUserDeletionObstaclesList = () => findModal().findComponent(UserDeletionObstaclesList);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('sets modal ID', async () => {
await createComponent();
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 af96396f09f..e4782ac7f2e 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
@@ -52,11 +52,6 @@ describe('RemoveGroupLinkModal', () => {
const getByText = (text, options) =>
createWrapper(within(findModal().element).getByText(text, options));
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when modal is open', () => {
beforeEach(async () => {
createComponent();
diff --git a/spec/frontend/members/components/modals/remove_member_modal_spec.js b/spec/frontend/members/components/modals/remove_member_modal_spec.js
index 47a03b5083a..baef0b30b02 100644
--- a/spec/frontend/members/components/modals/remove_member_modal_spec.js
+++ b/spec/frontend/members/components/modals/remove_member_modal_spec.js
@@ -54,10 +54,6 @@ describe('RemoveMemberModal', () => {
const findGlModal = () => wrapper.findComponent(GlModal);
const findUserDeletionObstaclesList = () => wrapper.findComponent(UserDeletionObstaclesList);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe.each`
state | memberModelType | isAccessRequest | isInvite | actionText | removeSubMembershipsCheckboxExpected | unassignIssuablesCheckboxExpected | message | userDeletionObstacles | isPartOfOncall
${'removing a group member'} | ${MEMBER_MODEL_TYPE_GROUP_MEMBER} | ${false} | ${false} | ${'Remove member'} | ${true} | ${true} | ${'Are you sure you want to remove Jane Doe from the Gitlab Org / Gitlab Test project?'} | ${{}} | ${false}
diff --git a/spec/frontend/members/components/table/created_at_spec.js b/spec/frontend/members/components/table/created_at_spec.js
index fa31177564b..2c0493e7c59 100644
--- a/spec/frontend/members/components/table/created_at_spec.js
+++ b/spec/frontend/members/components/table/created_at_spec.js
@@ -20,10 +20,6 @@ describe('CreatedAt', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('created at text', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/members/components/table/expiration_datepicker_spec.js b/spec/frontend/members/components/table/expiration_datepicker_spec.js
index 9b8f053348b..15812ee6572 100644
--- a/spec/frontend/members/components/table/expiration_datepicker_spec.js
+++ b/spec/frontend/members/components/table/expiration_datepicker_spec.js
@@ -58,10 +58,6 @@ describe('ExpirationDatepicker', () => {
const findInput = () => wrapper.find('input');
const findDatepicker = () => wrapper.findComponent(GlDatepicker);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('datepicker input', () => {
it('sets `member.expiresAt` as initial date', async () => {
createComponent({ member: { ...member, expiresAt: '2020-03-17T00:00:00Z' } });
diff --git a/spec/frontend/members/components/table/member_action_buttons_spec.js b/spec/frontend/members/components/table/member_action_buttons_spec.js
index 95db30a3683..3a04d1dcb0a 100644
--- a/spec/frontend/members/components/table/member_action_buttons_spec.js
+++ b/spec/frontend/members/components/table/member_action_buttons_spec.js
@@ -23,10 +23,6 @@ describe('MemberActions', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
memberType | member | expectedComponent | expectedComponentName
${MEMBER_TYPES.user} | ${memberMock} | ${UserActionDropdown} | ${'UserActionDropdown'}
diff --git a/spec/frontend/members/components/table/member_avatar_spec.js b/spec/frontend/members/components/table/member_avatar_spec.js
index dc5c97f41df..369f8a06cfd 100644
--- a/spec/frontend/members/components/table/member_avatar_spec.js
+++ b/spec/frontend/members/components/table/member_avatar_spec.js
@@ -18,10 +18,6 @@ describe('MemberList', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
memberType | member | expectedComponent | expectedComponentName
${MEMBER_TYPES.user} | ${memberMock} | ${UserAvatar} | ${'UserAvatar'}
diff --git a/spec/frontend/members/components/table/member_source_spec.js b/spec/frontend/members/components/table/member_source_spec.js
index fbfd0ca7ae7..bbfbb19fd92 100644
--- a/spec/frontend/members/components/table/member_source_spec.js
+++ b/spec/frontend/members/components/table/member_source_spec.js
@@ -23,17 +23,13 @@ describe('MemberSource', () => {
...propsData,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
const getTooltipDirective = (elementWrapper) => getBinding(elementWrapper.element, 'gl-tooltip');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('direct member', () => {
describe('when created by is available', () => {
it('displays "Direct member by <user name>"', () => {
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 ac5d83d028d..1c6f1b086cf 100644
--- a/spec/frontend/members/components/table/members_table_cell_spec.js
+++ b/spec/frontend/members/components/table/members_table_cell_spec.js
@@ -97,11 +97,6 @@ describe('MembersTableCell', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it.each`
member | expectedMemberType
${memberMock} | ${MEMBER_TYPES.user}
diff --git a/spec/frontend/members/components/table/members_table_spec.js b/spec/frontend/members/components/table/members_table_spec.js
index b8e0d73d8f6..e3c89bfed53 100644
--- a/spec/frontend/members/components/table/members_table_spec.js
+++ b/spec/frontend/members/components/table/members_table_spec.js
@@ -96,10 +96,6 @@ describe('MembersTable', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('fields', () => {
const memberCanUpdate = {
...directMember,
diff --git a/spec/frontend/members/components/table/role_dropdown_spec.js b/spec/frontend/members/components/table/role_dropdown_spec.js
index a11f67be8f5..d6e63b1930f 100644
--- a/spec/frontend/members/components/table/role_dropdown_spec.js
+++ b/spec/frontend/members/components/table/role_dropdown_spec.js
@@ -67,21 +67,13 @@ describe('RoleDropdown', () => {
.findAllComponents(GlDropdownItem)
.wrappers.find((dropdownItemWrapper) => dropdownItemWrapper.props('isChecked'));
- const findDropdownToggle = () => wrapper.find('button[aria-haspopup="true"]');
+ const findDropdownToggle = () => wrapper.find('button[aria-haspopup="menu"]');
const findDropdown = () => wrapper.findComponent(GlDropdown);
- let originalGon;
-
beforeEach(() => {
- originalGon = window.gon;
gon.features = { showOverageOnRolePromotion: true };
});
- afterEach(() => {
- window.gon = originalGon;
- wrapper.destroy();
- });
-
describe('when dropdown is open', () => {
beforeEach(() => {
guestOverageConfirmAction.mockReturnValue(true);
diff --git a/spec/frontend/members/index_spec.js b/spec/frontend/members/index_spec.js
index 5c813eb2a67..b1730cf3746 100644
--- a/spec/frontend/members/index_spec.js
+++ b/spec/frontend/members/index_spec.js
@@ -31,9 +31,6 @@ describe('initMembersApp', () => {
afterEach(() => {
el = null;
-
- wrapper.destroy();
- wrapper = null;
});
it('renders `MembersTabs`', () => {
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 9b5641ef7b3..ab913b30f3c 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
@@ -37,10 +37,6 @@ describe('Merge Conflict Resolver App', () => {
store.dispatch('setConflictsData', conflictsMock);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findLoadingSpinner = () => wrapper.findByTestId('loading-spinner');
const findConflictsCount = () => wrapper.findByTestId('conflicts-count');
const findFiles = () => wrapper.findAllByTestId('files');
diff --git a/spec/frontend/merge_conflicts/store/actions_spec.js b/spec/frontend/merge_conflicts/store/actions_spec.js
index 19ef4b7db25..d2c4c8b796c 100644
--- a/spec/frontend/merge_conflicts/store/actions_spec.js
+++ b/spec/frontend/merge_conflicts/store/actions_spec.js
@@ -4,13 +4,13 @@ import Cookies from '~/lib/utils/cookies';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import testAction from 'helpers/vuex_action_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { INTERACTIVE_RESOLVE_MODE, EDIT_RESOLVE_MODE } from '~/merge_conflicts/constants';
import * as actions from '~/merge_conflicts/store/actions';
import * as types from '~/merge_conflicts/store/mutation_types';
import { restoreFileLinesState, markLine, decorateFiles } from '~/merge_conflicts/utils';
-jest.mock('~/flash.js');
+jest.mock('~/alert');
jest.mock('~/merge_conflicts/utils');
jest.mock('~/lib/utils/cookies');
@@ -114,7 +114,7 @@ describe('merge conflicts actions', () => {
expect(window.location.assign).toHaveBeenCalledWith('hrefPath');
});
- it('on errors shows flash', async () => {
+ it('on errors shows an alert', async () => {
mock.onPost(resolveConflictsPath).reply(HTTP_STATUS_BAD_REQUEST);
await testAction(
actions.submitResolvedConflicts,
diff --git a/spec/frontend/merge_request_spec.js b/spec/frontend/merge_request_spec.js
index 579cee8c022..be16b5ebfd2 100644
--- a/spec/frontend/merge_request_spec.js
+++ b/spec/frontend/merge_request_spec.js
@@ -3,12 +3,12 @@ import $ from 'jquery';
import { loadHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'spec/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_CONFLICT, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import MergeRequest from '~/merge_request';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('MergeRequest', () => {
const test = {};
diff --git a/spec/frontend/merge_request_tabs_spec.js b/spec/frontend/merge_request_tabs_spec.js
index 6d434d7e654..76d1fa3a332 100644
--- a/spec/frontend/merge_request_tabs_spec.js
+++ b/spec/frontend/merge_request_tabs_spec.js
@@ -354,8 +354,6 @@ describe('MergeRequestTabs', () => {
testContext.class.expandSidebar.forEach((el) => {
expect(el.classList.contains('gl-display-none!')).toBe(hides);
});
-
- window.gon = {};
});
describe('when switching tabs', () => {
diff --git a/spec/frontend/merge_requests/components/compare_app_spec.js b/spec/frontend/merge_requests/components/compare_app_spec.js
index 8f84341b653..ba129363ffd 100644
--- a/spec/frontend/merge_requests/components/compare_app_spec.js
+++ b/spec/frontend/merge_requests/components/compare_app_spec.js
@@ -30,10 +30,6 @@ function factory(provideData = {}) {
}
describe('Merge requests compare app component', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it('shows commit box when selected branch is empty', () => {
factory({
currentBranch: {
diff --git a/spec/frontend/merge_requests/components/compare_dropdown_spec.js b/spec/frontend/merge_requests/components/compare_dropdown_spec.js
index ab5c315816c..ce03b80bdcb 100644
--- a/spec/frontend/merge_requests/components/compare_dropdown_spec.js
+++ b/spec/frontend/merge_requests/components/compare_dropdown_spec.js
@@ -47,7 +47,6 @@ describe('Merge requests compare dropdown component', () => {
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
});
diff --git a/spec/frontend/milestones/components/delete_milestone_modal_spec.js b/spec/frontend/milestones/components/delete_milestone_modal_spec.js
index 87235fa843a..f8730fd93a3 100644
--- a/spec/frontend/milestones/components/delete_milestone_modal_spec.js
+++ b/spec/frontend/milestones/components/delete_milestone_modal_spec.js
@@ -6,10 +6,10 @@ import DeleteMilestoneModal from '~/milestones/components/delete_milestone_modal
import eventHub from '~/milestones/event_hub';
import { HTTP_STATUS_IM_A_TEAPOT, HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status';
import { redirectTo } from '~/lib/utils/url_utility';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
jest.mock('~/lib/utils/url_utility');
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Delete milestone modal', () => {
let wrapper;
@@ -39,10 +39,6 @@ describe('Delete milestone modal', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('onSubmit', () => {
beforeEach(() => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
diff --git a/spec/frontend/milestones/components/milestone_combobox_spec.js b/spec/frontend/milestones/components/milestone_combobox_spec.js
index f8ddca1a2ad..748e01d4291 100644
--- a/spec/frontend/milestones/components/milestone_combobox_spec.js
+++ b/spec/frontend/milestones/components/milestone_combobox_spec.js
@@ -85,11 +85,6 @@ describe('Milestone combobox component', () => {
mock.onGet(`/api/v4/projects/${projectId}/search`).reply((config) => searchApiCallSpy(config));
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
//
// Finders
//
diff --git a/spec/frontend/milestones/components/promote_milestone_modal_spec.js b/spec/frontend/milestones/components/promote_milestone_modal_spec.js
index d7ad3d29d0a..e91e792afe8 100644
--- a/spec/frontend/milestones/components/promote_milestone_modal_spec.js
+++ b/spec/frontend/milestones/components/promote_milestone_modal_spec.js
@@ -3,14 +3,14 @@ import { shallowMount } from '@vue/test-utils';
import { setHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status';
import * as urlUtils from '~/lib/utils/url_utility';
import PromoteMilestoneModal from '~/milestones/components/promote_milestone_modal.vue';
jest.mock('~/lib/utils/url_utility');
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Promote milestone modal', () => {
let wrapper;
@@ -33,10 +33,6 @@ describe('Promote milestone modal', () => {
wrapper = shallowMount(PromoteMilestoneModal);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Modal opener button', () => {
it('button gets disabled when the modal opens', () => {
expect(promoteButton().disabled).toBe(false);
diff --git a/spec/frontend/ml/experiment_tracking/components/__snapshots__/ml_candidate_spec.js.snap b/spec/frontend/ml/experiment_tracking/routes/candidates/show/__snapshots__/ml_candidates_show_spec.js.snap
index 7d7eee2bc2c..dc21db39259 100644
--- a/spec/frontend/ml/experiment_tracking/components/__snapshots__/ml_candidate_spec.js.snap
+++ b/spec/frontend/ml/experiment_tracking/routes/candidates/show/__snapshots__/ml_candidates_show_spec.js.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`MlCandidate renders correctly 1`] = `
+exports[`MlCandidatesShow renders correctly 1`] = `
<div>
<div
class="gl-alert gl-alert-warning"
@@ -152,7 +152,24 @@ exports[`MlCandidate renders correctly 1`] = `
</td>
</tr>
- <!---->
+ <tr>
+ <td />
+
+ <td
+ class="gl-font-weight-bold"
+ >
+ Artifacts
+ </td>
+
+ <td>
+ <a
+ class="gl-link"
+ href="path_to_artifact"
+ >
+ Artifacts
+ </a>
+ </td>
+ </tr>
<tr
class="divider"
diff --git a/spec/frontend/ml/experiment_tracking/components/ml_candidate_spec.js b/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js
index 483e454d7d7..36455339041 100644
--- a/spec/frontend/ml/experiment_tracking/components/ml_candidate_spec.js
+++ b/spec/frontend/ml/experiment_tracking/routes/candidates/show/ml_candidates_show_spec.js
@@ -1,8 +1,8 @@
import { GlAlert } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
-import MlCandidate from '~/ml/experiment_tracking/components/ml_candidate.vue';
+import MlCandidatesShow from '~/ml/experiment_tracking/routes/candidates/show';
-describe('MlCandidate', () => {
+describe('MlCandidatesShow', () => {
let wrapper;
const createWrapper = () => {
@@ -21,14 +21,14 @@ describe('MlCandidate', () => {
],
info: {
iid: 'candidate_iid',
- artifact_link: 'path_to_artifact',
+ path_to_artifact: 'path_to_artifact',
experiment_name: 'The Experiment',
experiment_path: 'path/to/experiment',
status: 'SUCCESS',
},
};
- return mountExtended(MlCandidate, { propsData: { candidate } });
+ return mountExtended(MlCandidatesShow, { propsData: { candidate } });
};
const findAlert = () => wrapper.findComponent(GlAlert);
diff --git a/spec/frontend/ml/experiment_tracking/components/ml_experiment_spec.js b/spec/frontend/ml/experiment_tracking/routes/experiments/show/ml_experiments_show_spec.js
index f307d2c5a58..97a5049ea88 100644
--- a/spec/frontend/ml/experiment_tracking/components/ml_experiment_spec.js
+++ b/spec/frontend/ml/experiment_tracking/routes/experiments/show/ml_experiments_show_spec.js
@@ -1,87 +1,35 @@
-import { GlAlert, GlTable, GlLink } from '@gitlab/ui';
-import { nextTick } from 'vue';
+import { GlAlert, GlTableLite, GlLink, GlEmptyState } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
-import MlExperiment from '~/ml/experiment_tracking/components/ml_experiment.vue';
+import MlExperimentsShow from '~/ml/experiment_tracking/routes/experiments/show/ml_experiments_show.vue';
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
import Pagination from '~/vue_shared/components/incubation/pagination.vue';
import setWindowLocation from 'helpers/set_window_location_helper';
import * as urlHelpers from '~/lib/utils/url_utility';
+import { MOCK_START_CURSOR, MOCK_PAGE_INFO, MOCK_CANDIDATES } from './mock_data';
-describe('MlExperiment', () => {
+describe('MlExperimentsShow', () => {
let wrapper;
- const startCursor = 'eyJpZCI6IjE2In0';
- const defaultPageInfo = {
- startCursor,
- endCursor: 'eyJpZCI6IjIifQ',
- hasNextPage: true,
- hasPreviousPage: true,
- };
-
const createWrapper = (
candidates = [],
metricNames = [],
paramNames = [],
- pageInfo = defaultPageInfo,
+ pageInfo = MOCK_PAGE_INFO,
) => {
- wrapper = mountExtended(MlExperiment, {
- provide: { candidates, metricNames, paramNames, pageInfo },
+ wrapper = mountExtended(MlExperimentsShow, {
+ propsData: { candidates, metricNames, paramNames, pageInfo },
});
};
- const candidates = [
- {
- rmse: 1,
- l1_ratio: 0.4,
- details: 'link_to_candidate1',
- artifact: 'link_to_artifact',
- name: 'aCandidate',
- created_at: '2023-01-05T14:07:02.975Z',
- user: { username: 'root', path: '/root' },
- },
- {
- auc: 0.3,
- l1_ratio: 0.5,
- details: 'link_to_candidate2',
- created_at: '2023-01-05T14:07:02.975Z',
- name: null,
- user: null,
- },
- {
- auc: 0.3,
- l1_ratio: 0.5,
- details: 'link_to_candidate3',
- created_at: '2023-01-05T14:07:02.975Z',
- name: null,
- user: null,
- },
- {
- auc: 0.3,
- l1_ratio: 0.5,
- details: 'link_to_candidate4',
- created_at: '2023-01-05T14:07:02.975Z',
- name: null,
- user: null,
- },
- {
- auc: 0.3,
- l1_ratio: 0.5,
- details: 'link_to_candidate5',
- created_at: '2023-01-05T14:07:02.975Z',
- name: null,
- user: null,
- },
- ];
-
- const createWrapperWithCandidates = (pageInfo = defaultPageInfo) => {
- createWrapper(candidates, ['rmse', 'auc', 'mae'], ['l1_ratio'], pageInfo);
+ const createWrapperWithCandidates = (pageInfo = MOCK_PAGE_INFO) => {
+ createWrapper(MOCK_CANDIDATES, ['rmse', 'auc', 'mae'], ['l1_ratio'], pageInfo);
};
const findAlert = () => wrapper.findComponent(GlAlert);
const findPagination = () => wrapper.findComponent(Pagination);
- const findEmptyState = () => wrapper.findByText('No candidates to display');
+ const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findRegistrySearch = () => wrapper.findComponent(RegistrySearch);
- const findTable = () => wrapper.findComponent(GlTable);
+ const findTable = () => wrapper.findComponent(GlTableLite);
const findTableHeaders = () => findTable().findAll('th');
const findTableRows = () => findTable().findAll('tbody > tr');
const findNthTableRow = (idx) => findTableRows().at(idx);
@@ -98,8 +46,6 @@ describe('MlExperiment', () => {
describe('default inputs', () => {
beforeEach(async () => {
createWrapper();
-
- await nextTick();
});
it('shows empty state', () => {
@@ -110,8 +56,8 @@ describe('MlExperiment', () => {
expect(findPagination().exists()).toBe(false);
});
- it('there are no columns', () => {
- expect(findTable().findAll('th')).toHaveLength(0);
+ it('does not show table', () => {
+ expect(findTable().exists()).toBe(false);
});
it('initializes sorting correctly', () => {
@@ -227,34 +173,33 @@ describe('MlExperiment', () => {
it('Passes pagination to pagination component', () => {
createWrapperWithCandidates();
- expect(findPagination().props('startCursor')).toBe(startCursor);
+ expect(findPagination().props('startCursor')).toBe(MOCK_START_CURSOR);
});
});
describe('Candidate table', () => {
const firstCandidateIndex = 0;
const secondCandidateIndex = 1;
- const firstCandidate = candidates[firstCandidateIndex];
+ const firstCandidate = MOCK_CANDIDATES[firstCandidateIndex];
beforeEach(() => {
createWrapperWithCandidates();
});
it('renders all rows', () => {
- expect(findTableRows()).toHaveLength(candidates.length);
+ expect(findTableRows()).toHaveLength(MOCK_CANDIDATES.length);
});
it('sets the correct columns in the table', () => {
const expectedColumnNames = [
'Name',
'Created at',
- 'User',
+ 'Author',
'L1 Ratio',
'Rmse',
'Auc',
'Mae',
- '',
- '',
+ 'Artifacts',
];
expect(findTableHeaders().wrappers.map((h) => h.text())).toEqual(expectedColumnNames);
@@ -270,7 +215,9 @@ describe('MlExperiment', () => {
});
it('shows empty state when no artifact', () => {
- expect(findColumnInRow(secondCandidateIndex, artifactColumnIndex).text()).toBe('-');
+ expect(findColumnInRow(secondCandidateIndex, artifactColumnIndex).text()).toBe(
+ 'No artifacts',
+ );
});
});
@@ -301,15 +248,7 @@ describe('MlExperiment', () => {
});
it('when there is no user shows nothing', () => {
- expect(findColumnInRow(secondCandidateIndex, nameColumnIndex).text()).toBe('');
- });
- });
-
- describe('Detail column', () => {
- const detailColumn = -2;
-
- it('is a link to details', () => {
- expect(hrefInRowAndColumn(firstCandidateIndex, detailColumn)).toBe(firstCandidate.details);
+ expect(findColumnInRow(secondCandidateIndex, nameColumnIndex).text()).toBe('No name');
});
});
});
diff --git a/spec/frontend/ml/experiment_tracking/routes/experiments/show/mock_data.js b/spec/frontend/ml/experiment_tracking/routes/experiments/show/mock_data.js
new file mode 100644
index 00000000000..66378cd3f0d
--- /dev/null
+++ b/spec/frontend/ml/experiment_tracking/routes/experiments/show/mock_data.js
@@ -0,0 +1,52 @@
+export const MOCK_START_CURSOR = 'eyJpZCI6IjE2In0';
+
+export const MOCK_PAGE_INFO = {
+ startCursor: MOCK_START_CURSOR,
+ endCursor: 'eyJpZCI6IjIifQ',
+ hasNextPage: true,
+ hasPreviousPage: true,
+};
+
+export const MOCK_CANDIDATES = [
+ {
+ rmse: 1,
+ l1_ratio: 0.4,
+ details: 'link_to_candidate1',
+ artifact: 'link_to_artifact',
+ name: 'aCandidate',
+ created_at: '2023-01-05T14:07:02.975Z',
+ user: { username: 'root', path: '/root' },
+ },
+ {
+ auc: 0.3,
+ l1_ratio: 0.5,
+ details: 'link_to_candidate2',
+ created_at: '2023-01-05T14:07:02.975Z',
+ name: null,
+ user: null,
+ },
+ {
+ auc: 0.3,
+ l1_ratio: 0.5,
+ details: 'link_to_candidate3',
+ created_at: '2023-01-05T14:07:02.975Z',
+ name: null,
+ user: null,
+ },
+ {
+ auc: 0.3,
+ l1_ratio: 0.5,
+ details: 'link_to_candidate4',
+ created_at: '2023-01-05T14:07:02.975Z',
+ name: null,
+ user: null,
+ },
+ {
+ auc: 0.3,
+ l1_ratio: 0.5,
+ details: 'link_to_candidate5',
+ created_at: '2023-01-05T14:07:02.975Z',
+ name: null,
+ user: null,
+ },
+];
diff --git a/spec/frontend/monitoring/components/charts/column_spec.js b/spec/frontend/monitoring/components/charts/column_spec.js
index 0158966997f..cc38a3fd8a1 100644
--- a/spec/frontend/monitoring/components/charts/column_spec.js
+++ b/spec/frontend/monitoring/components/charts/column_spec.js
@@ -51,10 +51,6 @@ describe('Column component', () => {
createWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('xAxisLabel', () => {
const mockDate = Date.UTC(2020, 4, 26, 20); // 8:00 PM in GMT
diff --git a/spec/frontend/monitoring/components/charts/gauge_spec.js b/spec/frontend/monitoring/components/charts/gauge_spec.js
index 484199698ea..33ea5e83598 100644
--- a/spec/frontend/monitoring/components/charts/gauge_spec.js
+++ b/spec/frontend/monitoring/components/charts/gauge_spec.js
@@ -21,11 +21,6 @@ describe('Gauge Chart component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('chart component', () => {
it('is rendered when props are passed', () => {
createWrapper();
diff --git a/spec/frontend/monitoring/components/charts/heatmap_spec.js b/spec/frontend/monitoring/components/charts/heatmap_spec.js
index e163d4e73a0..54245cbdbc1 100644
--- a/spec/frontend/monitoring/components/charts/heatmap_spec.js
+++ b/spec/frontend/monitoring/components/charts/heatmap_spec.js
@@ -28,10 +28,6 @@ describe('Heatmap component', () => {
createWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should display a label on the x axis', () => {
expect(wrapper.vm.xAxisName).toBe(graphData.xLabel);
});
diff --git a/spec/frontend/monitoring/components/charts/single_stat_spec.js b/spec/frontend/monitoring/components/charts/single_stat_spec.js
index 62a0b7e6ad3..fa31b479296 100644
--- a/spec/frontend/monitoring/components/charts/single_stat_spec.js
+++ b/spec/frontend/monitoring/components/charts/single_stat_spec.js
@@ -21,10 +21,6 @@ describe('Single Stat Chart component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('computed', () => {
describe('statValue', () => {
it('should display the correct value', () => {
diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js
index 503dee7b937..c1b51f71a7e 100644
--- a/spec/frontend/monitoring/components/charts/time_series_spec.js
+++ b/spec/frontend/monitoring/components/charts/time_series_spec.js
@@ -58,10 +58,6 @@ describe('Time series component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('With a single time series', () => {
describe('general functions', () => {
const findChart = () => wrapper.findComponent({ ref: 'chart' });
diff --git a/spec/frontend/monitoring/components/create_dashboard_modal_spec.js b/spec/frontend/monitoring/components/create_dashboard_modal_spec.js
index 88de3467580..eb05b1f184a 100644
--- a/spec/frontend/monitoring/components/create_dashboard_modal_spec.js
+++ b/spec/frontend/monitoring/components/create_dashboard_modal_spec.js
@@ -29,10 +29,6 @@ describe('Create dashboard modal', () => {
createWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('has button that links to the project url', async () => {
findRepoButton().trigger('click');
diff --git a/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js b/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js
index bb57420d406..2758103fd6e 100644
--- a/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js
@@ -55,11 +55,6 @@ describe('Actions menu', () => {
store = createStore();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('add metric item', () => {
it('is rendered when custom metrics are available', async () => {
createShallowWrapper();
diff --git a/spec/frontend/monitoring/components/dashboard_header_spec.js b/spec/frontend/monitoring/components/dashboard_header_spec.js
index 18ccda2c41c..ab259249772 100644
--- a/spec/frontend/monitoring/components/dashboard_header_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_header_spec.js
@@ -59,10 +59,6 @@ describe('Dashboard header', () => {
store = createStore();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('dashboards dropdown', () => {
beforeEach(() => {
store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, {
diff --git a/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js b/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js
index d71f6374967..1cfd132b123 100644
--- a/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js
@@ -49,8 +49,6 @@ describe('dashboard invalid url parameters', () => {
jest.spyOn(store, 'dispatch').mockResolvedValue();
});
- afterEach(() => {});
-
it('is mounted', () => {
expect(wrapper.exists()).toBe(true);
});
diff --git a/spec/frontend/monitoring/components/dashboard_panel_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js
index 339c1710a9e..491649e5b96 100644
--- a/spec/frontend/monitoring/components/dashboard_panel_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js
@@ -106,10 +106,6 @@ describe('Dashboard Panel', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the chart title', () => {
expect(findTitle().text()).toBe(graphDataEmpty.title);
});
@@ -134,10 +130,6 @@ describe('Dashboard Panel', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders no chart title', () => {
expect(findTitle().text()).toBe('');
});
@@ -160,10 +152,6 @@ describe('Dashboard Panel', () => {
createWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the chart title', () => {
expect(findTitle().text()).toBe(graphData.title);
});
@@ -377,10 +365,6 @@ describe('Dashboard Panel', () => {
await nextTick();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('csvText', () => {
it('converts metrics data from json to csv', () => {
const header = `timestamp,"${graphData.y_label} > ${graphData.metrics[0].label}"`;
diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js
index 1d17a9116df..1f995965003 100644
--- a/spec/frontend/monitoring/components/dashboard_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_spec.js
@@ -4,7 +4,7 @@ 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';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { ESC_KEY } from '~/lib/utils/keys';
import { objectToQuery } from '~/lib/utils/url_utility';
@@ -33,7 +33,7 @@ import {
setupStoreWithLinks,
} from '../store_utils';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Dashboard', () => {
let store;
@@ -75,7 +75,6 @@ describe('Dashboard', () => {
if (store.dispatch.mockReset) {
store.dispatch.mockReset();
}
- wrapper.destroy();
});
describe('request information to the server', () => {
diff --git a/spec/frontend/monitoring/components/dashboard_url_time_spec.js b/spec/frontend/monitoring/components/dashboard_url_time_spec.js
index 9873654bdda..98791906700 100644
--- a/spec/frontend/monitoring/components/dashboard_url_time_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_url_time_spec.js
@@ -1,7 +1,7 @@
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import {
queryToObject,
@@ -18,7 +18,7 @@ import { defaultTimeRange } from '~/vue_shared/constants';
import { dashboardProps } from '../fixture_data';
import { mockProjectDir } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility');
describe('dashboard invalid url parameters', () => {
diff --git a/spec/frontend/monitoring/components/graph_group_spec.js b/spec/frontend/monitoring/components/graph_group_spec.js
index 104263e73e0..593d832f297 100644
--- a/spec/frontend/monitoring/components/graph_group_spec.js
+++ b/spec/frontend/monitoring/components/graph_group_spec.js
@@ -18,10 +18,6 @@ describe('Graph group component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('When group is not collapsed', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/monitoring/components/group_empty_state_spec.js b/spec/frontend/monitoring/components/group_empty_state_spec.js
index e3cd26b0e48..d3a48be7939 100644
--- a/spec/frontend/monitoring/components/group_empty_state_spec.js
+++ b/spec/frontend/monitoring/components/group_empty_state_spec.js
@@ -23,10 +23,6 @@ function createComponent(props) {
describe('GroupEmptyState', () => {
let wrapper;
- afterEach(() => {
- wrapper.destroy();
- });
-
describe.each([
metricStates.NO_DATA,
metricStates.TIMEOUT,
diff --git a/spec/frontend/monitoring/components/refresh_button_spec.js b/spec/frontend/monitoring/components/refresh_button_spec.js
index cb300870689..f6cc6789b1f 100644
--- a/spec/frontend/monitoring/components/refresh_button_spec.js
+++ b/spec/frontend/monitoring/components/refresh_button_spec.js
@@ -40,6 +40,7 @@ describe('RefreshButton', () => {
afterEach(() => {
dispatch.mockReset();
+ // eslint-disable-next-line @gitlab/vtu-no-explicit-wrapper-destroy
wrapper.destroy();
});
diff --git a/spec/frontend/monitoring/components/variables/dropdown_field_spec.js b/spec/frontend/monitoring/components/variables/dropdown_field_spec.js
index 012e2e9c3e2..96b228fd3b2 100644
--- a/spec/frontend/monitoring/components/variables/dropdown_field_spec.js
+++ b/spec/frontend/monitoring/components/variables/dropdown_field_spec.js
@@ -1,6 +1,5 @@
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', () => {
@@ -56,11 +55,8 @@ describe('Custom variable component', () => {
it('changing dropdown items triggers update', async () => {
createShallowWrapper();
- jest.spyOn(wrapper.vm, '$emit');
-
findDropdownItems().at(1).vm.$emit('click');
- await nextTick();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'canary');
+ expect(wrapper.emitted('input')).toEqual([['canary']]);
});
});
diff --git a/spec/frontend/monitoring/components/variables/text_field_spec.js b/spec/frontend/monitoring/components/variables/text_field_spec.js
index 3073b3968aa..20e1937c5ac 100644
--- a/spec/frontend/monitoring/components/variables/text_field_spec.js
+++ b/spec/frontend/monitoring/components/variables/text_field_spec.js
@@ -33,25 +33,23 @@ describe('Text variable component', () => {
it('triggers keyup enter', async () => {
createShallowWrapper();
- jest.spyOn(wrapper.vm, '$emit');
findInput().element.value = 'prod-pod';
findInput().trigger('input');
findInput().trigger('keyup.enter');
await nextTick();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'prod-pod');
+ expect(wrapper.emitted('input')).toEqual([['prod-pod']]);
});
it('triggers blur enter', async () => {
createShallowWrapper();
- jest.spyOn(wrapper.vm, '$emit');
findInput().element.value = 'canary-pod';
findInput().trigger('input');
findInput().trigger('blur');
await nextTick();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', 'canary-pod');
+ expect(wrapper.emitted('input')).toEqual([['canary-pod']]);
});
});
diff --git a/spec/frontend/monitoring/pages/panel_new_page_spec.js b/spec/frontend/monitoring/pages/panel_new_page_spec.js
index fa112fca2db..98ee6c1cb29 100644
--- a/spec/frontend/monitoring/pages/panel_new_page_spec.js
+++ b/spec/frontend/monitoring/pages/panel_new_page_spec.js
@@ -49,10 +49,6 @@ describe('monitoring/pages/panel_new_page', () => {
mountComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('back to dashboard button', () => {
it('is rendered', () => {
expect(findBackButton().exists()).toBe(true);
diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js
index 8eda46a2ff1..8097857f226 100644
--- a/spec/frontend/monitoring/store/actions_spec.js
+++ b/spec/frontend/monitoring/store/actions_spec.js
@@ -1,7 +1,7 @@
import MockAdapter from 'axios-mock-adapter';
import { backoffMockImplementation } from 'helpers/backoff_helper';
import testAction from 'helpers/vuex_action_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import * as commonUtils from '~/lib/utils/common_utils';
import {
@@ -61,7 +61,7 @@ import {
mockDashboardsErrorResponse,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Monitoring store actions', () => {
const { convertObjectPropsToCamelCase } = commonUtils;
@@ -177,7 +177,6 @@ describe('Monitoring store actions', () => {
});
it('dispatches when feature metricsDashboardAnnotations is on', () => {
- const origGon = window.gon;
window.gon = { features: { metricsDashboardAnnotations: true } };
return testAction(
@@ -190,9 +189,7 @@ describe('Monitoring store actions', () => {
{ type: 'fetchDashboard' },
{ type: 'fetchAnnotations' },
],
- ).then(() => {
- window.gon = origGon;
- });
+ );
});
});
@@ -263,7 +260,7 @@ describe('Monitoring store actions', () => {
});
});
- it('does not show a flash error when showErrorBanner is disabled', async () => {
+ it('does not show an alert error when showErrorBanner is disabled', async () => {
state.showErrorBanner = false;
await result();
diff --git a/spec/frontend/nav/components/new_nav_toggle_spec.js b/spec/frontend/nav/components/new_nav_toggle_spec.js
index bad24345f9d..fe543a346b5 100644
--- a/spec/frontend/nav/components/new_nav_toggle_spec.js
+++ b/spec/frontend/nav/components/new_nav_toggle_spec.js
@@ -1,16 +1,16 @@
import { mount, createWrapper } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { getByText as getByTextHelper } from '@testing-library/dom';
-import { GlToggle } from '@gitlab/ui';
+import { GlDisclosureDropdownItem, GlToggle } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import NewNavToggle from '~/nav/components/new_nav_toggle.vue';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { s__ } from '~/locale';
-jest.mock('~/flash');
+jest.mock('~/alert');
const TEST_ENDPONT = 'https://example.com/toggle';
@@ -20,6 +20,7 @@ describe('NewNavToggle', () => {
let wrapper;
const findToggle = () => wrapper.findComponent(GlToggle);
+ const findDisclosureItem = () => wrapper.findComponent(GlDisclosureDropdownItem);
const createComponent = (propsData = { enabled: false }) => {
wrapper = mount(NewNavToggle, {
@@ -30,83 +31,156 @@ describe('NewNavToggle', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const getByText = (text, options) =>
createWrapper(getByTextHelper(wrapper.element, text, options));
- it('renders its title', () => {
- createComponent();
- expect(getByText('Navigation redesign').exists()).toBe(true);
- });
+ describe('When rendered in scope of the new navigation', () => {
+ it('renders the disclosure item', () => {
+ createComponent({ newNavigation: true, enabled: true });
+ expect(findDisclosureItem().exists()).toBe(true);
+ });
- describe('when user preference is enabled', () => {
- beforeEach(() => {
- createComponent({ enabled: true });
+ describe('when user preference is enabled', () => {
+ beforeEach(() => {
+ createComponent({ newNavigation: true, enabled: true });
+ });
+
+ it('renders the toggle as enabled', () => {
+ expect(findToggle().props('value')).toBe(true);
+ });
});
- it('renders the toggle as enabled', () => {
- expect(findToggle().props('value')).toBe(true);
+ describe('when user preference is disabled', () => {
+ beforeEach(() => {
+ createComponent({ enabled: false });
+ });
+
+ it('renders the toggle as disabled', () => {
+ expect(findToggle().props('value')).toBe(false);
+ });
+ });
+
+ describe.each`
+ desc | actFn
+ ${'when toggle button is clicked'} | ${() => findToggle().trigger('click')}
+ ${'on menu item action'} | ${() => findDisclosureItem().vm.$emit('action')}
+ `('$desc', ({ actFn }) => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ createComponent({ enabled: false, newNavigation: true });
+ });
+
+ it('reloads the page on success', async () => {
+ mock.onPut(TEST_ENDPONT).reply(HTTP_STATUS_OK);
+
+ actFn();
+ await waitForPromises();
+
+ expect(window.location.reload).toHaveBeenCalled();
+ });
+
+ it('shows an alert on error', async () => {
+ mock.onPut(TEST_ENDPONT).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+
+ actFn();
+ await waitForPromises();
+
+ expect(createAlert).toHaveBeenCalledWith(
+ expect.objectContaining({
+ message: s__(
+ 'NorthstarNavigation|Could not update the new navigation preference. Please try again later.',
+ ),
+ }),
+ );
+ expect(window.location.reload).not.toHaveBeenCalled();
+ });
+
+ it('changes the toggle', async () => {
+ await actFn();
+
+ expect(findToggle().props('value')).toBe(true);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
});
});
- describe('when user preference is disabled', () => {
- beforeEach(() => {
- createComponent({ enabled: false });
+ describe('When rendered in scope of the current navigation', () => {
+ it('renders its title', () => {
+ createComponent();
+ expect(getByText('Navigation redesign').exists()).toBe(true);
});
- it('renders the toggle as disabled', () => {
- expect(findToggle().props('value')).toBe(false);
+ describe('when user preference is enabled', () => {
+ beforeEach(() => {
+ createComponent({ enabled: true });
+ });
+
+ it('renders the toggle as enabled', () => {
+ expect(findToggle().props('value')).toBe(true);
+ });
});
- });
- describe.each`
- desc | actFn
- ${'when toggle button is clicked'} | ${() => findToggle().trigger('click')}
- ${'when menu item text is clicked'} | ${() => getByText('New navigation').trigger('click')}
- `('$desc', ({ actFn }) => {
- let mock;
+ describe('when user preference is disabled', () => {
+ beforeEach(() => {
+ createComponent({ enabled: false });
+ });
- beforeEach(() => {
- mock = new MockAdapter(axios);
- createComponent({ enabled: false });
+ it('renders the toggle as disabled', () => {
+ expect(findToggle().props('value')).toBe(false);
+ });
});
- it('reloads the page on success', async () => {
- mock.onPut(TEST_ENDPONT).reply(HTTP_STATUS_OK);
+ describe.each`
+ desc | actFn
+ ${'when toggle button is clicked'} | ${() => findToggle().trigger('click')}
+ ${'when menu item text is clicked'} | ${() => getByText('New navigation').trigger('click')}
+ `('$desc', ({ actFn }) => {
+ let mock;
- actFn();
- await waitForPromises();
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ createComponent({ enabled: false });
+ });
- expect(window.location.reload).toHaveBeenCalled();
- });
+ it('reloads the page on success', async () => {
+ mock.onPut(TEST_ENDPONT).reply(HTTP_STATUS_OK);
- it('shows an alert on error', async () => {
- mock.onPut(TEST_ENDPONT).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+ actFn();
+ await waitForPromises();
- actFn();
- await waitForPromises();
+ expect(window.location.reload).toHaveBeenCalled();
+ });
- expect(createAlert).toHaveBeenCalledWith(
- expect.objectContaining({
- message: s__(
- 'NorthstarNavigation|Could not update the new navigation preference. Please try again later.',
- ),
- }),
- );
- expect(window.location.reload).not.toHaveBeenCalled();
- });
+ it('shows an alert on error', async () => {
+ mock.onPut(TEST_ENDPONT).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
- it('changes the toggle', async () => {
- await actFn();
+ actFn();
+ await waitForPromises();
- expect(findToggle().props('value')).toBe(true);
- });
+ expect(createAlert).toHaveBeenCalledWith(
+ expect.objectContaining({
+ message: s__(
+ 'NorthstarNavigation|Could not update the new navigation preference. Please try again later.',
+ ),
+ }),
+ );
+ expect(window.location.reload).not.toHaveBeenCalled();
+ });
+
+ it('changes the toggle', async () => {
+ await actFn();
+
+ expect(findToggle().props('value')).toBe(true);
+ });
- afterEach(() => {
- mock.restore();
+ afterEach(() => {
+ mock.restore();
+ });
});
});
});
diff --git a/spec/frontend/nav/components/responsive_app_spec.js b/spec/frontend/nav/components/responsive_app_spec.js
index 76b8ebdc92f..9d3b43520ec 100644
--- a/spec/frontend/nav/components/responsive_app_spec.js
+++ b/spec/frontend/nav/components/responsive_app_spec.js
@@ -33,10 +33,6 @@ describe('~/nav/components/responsive_app.vue', () => {
document.body.className = 'test-class';
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/nav/components/responsive_header_spec.js b/spec/frontend/nav/components/responsive_header_spec.js
index f87de0afb14..2514035270a 100644
--- a/spec/frontend/nav/components/responsive_header_spec.js
+++ b/spec/frontend/nav/components/responsive_header_spec.js
@@ -14,7 +14,7 @@ describe('~/nav/components/top_nav_menu_sections.vue', () => {
default: TEST_SLOT_CONTENT,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
@@ -25,10 +25,6 @@ describe('~/nav/components/top_nav_menu_sections.vue', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders slot', () => {
expect(wrapper.text()).toBe(TEST_SLOT_CONTENT);
});
diff --git a/spec/frontend/nav/components/responsive_home_spec.js b/spec/frontend/nav/components/responsive_home_spec.js
index 8f198d92747..5a5cfc93607 100644
--- a/spec/frontend/nav/components/responsive_home_spec.js
+++ b/spec/frontend/nav/components/responsive_home_spec.js
@@ -29,7 +29,7 @@ describe('~/nav/components/responsive_home.vue', () => {
...props,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
listeners: {
'menu-item-click': menuItemClickListener,
@@ -45,10 +45,6 @@ describe('~/nav/components/responsive_home.vue', () => {
menuItemClickListener = jest.fn();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/nav/components/top_nav_app_spec.js b/spec/frontend/nav/components/top_nav_app_spec.js
index e70f70afc97..7f39552eb42 100644
--- a/spec/frontend/nav/components/top_nav_app_spec.js
+++ b/spec/frontend/nav/components/top_nav_app_spec.js
@@ -28,10 +28,6 @@ describe('~/nav/components/top_nav_app.vue', () => {
const findNavItemDropdowToggle = () => findNavItemDropdown().find('.js-top-nav-dropdown-toggle');
const findMenu = () => wrapper.findComponent(TopNavDropdownMenu);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
beforeEach(() => {
createComponentShallow();
diff --git a/spec/frontend/nav/components/top_nav_container_view_spec.js b/spec/frontend/nav/components/top_nav_container_view_spec.js
index 293fe361fa9..388ac243648 100644
--- a/spec/frontend/nav/components/top_nav_container_view_spec.js
+++ b/spec/frontend/nav/components/top_nav_container_view_spec.js
@@ -48,10 +48,6 @@ describe('~/nav/components/top_nav_container_view.vue', () => {
};
const findFrequentItemsContainer = () => wrapper.find('[data-testid="frequent-items-container"]');
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each(['projects', 'groups'])(
'emits frequent items event to event hub (%s)',
async (frequentItemsDropdownType) => {
diff --git a/spec/frontend/nav/components/top_nav_dropdown_menu_spec.js b/spec/frontend/nav/components/top_nav_dropdown_menu_spec.js
index 8a0340087ec..08d6650b5bb 100644
--- a/spec/frontend/nav/components/top_nav_dropdown_menu_spec.js
+++ b/spec/frontend/nav/components/top_nav_dropdown_menu_spec.js
@@ -36,10 +36,6 @@ describe('~/nav/components/top_nav_dropdown_menu.vue', () => {
active: idx === activeIndex,
}));
- afterEach(() => {
- wrapper.destroy();
- });
-
beforeEach(() => {
jest.spyOn(console, 'error').mockImplementation();
});
diff --git a/spec/frontend/nav/components/top_nav_menu_sections_spec.js b/spec/frontend/nav/components/top_nav_menu_sections_spec.js
index 7a5a8475ab7..7a3e58fd964 100644
--- a/spec/frontend/nav/components/top_nav_menu_sections_spec.js
+++ b/spec/frontend/nav/components/top_nav_menu_sections_spec.js
@@ -54,10 +54,6 @@ describe('~/nav/components/top_nav_menu_sections.vue', () => {
menuItems: findMenuItemModels(x),
}));
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/nav/components/top_nav_new_dropdown_spec.js b/spec/frontend/nav/components/top_nav_new_dropdown_spec.js
index 18210658b89..2cd65307b0b 100644
--- a/spec/frontend/nav/components/top_nav_new_dropdown_spec.js
+++ b/spec/frontend/nav/components/top_nav_new_dropdown_spec.js
@@ -1,6 +1,8 @@
import { GlDropdown } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import TopNavNewDropdown from '~/nav/components/top_nav_new_dropdown.vue';
+import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue';
+import { TOP_NAV_INVITE_MEMBERS_COMPONENT } from '~/invite_members/constants';
const TEST_VIEW_MODEL = {
title: 'Dropdown',
@@ -18,6 +20,16 @@ const TEST_VIEW_MODEL = {
menu_items: [
{ id: 'bar-1', title: 'Bar 1', href: '/bar/1' },
{ id: 'bar-2', title: 'Bar 2', href: '/bar/2' },
+ {
+ id: 'invite',
+ title: '_invite members title_',
+ component: TOP_NAV_INVITE_MEMBERS_COMPONENT,
+ icon: '_icon_',
+ data: {
+ trigger_element: '_trigger_element_',
+ trigger_source: '_trigger_source_',
+ },
+ },
],
},
],
@@ -36,6 +48,7 @@ describe('~/nav/components/top_nav_menu_sections.vue', () => {
};
const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findInviteMembersTrigger = () => wrapper.findComponent(InviteMembersTrigger);
const findDropdownContents = () =>
findDropdown()
.findAll('[data-testid]')
@@ -55,10 +68,6 @@ describe('~/nav/components/top_nav_menu_sections.vue', () => {
};
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
beforeEach(() => {
createComponent();
@@ -73,6 +82,10 @@ describe('~/nav/components/top_nav_menu_sections.vue', () => {
});
it('renders dropdown content', () => {
+ const hrefItems = TEST_VIEW_MODEL.menu_sections[1].menu_items.filter((item) =>
+ Boolean(item.href),
+ );
+
expect(findDropdownContents()).toEqual([
{
type: 'header',
@@ -90,12 +103,18 @@ describe('~/nav/components/top_nav_menu_sections.vue', () => {
type: 'header',
text: TEST_VIEW_MODEL.menu_sections[1].title,
},
- ...TEST_VIEW_MODEL.menu_sections[1].menu_items.map(({ title, href }) => ({
+ ...hrefItems.map(({ title, href }) => ({
type: 'item',
href,
text: title,
})),
]);
+ expect(findInviteMembersTrigger().props()).toMatchObject({
+ displayText: '_invite members title_',
+ icon: '_icon_',
+ triggerElement: 'dropdown-_trigger_element_',
+ triggerSource: '_trigger_source_',
+ });
});
});
diff --git a/spec/frontend/notebook/cells/code_spec.js b/spec/frontend/notebook/cells/code_spec.js
index 10762a1c3a2..9836400a366 100644
--- a/spec/frontend/notebook/cells/code_spec.js
+++ b/spec/frontend/notebook/cells/code_spec.js
@@ -13,10 +13,6 @@ describe('Code component', () => {
json = JSON.parse(JSON.stringify(fixture));
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('without output', () => {
beforeEach(() => {
wrapper = mountComponent(json.cells[0]);
diff --git a/spec/frontend/notebook/cells/markdown_spec.js b/spec/frontend/notebook/cells/markdown_spec.js
index a7776bd5b69..f226776212a 100644
--- a/spec/frontend/notebook/cells/markdown_spec.js
+++ b/spec/frontend/notebook/cells/markdown_spec.js
@@ -1,18 +1,16 @@
import { mount } from '@vue/test-utils';
import katex from 'katex';
-import Vue, { nextTick } from 'vue';
+import { 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';
import MarkdownComponent from '~/notebook/cells/markdown.vue';
import Prompt from '~/notebook/cells/prompt.vue';
-const Component = Vue.extend(MarkdownComponent);
-
window.katex = katex;
function buildCellComponent(cell, relativePath = '', hidePrompt) {
- return mount(Component, {
+ return mount(MarkdownComponent, {
propsData: {
cell,
hidePrompt,
diff --git a/spec/frontend/notebook/cells/output/error_spec.js b/spec/frontend/notebook/cells/output/error_spec.js
new file mode 100644
index 00000000000..2e4ca8c1761
--- /dev/null
+++ b/spec/frontend/notebook/cells/output/error_spec.js
@@ -0,0 +1,48 @@
+import { mount } from '@vue/test-utils';
+import ErrorOutput from '~/notebook/cells/output/error.vue';
+import Prompt from '~/notebook/cells/prompt.vue';
+import Markdown from '~/notebook/cells/markdown.vue';
+import { errorOutputContent, relativeRawPath } from '../../mock_data';
+
+describe('notebook/cells/output/error.vue', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = mount(ErrorOutput, {
+ propsData: {
+ rawCode: errorOutputContent,
+ index: 1,
+ count: 2,
+ },
+ provide: { relativeRawPath },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ const findPrompt = () => wrapper.findComponent(Prompt);
+ const findMarkdown = () => wrapper.findComponent(Markdown);
+
+ it('renders the prompt', () => {
+ expect(findPrompt().props()).toMatchObject({ count: 2, showOutput: true, type: 'Out' });
+ });
+
+ it('renders the markdown', () => {
+ const expectedParsedMarkdown =
+ '```error\n' +
+ '---------------------------------------------------------------------------\n' +
+ 'NameError Traceback (most recent call last)\n' +
+ '/var/folders/cq/l637k4x13gx6y9p_gfs4c_gc0000gn/T/ipykernel_79203/294318627.py in <module>\n' +
+ '----> 1 To\n' +
+ '\n' +
+ "NameError: name 'To' is not defined\n" +
+ '```';
+
+ expect(findMarkdown().props()).toMatchObject({
+ cell: { source: [expectedParsedMarkdown] },
+ hidePrompt: true,
+ });
+ });
+});
diff --git a/spec/frontend/notebook/cells/output/index_spec.js b/spec/frontend/notebook/cells/output/index_spec.js
index 585cbb68eeb..1241c133b89 100644
--- a/spec/frontend/notebook/cells/output/index_spec.js
+++ b/spec/frontend/notebook/cells/output/index_spec.js
@@ -17,10 +17,6 @@ describe('Output component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('text output', () => {
beforeEach(() => {
const textType = json.cells[2];
diff --git a/spec/frontend/notebook/cells/prompt_spec.js b/spec/frontend/notebook/cells/prompt_spec.js
index 0cda0c5bc2b..4c864a9b930 100644
--- a/spec/frontend/notebook/cells/prompt_spec.js
+++ b/spec/frontend/notebook/cells/prompt_spec.js
@@ -6,10 +6,6 @@ describe('Prompt component', () => {
const mountComponent = ({ type }) => shallowMount(Prompt, { propsData: { type, count: 1 } });
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('input', () => {
beforeEach(() => {
wrapper = mountComponent({ type: 'In' });
diff --git a/spec/frontend/notebook/index_spec.js b/spec/frontend/notebook/index_spec.js
index b79000a3505..3c73d420703 100644
--- a/spec/frontend/notebook/index_spec.js
+++ b/spec/frontend/notebook/index_spec.js
@@ -1,16 +1,14 @@
import { mount } from '@vue/test-utils';
-import Vue, { nextTick } from 'vue';
+import { 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';
-const Component = Vue.extend(Notebook);
-
describe('Notebook component', () => {
let vm;
function buildComponent(notebook) {
- return mount(Component, {
+ return mount(Notebook, {
propsData: { notebook },
provide: { relativeRawPath: '' },
}).vm;
diff --git a/spec/frontend/notebook/mock_data.js b/spec/frontend/notebook/mock_data.js
index b1419e1256f..5c47cb5aa9b 100644
--- a/spec/frontend/notebook/mock_data.js
+++ b/spec/frontend/notebook/mock_data.js
@@ -1,2 +1,8 @@
export const relativeRawPath = '/test';
export const markdownCellContent = ['# Test'];
+export const errorOutputContent = [
+ '\u001b[0;31m---------------------------------------------------------------------------\u001b[0m',
+ '\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)',
+ '\u001b[0;32m/var/folders/cq/l637k4x13gx6y9p_gfs4c_gc0000gn/T/ipykernel_79203/294318627.py\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mTo\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m',
+ "\u001b[0;31mNameError\u001b[0m: name 'To' is not defined",
+];
diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js
index dfb05c85fc8..062cd098640 100644
--- a/spec/frontend/notes/components/comment_form_spec.js
+++ b/spec/frontend/notes/components/comment_form_spec.js
@@ -5,11 +5,12 @@ import MockAdapter from 'axios-mock-adapter';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import Autosave from '~/autosave';
import batchComments from '~/batch_comments/stores/modules/batch_comments';
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
+import { STATUS_CLOSED, STATUS_OPEN } from '~/issues/constants';
import axios from '~/lib/utils/axios_utils';
+import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import { HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status';
import CommentForm from '~/notes/components/comment_form.vue';
import CommentTypeDropdown from '~/notes/components/comment_type_dropdown.vue';
@@ -21,8 +22,7 @@ import { loggedOutnoteableData, notesDataMock, userDataMock, noteableDataMock }
jest.mock('autosize');
jest.mock('~/commons/nav/user_merge_requests');
-jest.mock('~/flash');
-jest.mock('~/autosave');
+jest.mock('~/alert');
Vue.use(Vuex);
@@ -32,7 +32,8 @@ describe('issue_comment_form component', () => {
let axiosMock;
const findCloseReopenButton = () => wrapper.findByTestId('close-reopen-button');
- const findTextArea = () => wrapper.findByTestId('comment-field');
+ const findMarkdownEditor = () => wrapper.findComponent(MarkdownEditor);
+ const findMarkdownEditorTextarea = () => findMarkdownEditor().find('textarea');
const findAddToReviewButton = () => wrapper.findByTestId('add-to-review-button');
const findAddCommentNowButton = () => wrapper.findByTestId('add-comment-now-button');
const findConfidentialNoteCheckbox = () => wrapper.findByTestId('internal-note-checkbox');
@@ -127,7 +128,6 @@ describe('issue_comment_form component', () => {
afterEach(() => {
axiosMock.restore();
- wrapper.destroy();
});
describe('user is logged in', () => {
@@ -136,7 +136,6 @@ describe('issue_comment_form component', () => {
mountComponent({ mountFunction: mount, initialData: { note: 'hello world' } });
jest.spyOn(wrapper.vm, 'saveNote').mockResolvedValue();
- jest.spyOn(wrapper.vm, 'resizeTextarea');
jest.spyOn(wrapper.vm, 'stopPolling');
findCloseReopenButton().trigger('click');
@@ -145,7 +144,6 @@ describe('issue_comment_form component', () => {
expect(wrapper.vm.note).toBe('');
expect(wrapper.vm.saveNote).toHaveBeenCalled();
expect(wrapper.vm.stopPolling).toHaveBeenCalled();
- expect(wrapper.vm.resizeTextarea).toHaveBeenCalled();
});
it('does not report errors in the UI when the save succeeds', async () => {
@@ -260,6 +258,18 @@ describe('issue_comment_form component', () => {
});
});
+ it('hides content editor switcher if feature flag content_editor_on_issues is off', () => {
+ mountComponent({ mountFunction: mount, features: { contentEditorOnIssues: false } });
+
+ expect(wrapper.text()).not.toContain('Rich text');
+ });
+
+ it('shows content editor switcher if feature flag content_editor_on_issues is on', () => {
+ mountComponent({ mountFunction: mount, features: { contentEditorOnIssues: true } });
+
+ expect(wrapper.text()).toContain('Rich text');
+ });
+
describe('textarea', () => {
describe('general', () => {
it.each`
@@ -268,13 +278,13 @@ describe('issue_comment_form component', () => {
${'internal note'} | ${true} | ${'Write an internal note or drag your files here…'}
`(
'should render textarea with placeholder for $noteType',
- ({ noteIsInternal, placeholder }) => {
- mountComponent({
- mountFunction: mount,
- initialData: { noteIsInternal },
- });
+ async ({ noteIsInternal, placeholder }) => {
+ mountComponent();
+
+ wrapper.vm.noteIsInternal = noteIsInternal;
+ await nextTick();
- expect(findTextArea().attributes('placeholder')).toBe(placeholder);
+ expect(findMarkdownEditor().props('formFieldProps').placeholder).toBe(placeholder);
},
);
@@ -290,13 +300,13 @@ describe('issue_comment_form component', () => {
await findCommentButton().trigger('click');
- expect(findTextArea().attributes('disabled')).toBe('disabled');
+ expect(findMarkdownEditor().find('textarea').attributes('disabled')).toBe('disabled');
});
it('should support quick actions', () => {
mountComponent({ mountFunction: mount });
- expect(findTextArea().attributes('data-supports-quick-actions')).toBe('true');
+ expect(findMarkdownEditor().props('supportsQuickActions')).toBe(true);
});
it('should link to markdown docs', () => {
@@ -336,63 +346,51 @@ describe('issue_comment_form component', () => {
it('should enter edit mode when arrow up is pressed', () => {
jest.spyOn(wrapper.vm, 'editCurrentUserLastNote');
- findTextArea().trigger('keydown.up');
+ findMarkdownEditorTextarea().trigger('keydown.up');
expect(wrapper.vm.editCurrentUserLastNote).toHaveBeenCalled();
});
- it('inits autosave', () => {
- expect(Autosave).toHaveBeenCalledWith(expect.any(Element), [
- 'Note',
- 'Issue',
- noteableDataMock.id,
- ]);
- });
- });
+ describe('event enter', () => {
+ describe('when no draft exists', () => {
+ it('should save note when cmd+enter is pressed', () => {
+ jest.spyOn(wrapper.vm, 'handleSave');
- describe('event enter', () => {
- beforeEach(() => {
- mountComponent({ mountFunction: mount });
- });
-
- describe('when no draft exists', () => {
- it('should save note when cmd+enter is pressed', () => {
- jest.spyOn(wrapper.vm, 'handleSave');
-
- findTextArea().trigger('keydown.enter', { metaKey: true });
+ findMarkdownEditorTextarea().trigger('keydown.enter', { metaKey: true });
- expect(wrapper.vm.handleSave).toHaveBeenCalledWith();
- });
+ expect(wrapper.vm.handleSave).toHaveBeenCalledWith();
+ });
- it('should save note when ctrl+enter is pressed', () => {
- jest.spyOn(wrapper.vm, 'handleSave');
+ it('should save note when ctrl+enter is pressed', () => {
+ jest.spyOn(wrapper.vm, 'handleSave');
- findTextArea().trigger('keydown.enter', { ctrlKey: true });
+ findMarkdownEditorTextarea().trigger('keydown.enter', { ctrlKey: true });
- expect(wrapper.vm.handleSave).toHaveBeenCalledWith();
+ expect(wrapper.vm.handleSave).toHaveBeenCalledWith();
+ });
});
- });
- describe('when a draft exists', () => {
- beforeEach(() => {
- store.registerModule('batchComments', batchComments());
- store.state.batchComments.drafts = [{ note: 'A' }];
- });
+ describe('when a draft exists', () => {
+ beforeEach(() => {
+ store.registerModule('batchComments', batchComments());
+ store.state.batchComments.drafts = [{ note: 'A' }];
+ });
- it('should save note draft when cmd+enter is pressed', () => {
- jest.spyOn(wrapper.vm, 'handleSaveDraft');
+ it('should save note draft when cmd+enter is pressed', () => {
+ jest.spyOn(wrapper.vm, 'handleSaveDraft');
- findTextArea().trigger('keydown.enter', { metaKey: true });
+ findMarkdownEditorTextarea().trigger('keydown.enter', { metaKey: true });
- expect(wrapper.vm.handleSaveDraft).toHaveBeenCalledWith();
- });
+ expect(wrapper.vm.handleSaveDraft).toHaveBeenCalledWith();
+ });
- it('should save note draft when ctrl+enter is pressed', () => {
- jest.spyOn(wrapper.vm, 'handleSaveDraft');
+ it('should save note draft when ctrl+enter is pressed', () => {
+ jest.spyOn(wrapper.vm, 'handleSaveDraft');
- findTextArea().trigger('keydown.enter', { ctrlKey: true });
+ findMarkdownEditorTextarea().trigger('keydown.enter', { ctrlKey: true });
- expect(wrapper.vm.handleSaveDraft).toHaveBeenCalledWith();
+ expect(wrapper.vm.handleSaveDraft).toHaveBeenCalledWith();
+ });
});
});
});
@@ -482,7 +480,7 @@ describe('issue_comment_form component', () => {
it(`makes an API call to open it`, () => {
mountComponent({
noteableType,
- noteableData: { ...noteableDataMock, state: constants.OPENED },
+ noteableData: { ...noteableDataMock, state: STATUS_OPEN },
mountFunction: mount,
});
@@ -496,7 +494,7 @@ describe('issue_comment_form component', () => {
it(`shows an error when the API call fails`, async () => {
mountComponent({
noteableType,
- noteableData: { ...noteableDataMock, state: constants.OPENED },
+ noteableData: { ...noteableDataMock, state: STATUS_OPEN },
mountFunction: mount,
});
@@ -517,7 +515,7 @@ describe('issue_comment_form component', () => {
it('makes an API call to close it', () => {
mountComponent({
noteableType,
- noteableData: { ...noteableDataMock, state: constants.CLOSED },
+ noteableData: { ...noteableDataMock, state: STATUS_CLOSED },
mountFunction: mount,
});
@@ -532,7 +530,7 @@ describe('issue_comment_form component', () => {
it(`shows an error when the API call fails`, async () => {
mountComponent({
noteableType,
- noteableData: { ...noteableDataMock, state: constants.CLOSED },
+ noteableData: { ...noteableDataMock, state: STATUS_CLOSED },
mountFunction: mount,
});
@@ -661,7 +659,7 @@ describe('issue_comment_form component', () => {
});
it('should not render submission form', () => {
- expect(findTextArea().exists()).toBe(false);
+ expect(findMarkdownEditor().exists()).toBe(false);
});
});
diff --git a/spec/frontend/notes/components/comment_type_dropdown_spec.js b/spec/frontend/notes/components/comment_type_dropdown_spec.js
index cabf551deba..b891c1f553d 100644
--- a/spec/frontend/notes/components/comment_type_dropdown_spec.js
+++ b/spec/frontend/notes/components/comment_type_dropdown_spec.js
@@ -24,10 +24,6 @@ describe('CommentTypeDropdown component', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
isInternalNote | buttonText
${false} | ${COMMENT_FORM.comment}
diff --git a/spec/frontend/notes/components/diff_discussion_header_spec.js b/spec/frontend/notes/components/diff_discussion_header_spec.js
index bb44563b87a..66b86ed3ce0 100644
--- a/spec/frontend/notes/components/diff_discussion_header_spec.js
+++ b/spec/frontend/notes/components/diff_discussion_header_spec.js
@@ -22,10 +22,6 @@ describe('diff_discussion_header component', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Avatar', () => {
const firstNoteAuthor = discussionMock.notes[0].author;
const findAvatarLink = () => wrapper.findComponent(GlAvatarLink);
diff --git a/spec/frontend/notes/components/discussion_actions_spec.js b/spec/frontend/notes/components/discussion_actions_spec.js
index e414ada1854..a9a20bd8bc3 100644
--- a/spec/frontend/notes/components/discussion_actions_spec.js
+++ b/spec/frontend/notes/components/discussion_actions_spec.js
@@ -38,15 +38,12 @@ describe('DiscussionActions', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('rendering', () => {
const createComponent = createComponentFactory();
it('renders reply placeholder, resolve discussion button, resolve with issue button and jump to next discussion button', () => {
createComponent();
+
expect(wrapper.findComponent(ReplyPlaceholder).exists()).toBe(true);
expect(wrapper.findComponent(ResolveDiscussionButton).exists()).toBe(true);
expect(wrapper.findComponent(ResolveWithIssueButton).exists()).toBe(true);
@@ -94,17 +91,15 @@ describe('DiscussionActions', () => {
it('emits showReplyForm event when clicking on reply placeholder', () => {
createComponent({}, { attachTo: document.body });
- jest.spyOn(wrapper.vm, '$emit');
wrapper.findComponent(ReplyPlaceholder).find('textarea').trigger('focus');
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('showReplyForm');
+ expect(wrapper.emitted().showReplyForm).toHaveLength(1);
});
it('emits resolve event when clicking on resolve button', () => {
createComponent();
- jest.spyOn(wrapper.vm, '$emit');
wrapper.findComponent(ResolveDiscussionButton).find('button').trigger('click');
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('resolve');
+ expect(wrapper.emitted().resolve).toHaveLength(1);
});
});
});
diff --git a/spec/frontend/notes/components/discussion_counter_spec.js b/spec/frontend/notes/components/discussion_counter_spec.js
index f4ec7f835bb..ac677841ee1 100644
--- a/spec/frontend/notes/components/discussion_counter_spec.js
+++ b/spec/frontend/notes/components/discussion_counter_spec.js
@@ -40,7 +40,6 @@ describe('DiscussionCounter component', () => {
afterEach(() => {
wrapper.vm.$destroy();
- wrapper = null;
});
describe('has no discussions', () => {
@@ -119,8 +118,6 @@ describe('DiscussionCounter component', () => {
toggleAllButton = wrapper.find('[data-testid="toggle-all-discussions-btn"]');
};
- afterEach(() => wrapper.destroy());
-
it('calls button handler when clicked', async () => {
await updateStoreWithExpanded(true);
diff --git a/spec/frontend/notes/components/discussion_filter_note_spec.js b/spec/frontend/notes/components/discussion_filter_note_spec.js
index 48f5030aa1a..e31155a028f 100644
--- a/spec/frontend/notes/components/discussion_filter_note_spec.js
+++ b/spec/frontend/notes/components/discussion_filter_note_spec.js
@@ -18,11 +18,6 @@ describe('DiscussionFilterNote component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('timelineContent renders a string containing instruction for switching feed type', () => {
expect(wrapper.find('[data-testid="discussion-filter-timeline-content"]').html()).toBe(
'<div data-testid="discussion-filter-timeline-content">You\'re only seeing <b>other activity</b> in the feed. To add a comment, switch to one of the following options.</div>',
diff --git a/spec/frontend/notes/components/discussion_navigator_spec.js b/spec/frontend/notes/components/discussion_navigator_spec.js
index 77ae7b2c3b5..6e095f63003 100644
--- a/spec/frontend/notes/components/discussion_navigator_spec.js
+++ b/spec/frontend/notes/components/discussion_navigator_spec.js
@@ -37,7 +37,6 @@ describe('notes/components/discussion_navigator', () => {
if (wrapper) {
wrapper.destroy();
}
- wrapper = null;
});
describe('on create', () => {
diff --git a/spec/frontend/notes/components/discussion_notes_replies_wrapper_spec.js b/spec/frontend/notes/components/discussion_notes_replies_wrapper_spec.js
index 8d5ea108b50..d11ca7ad1ec 100644
--- a/spec/frontend/notes/components/discussion_notes_replies_wrapper_spec.js
+++ b/spec/frontend/notes/components/discussion_notes_replies_wrapper_spec.js
@@ -19,10 +19,6 @@ describe('DiscussionNotesRepliesWrapper', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when normal discussion', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/notes/components/discussion_notes_spec.js b/spec/frontend/notes/components/discussion_notes_spec.js
index add2ed1ba8a..bc0c04f2d8a 100644
--- a/spec/frontend/notes/components/discussion_notes_spec.js
+++ b/spec/frontend/notes/components/discussion_notes_spec.js
@@ -53,11 +53,6 @@ describe('DiscussionNotes', () => {
store.dispatch('setNotesData', notesDataMock);
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('rendering', () => {
it('renders an element for each note in the discussion', () => {
createComponent();
diff --git a/spec/frontend/notes/components/discussion_reply_placeholder_spec.js b/spec/frontend/notes/components/discussion_reply_placeholder_spec.js
index 971e3987929..a9201b78669 100644
--- a/spec/frontend/notes/components/discussion_reply_placeholder_spec.js
+++ b/spec/frontend/notes/components/discussion_reply_placeholder_spec.js
@@ -17,10 +17,6 @@ describe('ReplyPlaceholder', () => {
const findTextarea = () => wrapper.findComponent({ ref: 'textarea' });
- afterEach(() => {
- wrapper.destroy();
- });
-
it('emits focus event on button click', async () => {
createComponent({ options: { attachTo: document.body } });
diff --git a/spec/frontend/notes/components/discussion_resolve_button_spec.js b/spec/frontend/notes/components/discussion_resolve_button_spec.js
index 17c3523cf48..4bd21842fec 100644
--- a/spec/frontend/notes/components/discussion_resolve_button_spec.js
+++ b/spec/frontend/notes/components/discussion_resolve_button_spec.js
@@ -23,10 +23,6 @@ describe('resolveDiscussionButton', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should emit a onClick event on button click', async () => {
const button = wrapper.findComponent(GlButton);
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 a185f11ffaa..3dfae45ec49 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
@@ -15,10 +15,6 @@ describe('ResolveWithIssueButton', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should have a link with the provided link property as href', () => {
const button = wrapper.findComponent(GlButton);
diff --git a/spec/frontend/notes/components/email_participants_warning_spec.js b/spec/frontend/notes/components/email_participants_warning_spec.js
index ab1a6b152a4..34b7524d8fb 100644
--- a/spec/frontend/notes/components/email_participants_warning_spec.js
+++ b/spec/frontend/notes/components/email_participants_warning_spec.js
@@ -4,11 +4,6 @@ import EmailParticipantsWarning from '~/notes/components/email_participants_warn
describe('Email Participants Warning Component', () => {
let wrapper;
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findMoreButton = () => wrapper.find('button');
const createWrapper = (emails) => {
diff --git a/spec/frontend/notes/components/note_actions/reply_button_spec.js b/spec/frontend/notes/components/note_actions/reply_button_spec.js
index 20b32b8c178..68b11fb3b1a 100644
--- a/spec/frontend/notes/components/note_actions/reply_button_spec.js
+++ b/spec/frontend/notes/components/note_actions/reply_button_spec.js
@@ -9,11 +9,6 @@ describe('ReplyButton', () => {
wrapper = shallowMount(ReplyButton);
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('emits startReplying on click', () => {
wrapper.findComponent(GlButton).vm.$emit('click');
diff --git a/spec/frontend/notes/components/note_actions/timeline_event_button_spec.js b/spec/frontend/notes/components/note_actions/timeline_event_button_spec.js
index 658e844a9b1..bee08ee0605 100644
--- a/spec/frontend/notes/components/note_actions/timeline_event_button_spec.js
+++ b/spec/frontend/notes/components/note_actions/timeline_event_button_spec.js
@@ -20,10 +20,6 @@ describe('NoteTimelineEventButton', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findTimelineButton = () => wrapper.findComponent(GlButton);
it('emits click-promote-comment-to-event', async () => {
diff --git a/spec/frontend/notes/components/note_actions_spec.js b/spec/frontend/notes/components/note_actions_spec.js
index 8630b7b7d07..63286927d53 100644
--- a/spec/frontend/notes/components/note_actions_spec.js
+++ b/spec/frontend/notes/components/note_actions_spec.js
@@ -77,7 +77,6 @@ describe('noteActions', () => {
});
afterEach(() => {
- wrapper.destroy();
axiosMock.restore();
});
@@ -203,7 +202,6 @@ describe('noteActions', () => {
});
afterEach(() => {
- wrapper.destroy();
axiosMock.restore();
});
@@ -226,7 +224,6 @@ describe('noteActions', () => {
});
afterEach(() => {
- wrapper.destroy();
axiosMock.restore();
});
@@ -248,10 +245,6 @@ describe('noteActions', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should not be possible to assign the comment author', testButtonDoesNotRender);
it('should not be possible to unassign the comment author', testButtonDoesNotRender);
});
diff --git a/spec/frontend/notes/components/note_attachment_spec.js b/spec/frontend/notes/components/note_attachment_spec.js
index 24632f8e427..7f44171f6cc 100644
--- a/spec/frontend/notes/components/note_attachment_spec.js
+++ b/spec/frontend/notes/components/note_attachment_spec.js
@@ -15,11 +15,6 @@ describe('Issue note attachment', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders attachment image if it is passed in attachment prop', () => {
createComponent({
image: 'test-image',
diff --git a/spec/frontend/notes/components/note_body_spec.js b/spec/frontend/notes/components/note_body_spec.js
index c71cf7666ab..b4f185004bb 100644
--- a/spec/frontend/notes/components/note_body_spec.js
+++ b/spec/frontend/notes/components/note_body_spec.js
@@ -49,10 +49,6 @@ describe('issue_note_body component', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render the note', () => {
expect(wrapper.find('.note-text').html()).toContain(note.note_html);
});
diff --git a/spec/frontend/notes/components/note_edited_text_spec.js b/spec/frontend/notes/components/note_edited_text_spec.js
index 0a5fe48ef94..577e1044588 100644
--- a/spec/frontend/notes/components/note_edited_text_spec.js
+++ b/spec/frontend/notes/components/note_edited_text_spec.js
@@ -1,3 +1,4 @@
+import { GlSprintf, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import NoteEditedText from '~/notes/components/note_edited_text.vue';
@@ -5,41 +6,63 @@ const propsData = {
actionText: 'Edited',
className: 'foo-bar',
editedAt: '2017-08-04T09:52:31.062Z',
- editedBy: {
- avatar_url: 'path',
- id: 1,
- name: 'Root',
- path: '/root',
- state: 'active',
- username: 'root',
- },
+ editedBy: null,
};
describe('NoteEditedText', () => {
let wrapper;
- beforeEach(() => {
+ const createWrapper = (props = {}) => {
wrapper = shallowMount(NoteEditedText, {
- propsData,
+ propsData: {
+ ...propsData,
+ ...props,
+ },
+ stubs: {
+ GlSprintf,
+ },
});
- });
+ };
- afterEach(() => {
- wrapper.destroy();
- });
+ const findUserElement = () => wrapper.findComponent(GlLink);
- it('should render block with provided className', () => {
- expect(wrapper.classes()).toContain(propsData.className);
- });
+ describe('default', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
- it('should render provided actionText', () => {
- expect(wrapper.text().trim()).toContain(propsData.actionText);
+ it('should render block with provided className', () => {
+ expect(wrapper.classes()).toContain(propsData.className);
+ });
+
+ it('should render provided actionText', () => {
+ expect(wrapper.text().trim()).toContain(propsData.actionText);
+ });
+
+ it('should not render user information', () => {
+ expect(findUserElement().exists()).toBe(false);
+ });
});
- it('should render provided user information', () => {
- const authorLink = wrapper.find('.js-user-link');
+ describe('edited note', () => {
+ const editedBy = {
+ avatar_url: 'path',
+ id: 1,
+ name: 'Root',
+ path: '/root',
+ state: 'active',
+ username: 'root',
+ };
+
+ beforeEach(() => {
+ createWrapper({ editedBy });
+ });
+
+ it('should render user information', () => {
+ const authorLink = findUserElement();
- expect(authorLink.attributes('href')).toEqual(propsData.editedBy.path);
- expect(authorLink.text().trim()).toEqual(propsData.editedBy.name);
+ expect(authorLink.attributes('href')).toEqual(editedBy.path);
+ expect(authorLink.text().trim()).toEqual(editedBy.name);
+ });
});
});
diff --git a/spec/frontend/notes/components/note_form_spec.js b/spec/frontend/notes/components/note_form_spec.js
index 90473e7ccba..59362e18098 100644
--- a/spec/frontend/notes/components/note_form_spec.js
+++ b/spec/frontend/notes/components/note_form_spec.js
@@ -48,10 +48,6 @@ describe('issue_note_form component', () => {
};
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('noteHash', () => {
beforeEach(() => {
wrapper = createComponentWrapper();
diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js
index 56c22b09e1b..b3d6fab7f91 100644
--- a/spec/frontend/notes/components/note_header_spec.js
+++ b/spec/frontend/notes/components/note_header_spec.js
@@ -44,11 +44,6 @@ describe('NoteHeader component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('does not render discussion actions when includeToggle is false', () => {
createComponent({
includeToggle: false,
diff --git a/spec/frontend/notes/components/note_signed_out_widget_spec.js b/spec/frontend/notes/components/note_signed_out_widget_spec.js
index 84f20e4ad58..d56ee234cd9 100644
--- a/spec/frontend/notes/components/note_signed_out_widget_spec.js
+++ b/spec/frontend/notes/components/note_signed_out_widget_spec.js
@@ -12,10 +12,6 @@ describe('NoteSignedOutWidget component', () => {
wrapper = shallowMount(NoteSignedOutWidget, { store });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders sign in link provided in the store', () => {
expect(wrapper.find(`a[href="${notesDataMock.newSessionPath}"]`).text()).toBe('sign in');
});
diff --git a/spec/frontend/notes/components/noteable_discussion_spec.js b/spec/frontend/notes/components/noteable_discussion_spec.js
index a90d8bdde06..ac0c037fe36 100644
--- a/spec/frontend/notes/components/noteable_discussion_spec.js
+++ b/spec/frontend/notes/components/noteable_discussion_spec.js
@@ -22,7 +22,6 @@ jest.mock('~/behaviors/markdown/render_gfm');
describe('noteable_discussion component', () => {
let store;
let wrapper;
- let originalGon;
beforeEach(() => {
window.mrTabs = {};
@@ -36,10 +35,6 @@ describe('noteable_discussion component', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should not render thread header for non diff threads', () => {
expect(wrapper.find('.discussion-header').exists()).toBe(false);
});
@@ -167,16 +162,6 @@ describe('noteable_discussion component', () => {
});
describe('signout widget', () => {
- beforeEach(() => {
- originalGon = { ...window.gon };
- window.gon = window.gon || {};
- });
-
- afterEach(() => {
- wrapper.destroy();
- window.gon = originalGon;
- });
-
describe('user is logged in', () => {
beforeEach(() => {
window.gon.current_user_id = userDataMock.id;
diff --git a/spec/frontend/notes/components/noteable_note_spec.js b/spec/frontend/notes/components/noteable_note_spec.js
index af1b4f64037..b158cfff10d 100644
--- a/spec/frontend/notes/components/noteable_note_spec.js
+++ b/spec/frontend/notes/components/noteable_note_spec.js
@@ -71,10 +71,6 @@ describe('issue_note', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('mutiline comments', () => {
beforeEach(() => {
createWrapper();
diff --git a/spec/frontend/notes/components/notes_activity_header_spec.js b/spec/frontend/notes/components/notes_activity_header_spec.js
index 5b3165bf401..2de491477b6 100644
--- a/spec/frontend/notes/components/notes_activity_header_spec.js
+++ b/spec/frontend/notes/components/notes_activity_header_spec.js
@@ -24,10 +24,6 @@ describe('~/notes/components/notes_activity_header.vue', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/notes/components/notes_app_spec.js b/spec/frontend/notes/components/notes_app_spec.js
index b08a22f8674..832264aa7d3 100644
--- a/spec/frontend/notes/components/notes_app_spec.js
+++ b/spec/frontend/notes/components/notes_app_spec.js
@@ -90,8 +90,9 @@ describe('note_app', () => {
});
afterEach(() => {
- wrapper.destroy();
axiosMock.restore();
+ // eslint-disable-next-line @gitlab/vtu-no-explicit-wrapper-destroy
+ wrapper.destroy();
});
describe('render', () => {
diff --git a/spec/frontend/notes/components/toggle_replies_widget_spec.js b/spec/frontend/notes/components/toggle_replies_widget_spec.js
index 8c3696e88b7..ef5f06ad2fa 100644
--- a/spec/frontend/notes/components/toggle_replies_widget_spec.js
+++ b/spec/frontend/notes/components/toggle_replies_widget_spec.js
@@ -30,10 +30,6 @@ describe('toggle replies widget for notes', () => {
const mountComponent = ({ collapsed = false }) =>
mountExtended(ToggleRepliesWidget, { propsData: { replies, collapsed } });
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('collapsed state', () => {
beforeEach(() => {
wrapper = mountComponent({ collapsed: true });
diff --git a/spec/frontend/notes/deprecated_notes_spec.js b/spec/frontend/notes/deprecated_notes_spec.js
index 6d3bc19bd45..40f10ca901b 100644
--- a/spec/frontend/notes/deprecated_notes_spec.js
+++ b/spec/frontend/notes/deprecated_notes_spec.js
@@ -23,7 +23,6 @@ const fixture = 'snippets/show.html';
let mockAxios;
window.project_uploads_path = `${TEST_HOST}/uploads`;
-window.gon = window.gon || {};
window.gl = window.gl || {};
gl.utils = gl.utils || {};
gl.utils.disableButtonIfEmptyField = () => {};
diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js
index c4c0dc58b0d..0d3ebea7af2 100644
--- a/spec/frontend/notes/stores/actions_spec.js
+++ b/spec/frontend/notes/stores/actions_spec.js
@@ -3,7 +3,7 @@ import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import testAction from 'helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants';
import Api from '~/api';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import toast from '~/vue_shared/plugins/global_toast';
import { EVENT_ISSUABLE_VUE_APP_CHANGE } from '~/issuable/constants';
import axios from '~/lib/utils/axios_utils';
@@ -36,7 +36,7 @@ import {
const TEST_ERROR_MESSAGE = 'Test error message';
const mockAlertDismiss = jest.fn();
-jest.mock('~/flash', () => ({
+jest.mock('~/alert', () => ({
createAlert: jest.fn().mockImplementation(() => ({
dismiss: mockAlertDismiss,
})),
@@ -876,7 +876,7 @@ describe('Actions Notes Store', () => {
const res = { errors: { base: ['something went wrong'] } };
const error = { message: 'Unprocessable entity', response: { data: res } };
- it('sets flash alert using errors.base message', async () => {
+ it('sets an alert using errors.base message', async () => {
const resp = await actions.saveNote(
{
commit() {},
@@ -906,6 +906,20 @@ describe('Actions Notes Store', () => {
expect(data).toBe(res);
expect(createAlert).not.toHaveBeenCalled();
});
+
+ it('dispatches clearDrafts is command names contains submit_review', async () => {
+ const response = { command_names: ['submit_review'], valid: true };
+ dispatch = jest.fn().mockResolvedValue(response);
+ await actions.saveNote(
+ {
+ commit() {},
+ dispatch,
+ },
+ payload,
+ );
+
+ expect(dispatch).toHaveBeenCalledWith('batchComments/clearDrafts');
+ });
});
});
@@ -946,7 +960,7 @@ describe('Actions Notes Store', () => {
});
});
- it('when service fails, flashes error message', () => {
+ it('when service fails, creates an alert with error message', () => {
const response = { response: { data: { message: TEST_ERROR_MESSAGE } } };
Api.applySuggestion.mockReturnValue(Promise.reject(response));
@@ -1439,10 +1453,6 @@ describe('Actions Notes Store', () => {
describe('fetchDiscussions', () => {
const discussion = { notes: [] };
- afterEach(() => {
- window.gon = {};
- });
-
it('updates the discussions and dispatches `updateResolvableDiscussionsCounts`', () => {
axiosMock.onAny().reply(HTTP_STATUS_OK, { discussion });
return testAction(
diff --git a/spec/frontend/notifications/components/custom_notifications_modal_spec.js b/spec/frontend/notifications/components/custom_notifications_modal_spec.js
index 70749557e61..0fbd073191e 100644
--- a/spec/frontend/notifications/components/custom_notifications_modal_spec.js
+++ b/spec/frontend/notifications/components/custom_notifications_modal_spec.js
@@ -2,7 +2,6 @@ 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 { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status';
@@ -66,8 +65,6 @@ describe('CustomNotificationsModal', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
mockAxios.restore();
});
@@ -87,24 +84,23 @@ describe('CustomNotificationsModal', () => {
describe('checkbox items', () => {
beforeEach(async () => {
+ const endpointUrl = '/api/v4/notification_settings';
+
+ mockAxios
+ .onGet(endpointUrl)
+ .reply(HTTP_STATUS_OK, mockNotificationSettingsResponses.default);
+
wrapper = 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({
- events: [
- { id: 'new_release', enabled: true, name: 'New release', loading: false },
- { id: 'new_note', enabled: false, name: 'New note', loading: true },
- ],
- });
+ wrapper.findComponent(GlModal).vm.$emit('show');
- await nextTick();
+ await waitForPromises();
});
it.each`
index | eventId | eventName | enabled | loading
${0} | ${'new_release'} | ${'New release'} | ${true} | ${false}
- ${1} | ${'new_note'} | ${'New note'} | ${false} | ${true}
+ ${1} | ${'new_note'} | ${'New note'} | ${false} | ${false}
`(
'renders a checkbox for "$eventName" with checked=$enabled',
async ({ index, eventName, enabled, loading }) => {
@@ -214,16 +210,9 @@ describe('CustomNotificationsModal', () => {
wrapper = createComponent({ injectedProperties });
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- events: [
- { id: 'new_release', enabled: true, name: 'New release', loading: false },
- { id: 'new_note', enabled: false, name: 'New note', loading: false },
- ],
- });
+ wrapper.findComponent(GlModal).vm.$emit('show');
- await nextTick();
+ await waitForPromises();
findCheckboxAt(1).vm.$emit('change', true);
@@ -241,19 +230,18 @@ describe('CustomNotificationsModal', () => {
);
it('shows a toast message when the request fails', async () => {
- mockAxios.onPut('/api/v4/notification_settings').reply(HTTP_STATUS_NOT_FOUND, {});
+ const endpointUrl = '/api/v4/notification_settings';
+
+ mockAxios
+ .onGet(endpointUrl)
+ .reply(HTTP_STATUS_OK, mockNotificationSettingsResponses.default);
+
+ mockAxios.onPut(endpointUrl).reply(HTTP_STATUS_NOT_FOUND, {});
wrapper = 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({
- events: [
- { id: 'new_release', enabled: true, name: 'New release', loading: false },
- { id: 'new_note', enabled: false, name: 'New note', loading: false },
- ],
- });
+ wrapper.findComponent(GlModal).vm.$emit('show');
- await nextTick();
+ await waitForPromises();
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 0f13de0e6d8..bae9b028cf7 100644
--- a/spec/frontend/notifications/components/notifications_dropdown_spec.js
+++ b/spec/frontend/notifications/components/notifications_dropdown_spec.js
@@ -25,7 +25,7 @@ describe('NotificationsDropdown', () => {
CustomNotificationsModal,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
provide: {
dropdownItems: mockDropdownItems,
@@ -61,8 +61,6 @@ describe('NotificationsDropdown', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
mockAxios.restore();
});
diff --git a/spec/frontend/observability/index_spec.js b/spec/frontend/observability/index_spec.js
new file mode 100644
index 00000000000..83f72ff72b5
--- /dev/null
+++ b/spec/frontend/observability/index_spec.js
@@ -0,0 +1,64 @@
+import { createWrapper } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+import renderObservability from '~/observability/index';
+import ObservabilityApp from '~/observability/components/observability_app.vue';
+import { SKELETON_VARIANTS_BY_ROUTE } from '~/observability/constants';
+
+describe('renderObservability', () => {
+ let element;
+ let vueInstance;
+ let component;
+
+ const OBSERVABILITY_ROUTES = Object.keys(SKELETON_VARIANTS_BY_ROUTE);
+ const SKELETON_VARIANTS = Object.values(SKELETON_VARIANTS_BY_ROUTE);
+
+ beforeEach(() => {
+ element = document.createElement('div');
+ element.setAttribute('id', 'js-observability-app');
+ element.dataset.observabilityIframeSrc = 'https://observe.gitlab.com/';
+ document.body.appendChild(element);
+
+ vueInstance = renderObservability();
+ component = createWrapper(vueInstance).findComponent(ObservabilityApp);
+ });
+
+ afterEach(() => {
+ element.remove();
+ });
+
+ it('should return a Vue instance', () => {
+ expect(vueInstance).toEqual(expect.any(Vue));
+ });
+
+ it('should render the ObservabilityApp component', () => {
+ expect(component.props('observabilityIframeSrc')).toBe('https://observe.gitlab.com/');
+ });
+
+ describe('skeleton variant', () => {
+ it.each`
+ pathDescription | path | variant
+ ${'dashboards'} | ${OBSERVABILITY_ROUTES[0]} | ${SKELETON_VARIANTS[0]}
+ ${'explore'} | ${OBSERVABILITY_ROUTES[1]} | ${SKELETON_VARIANTS[1]}
+ ${'manage dashboards'} | ${OBSERVABILITY_ROUTES[2]} | ${SKELETON_VARIANTS[2]}
+ ${'any other'} | ${'unknown/route'} | ${SKELETON_VARIANTS[0]}
+ `(
+ 'renders the $variant skeleton variant for $pathDescription path',
+ async ({ path, variant }) => {
+ component.vm.$router.push(path);
+ await nextTick();
+
+ expect(component.props('skeletonVariant')).toBe(variant);
+ },
+ );
+ });
+
+ it('handle route-update events', async () => {
+ component.vm.$router.push('/something?foo=bar');
+ component.vm.$emit('route-update', { url: '/some_path' });
+ expect(component.vm.$router.currentRoute.path).toBe('/something');
+ expect(component.vm.$router.currentRoute.query).toEqual({
+ foo: 'bar',
+ observability_path: '/some_path',
+ });
+ });
+});
diff --git a/spec/frontend/observability/observability_app_spec.js b/spec/frontend/observability/observability_app_spec.js
index e3bcd140d60..4a9be71b880 100644
--- a/spec/frontend/observability/observability_app_spec.js
+++ b/spec/frontend/observability/observability_app_spec.js
@@ -1,19 +1,20 @@
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ObservabilityApp from '~/observability/components/observability_app.vue';
import ObservabilitySkeleton from '~/observability/components/skeleton/index.vue';
-
-import { MESSAGE_EVENT_TYPE, SKELETON_VARIANTS_BY_ROUTE } from '~/observability/constants';
+import {
+ MESSAGE_EVENT_TYPE,
+ INLINE_EMBED_DIMENSIONS,
+ FULL_APP_DIMENSIONS,
+ SKELETON_VARIANT_EMBED,
+} from '~/observability/constants';
import { darkModeEnabled } from '~/lib/utils/color_utils';
jest.mock('~/lib/utils/color_utils');
-describe('Observability root app', () => {
+describe('ObservabilityApp', () => {
let wrapper;
- const replace = jest.fn();
- const $router = {
- replace,
- };
+
const $route = {
pathname: 'https://gitlab.com/gitlab-org/',
path: 'https://gitlab.com/gitlab-org/-/observability/dashboards',
@@ -26,21 +27,19 @@ describe('Observability root app', () => {
const TEST_IFRAME_SRC = 'https://observe.gitlab.com/9970/?groupId=14485840';
- const OBSERVABILITY_ROUTES = Object.keys(SKELETON_VARIANTS_BY_ROUTE);
-
- const SKELETON_VARIANTS = Object.values(SKELETON_VARIANTS_BY_ROUTE);
+ const TEST_USERNAME = 'test-user';
- const mountComponent = (route = $route) => {
+ const mountComponent = (props) => {
wrapper = shallowMountExtended(ObservabilityApp, {
propsData: {
observabilityIframeSrc: TEST_IFRAME_SRC,
+ ...props,
},
stubs: {
'observability-skeleton': ObservabilitySkeleton,
},
mocks: {
- $router,
- $route: route,
+ $route,
},
});
};
@@ -48,17 +47,11 @@ describe('Observability root app', () => {
const dispatchMessageEvent = (message) =>
window.dispatchEvent(new MessageEvent('message', message));
- afterEach(() => {
- wrapper.destroy();
+ beforeEach(() => {
+ gon.current_username = TEST_USERNAME;
});
describe('iframe src', () => {
- const TEST_USERNAME = 'test-user';
-
- beforeAll(() => {
- gon.current_username = TEST_USERNAME;
- });
-
it('should render an iframe with observabilityIframeSrc, decorated with light theme and username', () => {
darkModeEnabled.mockReturnValueOnce(false);
mountComponent();
@@ -92,48 +85,70 @@ describe('Observability root app', () => {
});
});
- describe('on GOUI_ROUTE_UPDATE', () => {
- it('should not call replace method from vue router if message event does not have url', () => {
- mountComponent();
- dispatchMessageEvent({
- type: MESSAGE_EVENT_TYPE.GOUI_ROUTE_UPDATE,
- payload: { data: 'some other data' },
+ describe('iframe kiosk query param', () => {
+ it('when inlineEmbed, it should set the proper kiosk query parameter', () => {
+ mountComponent({
+ inlineEmbed: true,
});
- expect(replace).not.toHaveBeenCalled();
+
+ const iframe = findIframe();
+
+ expect(iframe.attributes('src')).toBe(
+ `${TEST_IFRAME_SRC}&theme=light&username=${TEST_USERNAME}&kiosk=inline-embed`,
+ );
});
+ });
- it.each`
- condition | origin | observability_path | url
- ${'message origin is different from iframe source origin'} | ${'https://example.com'} | ${'/'} | ${'/explore'}
- ${'path is same as before (observability_path)'} | ${'https://observe.gitlab.com'} | ${'/foo?bar=test'} | ${'/foo?bar=test'}
- `(
- 'should not call replace method from vue router if $condition',
- async ({ origin, observability_path, url }) => {
- mountComponent({ ...$route, query: { observability_path } });
- dispatchMessageEvent({
- data: { type: MESSAGE_EVENT_TYPE.GOUI_ROUTE_UPDATE, payload: { url } },
- origin,
- });
- expect(replace).not.toHaveBeenCalled();
- },
- );
+ describe('iframe size', () => {
+ it('should set the specified size', () => {
+ mountComponent({
+ height: INLINE_EMBED_DIMENSIONS.HEIGHT,
+ width: INLINE_EMBED_DIMENSIONS.WIDTH,
+ });
+
+ const iframe = findIframe();
+
+ expect(iframe.attributes('width')).toBe(INLINE_EMBED_DIMENSIONS.WIDTH);
+ expect(iframe.attributes('height')).toBe(INLINE_EMBED_DIMENSIONS.HEIGHT);
+ });
+
+ it('should fallback to default size', () => {
+ mountComponent({});
+
+ const iframe = findIframe();
- it('should call replace method from vue router on message event callback', () => {
+ expect(iframe.attributes('width')).toBe(FULL_APP_DIMENSIONS.WIDTH);
+ expect(iframe.attributes('height')).toBe(FULL_APP_DIMENSIONS.HEIGHT);
+ });
+ });
+
+ describe('skeleton variant', () => {
+ it('sets the specified skeleton variant', () => {
+ mountComponent({ skeletonVariant: SKELETON_VARIANT_EMBED });
+ const props = wrapper.findComponent(ObservabilitySkeleton).props();
+
+ expect(props.variant).toBe(SKELETON_VARIANT_EMBED);
+ });
+
+ it('should have a default skeleton variant', () => {
+ mountComponent();
+ const props = wrapper.findComponent(ObservabilitySkeleton).props();
+
+ expect(props.variant).toBe('dashboards');
+ });
+ });
+
+ describe('on GOUI_ROUTE_UPDATE', () => {
+ it('should emit a route-update event', () => {
mountComponent();
+ const payload = { url: '/explore' };
dispatchMessageEvent({
- data: { type: MESSAGE_EVENT_TYPE.GOUI_ROUTE_UPDATE, payload: { url: '/explore' } },
+ data: { type: MESSAGE_EVENT_TYPE.GOUI_ROUTE_UPDATE, payload },
origin: 'https://observe.gitlab.com',
});
- expect(replace).toHaveBeenCalled();
- expect(replace).toHaveBeenCalledWith({
- name: 'https://gitlab.com/gitlab-org/',
- query: {
- otherQuery: 100,
- observability_path: '/explore',
- },
- });
+ expect(wrapper.emitted('route-update')[0]).toEqual([payload]);
});
});
@@ -167,34 +182,17 @@ describe('Observability root app', () => {
});
});
- describe('skeleton variant', () => {
- it.each`
- pathDescription | path | variant
- ${'dashboards'} | ${OBSERVABILITY_ROUTES[0]} | ${SKELETON_VARIANTS[0]}
- ${'explore'} | ${OBSERVABILITY_ROUTES[1]} | ${SKELETON_VARIANTS[1]}
- ${'manage dashboards'} | ${OBSERVABILITY_ROUTES[2]} | ${SKELETON_VARIANTS[2]}
- ${'any other'} | ${'unknown/route'} | ${SKELETON_VARIANTS[0]}
- `('renders the $variant skeleton variant for $pathDescription path', ({ path, variant }) => {
- mountComponent({ ...$route, path });
- const props = wrapper.findComponent(ObservabilitySkeleton).props();
-
- expect(props.variant).toBe(variant);
- });
- });
-
- describe('on observability ui unmount', () => {
- it('should remove message event and should not call replace method from vue router', () => {
+ describe('on unmount', () => {
+ it('should not emit any even on route update', () => {
mountComponent();
wrapper.destroy();
- // testing event cleanup logic, should not call on messege event after component is destroyed
-
dispatchMessageEvent({
data: { type: MESSAGE_EVENT_TYPE.GOUI_ROUTE_UPDATE, payload: { url: '/explore' } },
origin: 'https://observe.gitlab.com',
});
- expect(replace).not.toHaveBeenCalled();
+ expect(wrapper.emitted('route-update')).toBeUndefined();
});
});
});
diff --git a/spec/frontend/observability/skeleton_spec.js b/spec/frontend/observability/skeleton_spec.js
index a95597d8516..65dbb003743 100644
--- a/spec/frontend/observability/skeleton_spec.js
+++ b/spec/frontend/observability/skeleton_spec.js
@@ -6,8 +6,13 @@ import Skeleton from '~/observability/components/skeleton/index.vue';
import DashboardsSkeleton from '~/observability/components/skeleton/dashboards.vue';
import ExploreSkeleton from '~/observability/components/skeleton/explore.vue';
import ManageSkeleton from '~/observability/components/skeleton/manage.vue';
+import EmbedSkeleton from '~/observability/components/skeleton/embed.vue';
-import { SKELETON_VARIANTS_BY_ROUTE, DEFAULT_TIMERS } from '~/observability/constants';
+import {
+ SKELETON_VARIANTS_BY_ROUTE,
+ DEFAULT_TIMERS,
+ SKELETON_VARIANT_EMBED,
+} from '~/observability/constants';
describe('Skeleton component', () => {
let wrapper;
@@ -22,6 +27,8 @@ describe('Skeleton component', () => {
const findManageSkeleton = () => wrapper.findComponent(ManageSkeleton);
+ const findEmbedSkeleton = () => wrapper.findComponent(EmbedSkeleton);
+
const findAlert = () => wrapper.findComponent(GlAlert);
const mountComponent = ({ ...props } = {}) => {
@@ -97,16 +104,20 @@ describe('Skeleton component', () => {
${'dashboards'} | ${'variant is dashboards'} | ${SKELETON_VARIANTS[0]}
${'explore'} | ${'variant is explore'} | ${SKELETON_VARIANTS[1]}
${'manage'} | ${'variant is manage'} | ${SKELETON_VARIANTS[2]}
+ ${'embed'} | ${'variant is embed'} | ${SKELETON_VARIANT_EMBED}
${'default'} | ${'variant is not manage, dashboards or explore'} | ${'unknown'}
`('should render $skeletonType skeleton if $condition', async ({ skeletonType, variant }) => {
mountComponent({ variant });
jest.advanceTimersByTime(DEFAULT_TIMERS.CONTENT_WAIT_MS);
await nextTick();
- const showsDefaultSkeleton = !SKELETON_VARIANTS.includes(variant);
+ const showsDefaultSkeleton = ![...SKELETON_VARIANTS, SKELETON_VARIANT_EMBED].includes(
+ variant,
+ );
expect(findDashboardsSkeleton().exists()).toBe(skeletonType === SKELETON_VARIANTS[0]);
expect(findExploreSkeleton().exists()).toBe(skeletonType === SKELETON_VARIANTS[1]);
expect(findManageSkeleton().exists()).toBe(skeletonType === SKELETON_VARIANTS[2]);
+ expect(findEmbedSkeleton().exists()).toBe(skeletonType === SKELETON_VARIANT_EMBED);
expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(showsDefaultSkeleton);
});
diff --git a/spec/frontend/operation_settings/components/metrics_settings_spec.js b/spec/frontend/operation_settings/components/metrics_settings_spec.js
index 732dfdd42fb..ee450dfc851 100644
--- a/spec/frontend/operation_settings/components/metrics_settings_spec.js
+++ b/spec/frontend/operation_settings/components/metrics_settings_spec.js
@@ -2,7 +2,7 @@ import { GlButton, GlLink, GlFormGroup, GlFormInput, GlFormSelect } from '@gitla
import { mount, shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import { timezones } from '~/monitoring/format_date';
@@ -13,7 +13,7 @@ import MetricsSettings from '~/operation_settings/components/metrics_settings.vu
import store from '~/operation_settings/store';
jest.mock('~/lib/utils/url_utility');
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('operation settings external dashboard component', () => {
let wrapper;
@@ -198,7 +198,7 @@ describe('operation settings external dashboard component', () => {
expect(refreshCurrentPage).toHaveBeenCalled();
});
- it('creates flash banner on error', async () => {
+ it('creates alert banner on error', async () => {
mountComponent(false);
const message = 'mockErrorMessage';
axios.patch.mockRejectedValue({ response: { data: { message } } });
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js
index ff11c8843bb..8ba7e40d728 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_button_spec.js
@@ -27,11 +27,6 @@ describe('delete_button', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('tooltip', () => {
it('the title is controlled by tooltipTitle prop', () => {
mountComponent();
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_image_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_image_spec.js
index 620c96e8c9e..5a7cbdcff5b 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_image_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/delete_image_spec.js
@@ -46,11 +46,6 @@ describe('Delete Image', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('executes apollo mutate on doDelete', () => {
const mutate = jest.fn().mockResolvedValue({});
mountComponent({ mutate });
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js
index d45b993b5a2..9d187439ca3 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_alert_spec.js
@@ -19,11 +19,6 @@ describe('Delete alert', () => {
wrapper = shallowMount(component, { stubs: { GlSprintf }, propsData });
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when deleteAlertType is null', () => {
it('does not show the alert', () => {
mountComponent();
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_modal_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_modal_spec.js
index 16c9485e69e..860f9b3a0c1 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_modal_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/delete_modal_spec.js
@@ -30,15 +30,10 @@ describe('Delete Modal', () => {
const expectPrimaryActionStatus = (disabled = true) =>
expect(findModal().props('actionPrimary')).toMatchObject(
expect.objectContaining({
- attributes: [{ variant: 'danger' }, { disabled }],
+ attributes: { variant: 'danger', disabled },
}),
);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('contains a GlModal', () => {
mountComponent();
expect(findModal().exists()).toBe(true);
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 b37edac83f7..9e443234c34 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
@@ -73,7 +73,7 @@ describe('Details Header', () => {
apolloProvider,
propsData,
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
stubs: {
TitleArea,
@@ -85,9 +85,7 @@ describe('Details Header', () => {
afterEach(() => {
// if we want to mix createMockApollo and manual mocks we need to reset everything
- wrapper.destroy();
apolloProvider = undefined;
- wrapper = null;
});
describe('image name', () => {
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js
index ce5ecfe4608..d6c1b2c3f51 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/partial_cleanup_alert_spec.js
@@ -23,11 +23,6 @@ describe('Partial Cleanup alert', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it(`gl-alert has the correct properties`, () => {
mountComponent();
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js
index d83a5099bcd..3e1fd14475d 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/status_alert_spec.js
@@ -27,11 +27,6 @@ describe('Status Alert', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it.each`
status | title | variant | message | link
${DELETE_SCHEDULED} | ${SCHEDULED_FOR_DELETION_STATUS_TITLE} | ${'info'} | ${SCHEDULED_FOR_DELETION_STATUS_MESSAGE} | ${PACKAGE_DELETE_HELP_PAGE_PATH}
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js
index fa0d76762df..bfefe46c09b 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row_spec.js
@@ -50,16 +50,11 @@ describe('tags list row', () => {
},
propsData,
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('checkbox', () => {
it('exists', () => {
mountComponent();
@@ -283,26 +278,30 @@ describe('tags list row', () => {
textSrOnly: true,
category: 'tertiary',
right: true,
+ disabled: false,
});
});
- it.each`
- canDelete | digest | disabled | buttonDisabled
- ${true} | ${null} | ${true} | ${true}
- ${false} | ${'foo'} | ${true} | ${true}
- ${false} | ${null} | ${true} | ${true}
- ${true} | ${'foo'} | ${true} | ${true}
- ${true} | ${'foo'} | ${false} | ${false}
- `(
- 'is $visible that is visible when canDelete is $canDelete and digest is $digest and disabled is $disabled',
- ({ canDelete, digest, disabled, buttonDisabled }) => {
- mountComponent({ ...defaultProps, tag: { ...tag, canDelete, digest }, disabled });
+ it('has the correct classes', () => {
+ mountComponent();
- expect(findAdditionalActionsMenu().props('disabled')).toBe(buttonDisabled);
- expect(findAdditionalActionsMenu().classes('gl-opacity-0')).toBe(buttonDisabled);
- expect(findAdditionalActionsMenu().classes('gl-pointer-events-none')).toBe(buttonDisabled);
- },
- );
+ expect(findAdditionalActionsMenu().classes('gl-opacity-0')).toBe(false);
+ expect(findAdditionalActionsMenu().classes('gl-pointer-events-none')).toBe(false);
+ });
+
+ it('is not rendered when tag.canDelete is false', () => {
+ mountComponent({ ...defaultProps, tag: { ...tag, canDelete: false } });
+
+ expect(findAdditionalActionsMenu().exists()).toBe(false);
+ });
+
+ it('is hidden when disabled prop is set to true', () => {
+ mountComponent({ ...defaultProps, disabled: true });
+
+ expect(findAdditionalActionsMenu().props('disabled')).toBe(true);
+ expect(findAdditionalActionsMenu().classes('gl-opacity-0')).toBe(true);
+ expect(findAdditionalActionsMenu().classes('gl-pointer-events-none')).toBe(true);
+ });
describe('delete button', () => {
it('exists and has the correct attrs', () => {
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 1017ff06a25..09d0370efbf 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
@@ -68,15 +68,11 @@ describe('Tags List', () => {
resolver = jest.fn().mockResolvedValue(imageTagsMock());
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('registry list', () => {
- beforeEach(() => {
+ beforeEach(async () => {
mountComponent();
fireFirstSortUpdate();
- return waitForApolloRequestRender();
+ await waitForApolloRequestRender();
});
it('has a persisted search', () => {
@@ -98,6 +94,7 @@ describe('Tags List', () => {
pagination: tagsPageInfo,
items: tags,
idProperty: 'name',
+ hiddenDelete: false,
});
});
@@ -186,12 +183,23 @@ describe('Tags List', () => {
});
});
+ describe('when user does not have permission to delete list rows', () => {
+ it('sets registry list hiddenDelete prop to true', async () => {
+ resolver = jest.fn().mockResolvedValue(imageTagsMock({ canDelete: false }));
+ mountComponent();
+ fireFirstSortUpdate();
+ await waitForApolloRequestRender();
+
+ expect(findRegistryList().props('hiddenDelete')).toBe(true);
+ });
+ });
+
describe('when the list of tags is empty', () => {
- beforeEach(() => {
- resolver = jest.fn().mockResolvedValue(imageTagsMock([]));
+ beforeEach(async () => {
+ resolver = jest.fn().mockResolvedValue(imageTagsMock({ nodes: [] }));
mountComponent();
fireFirstSortUpdate();
- return waitForApolloRequestRender();
+ await waitForApolloRequestRender();
});
it('does not show the loader', () => {
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 88e79c513bc..8896185ce67 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
@@ -20,11 +20,6 @@ describe('TagsLoader component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('produces the correct amount of loaders', () => {
mountComponent();
expect(findGlSkeletonLoaders().length).toBe(1);
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js
index 535faebdd4e..0d1d2c53cab 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js
@@ -36,10 +36,6 @@ describe('cleanup_status', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
status | visible | text
${UNFINISHED_STATUS} | ${true} | ${CLEANUP_STATUS_UNFINISHED}
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 d2086943e4f..900ea61e4ea 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
@@ -26,10 +26,6 @@ describe('Registry Group Empty state', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('to match the default snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
index 75068591007..7da9c7533a0 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
@@ -1,4 +1,4 @@
-import { GlIcon, GlSprintf, GlSkeletonLoader, GlButton } from '@gitlab/ui';
+import { GlSprintf, GlSkeletonLoader, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { createMockDirective } from 'helpers/vue_mock_directive';
import { mockTracking } from 'helpers/tracking_helper';
@@ -49,16 +49,11 @@ describe('Image List Row', () => {
config: {},
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('image title and path', () => {
it('renders shortened name of image and contains a link to the details page', () => {
mountComponent();
@@ -206,13 +201,6 @@ describe('Image List Row', () => {
expect(findTagsCount().exists()).toBe(true);
});
- it('contains a tag icon', () => {
- mountComponent();
- const icon = findTagsCount().findComponent(GlIcon);
- expect(icon.exists()).toBe(true);
- expect(icon.props('name')).toBe('tag');
- });
-
describe('loading state', () => {
it('shows a loader when metadataLoading is true', () => {
mountComponent({ metadataLoading: true });
@@ -231,12 +219,12 @@ describe('Image List Row', () => {
it('with one tag in the image', () => {
mountComponent({ item: { ...item, tagsCount: 1 } });
- expect(findTagsCount().text()).toMatchInterpolatedText('1 Tag');
+ expect(findTagsCount().text()).toMatchInterpolatedText('1 tag');
});
it('with more than one tag in the image', () => {
mountComponent({ item: { ...item, tagsCount: 3 } });
- expect(findTagsCount().text()).toMatchInterpolatedText('3 Tags');
+ expect(findTagsCount().text()).toMatchInterpolatedText('3 tags');
});
});
});
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js
index 042b8383571..6c771887b88 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_spec.js
@@ -21,11 +21,6 @@ describe('Image List', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('list', () => {
it('contains one list element for each image', () => {
mountComponent();
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 8cfa8128021..e4d13143484 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
@@ -34,10 +34,6 @@ describe('Registry Project Empty state', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('to match the default snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
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 bcc8e41fce8..45304cc2329 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
@@ -35,11 +35,6 @@ describe('registry_header', () => {
await nextTick();
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('header', () => {
it('has a title', () => {
mountComponent({ metadataLoading: true });
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js b/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js
index e5b99f15e8c..cd54b856c97 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js
@@ -177,11 +177,12 @@ export const tagsMock = [
},
];
-export const imageTagsMock = (nodes = tagsMock) => ({
+export const imageTagsMock = ({ nodes = tagsMock, canDelete = true } = {}) => ({
data: {
containerRepository: {
id: containerRepositoryMock.id,
tagsCount: nodes.length,
+ canDelete,
tags: {
nodes,
pageInfo: { ...tagsPageInfo },
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 26f0e506829..888c3e5bffa 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
@@ -127,11 +127,6 @@ describe('Details Page', () => {
jest.spyOn(Tracking, 'event');
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when isLoading is true', () => {
it('shows the loader', () => {
mountComponent();
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 1e514d85e82..acc61157ab5 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
@@ -113,10 +113,6 @@ describe('List Page', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('contains registry header', async () => {
mountComponent();
fireFirstSortUpdate();
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 601f8abd34d..c2ae34ce697 100644
--- a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
+++ b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
@@ -31,11 +31,6 @@ import { proxyDetailsQuery, proxyData, pagination, proxyManifests } from './mock
const dummyApiVersion = 'v3000';
const dummyGrouptId = 1;
const dummyUrlRoot = '/gitlab';
-const dummyGon = {
- api_version: dummyApiVersion,
- relative_url_root: dummyUrlRoot,
-};
-let originalGon;
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${dummyGrouptId}/dependency_proxy/cache`;
Vue.use(VueApollo);
@@ -89,16 +84,16 @@ describe('DependencyProxyApp', () => {
beforeEach(() => {
resolver = jest.fn().mockResolvedValue(proxyDetailsQuery());
- originalGon = window.gon;
- window.gon = { ...dummyGon };
+ window.gon = {
+ api_version: dummyApiVersion,
+ relative_url_root: dummyUrlRoot,
+ };
mock = new MockAdapter(axios);
mock.onDelete(expectedUrl).reply(HTTP_STATUS_ACCEPTED, {});
});
afterEach(() => {
- wrapper.destroy();
- window.gon = originalGon;
mock.restore();
});
diff --git a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js
index 2f415bfd6f9..639a4fbb99d 100644
--- a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js
+++ b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js
@@ -25,10 +25,6 @@ describe('Manifests List', () => {
const findRows = () => wrapper.findAllComponents(ManifestRow);
const findPagination = () => wrapper.findComponent(GlKeysetPagination);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('has the correct title', () => {
createComponent();
diff --git a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_row_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_row_spec.js
index be3236d1f9c..ace5ce3a58d 100644
--- a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_row_spec.js
+++ b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_row_spec.js
@@ -29,10 +29,6 @@ describe('Manifest Row', () => {
const findTimeAgoTooltip = () => wrapper.findComponent(TimeagoTooltip);
const findStatus = () => wrapper.findByTestId('status');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('With a manifest on the DEFAULT status', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js
index a2e5cbdce8b..1e9b9b1ce47 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_row_spec.js
@@ -63,10 +63,6 @@ describe('Harbor artifact list row', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('list item', () => {
beforeEach(() => {
mountComponent({
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_spec.js
index b9d6dc2679e..786a4715731 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/details/artifacts_list_spec.js
@@ -26,10 +26,6 @@ describe('Harbor artifacts list', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when isLoading is true', () => {
beforeEach(() => {
mountComponent({
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/details/details_header_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/details/details_header_spec.js
index e8cc2b2e22d..d8fb91c085c 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/components/details/details_header_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/details/details_header_spec.js
@@ -20,10 +20,6 @@ describe('Harbor Details Header', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('artifact name', () => {
describe('missing image name', () => {
beforeEach(() => {
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_header_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_header_spec.js
index 7a6169d300c..9a7ad759dba 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_header_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_header_spec.js
@@ -29,10 +29,6 @@ describe('harbor_list_header', () => {
await nextTick();
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('header', () => {
it('has a title', () => {
mountComponent({ metadataLoading: true });
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_row_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_row_spec.js
index b62d4e8836b..1e031e0557a 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_row_spec.js
@@ -28,10 +28,6 @@ describe('Harbor List Row', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('image title and path', () => {
it('contains a link to the details page', () => {
mountComponent();
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_spec.js
index e7e74a0da58..a1803ecf7fb 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/list/harbor_list_spec.js
@@ -20,10 +20,6 @@ describe('Harbor List', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('list', () => {
it('contains one list element for each image', () => {
mountComponent();
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_header_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_header_spec.js
index 5e299a269e3..9370ff1fdd4 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_header_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_header_spec.js
@@ -26,10 +26,6 @@ describe('Harbor Tags Header', () => {
totalPages: 1,
};
- afterEach(() => {
- wrapper.destroy();
- });
-
beforeEach(() => {
mountComponent({
propsData: { artifactDetail: mockArtifactDetail, pageInfo: mockPageInfo, tagsLoading: false },
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_row_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_row_spec.js
index 849215e286b..0b2ce01ebf6 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_row_spec.js
@@ -37,10 +37,6 @@ describe('Harbor tag list row', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('list item', () => {
beforeEach(() => {
mountComponent({
diff --git a/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_spec.js b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_spec.js
index 4c6b2b6daaa..e2a2a584b7d 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/components/tags/tags_list_spec.js
@@ -24,10 +24,6 @@ describe('Harbor Tags List', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when isLoading is true', () => {
beforeEach(() => {
mountComponent({
diff --git a/spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js b/spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js
index 69765d31674..90c3d9082f7 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/pages/details_spec.js
@@ -74,10 +74,6 @@ describe('Harbor Details Page', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when isLoading is true', () => {
it('shows the loader', () => {
mountComponent();
diff --git a/spec/frontend/packages_and_registries/harbor_registry/pages/list_spec.js b/spec/frontend/packages_and_registries/harbor_registry/pages/list_spec.js
index 97d30e6fe99..63ea8feb1e7 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/pages/list_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/pages/list_spec.js
@@ -60,10 +60,6 @@ describe('Harbor List Page', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('contains harbor registry header', async () => {
mountComponent();
fireFirstSortUpdate();
diff --git a/spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js b/spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js
index 10901c6ec1e..6002faa1fa3 100644
--- a/spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js
+++ b/spec/frontend/packages_and_registries/harbor_registry/pages/tags_spec.js
@@ -60,10 +60,6 @@ describe('Harbor Tags page', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('contains tags header', () => {
mountComponent();
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 e74375b7705..f8130287c12 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
@@ -86,10 +86,6 @@ describe('PackagesApp', () => {
const findTerraformInstallation = () => wrapper.findComponent(TerraformInstallation);
const findPackageFiles = () => wrapper.findComponent(PackageFiles);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the app and displays the package title', async () => {
createComponent();
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 b504f7489ab..148e87699f1 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
@@ -39,10 +39,6 @@ describe('PackageTitle', () => {
const pipelineProject = () => wrapper.find('[data-testid="pipeline-project"]');
const packageRef = () => wrapper.find('[data-testid="package-ref"]');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('module title', () => {
it('is correctly bound', async () => {
await createComponent();
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/file_sha_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/file_sha_spec.js
index d7caa8ca2d8..7352afff051 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/file_sha_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/file_sha_spec.js
@@ -23,10 +23,6 @@ describe('FileSha', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders', () => {
createComponent();
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js
index b76d7c2b57b..c3e0818fc11 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js
@@ -37,11 +37,6 @@ describe('Package Files', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('rows', () => {
it('renders a single file for an npm package', () => {
createComponent();
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js
index 0cbe2755f7e..a650aba464e 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_history_spec.js
@@ -30,11 +30,6 @@ describe('Package History', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findHistoryElement = (testId) => wrapper.find(`[data-testid="${testId}"]`);
const findElementLink = (container) => container.findComponent(GlLink);
const findElementTimeAgo = (container) => container.findComponent(TimeAgoTooltip);
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 78c1b840dbc..94797f01d16 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
@@ -30,10 +30,6 @@ describe('TerraformInstallation', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders all the messages', () => {
expect(wrapper.element).toMatchSnapshot();
});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js
index bb970336b94..ea4d268d84e 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/store/actions_spec.js
@@ -1,6 +1,6 @@
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
-import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/flash';
+import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/alert';
import { FETCH_PACKAGE_VERSIONS_ERROR } from '~/packages_and_registries/infrastructure_registry/details/constants';
import {
fetchPackageVersions,
@@ -15,7 +15,7 @@ import {
} from '~/packages_and_registries/shared/constants';
import { npmPackage as packageEntity } from '../../mock_data';
-jest.mock('~/flash.js');
+jest.mock('~/alert');
jest.mock('~/api.js');
describe('Actions Package details store', () => {
@@ -53,7 +53,7 @@ describe('Actions Package details store', () => {
expect(Api.projectPackage).toHaveBeenCalledWith(packageEntity.project_id, packageEntity.id);
});
- it('should create flash on API error', async () => {
+ it('should create alert on API error', async () => {
Api.projectPackage = jest.fn().mockRejectedValue();
await testAction(
@@ -83,7 +83,7 @@ describe('Actions Package details store', () => {
packageEntity.id,
);
});
- it('should create flash on API error', async () => {
+ it('should create alert on API error', async () => {
Api.deleteProjectPackage = jest.fn().mockRejectedValue();
await testAction(deletePackage, undefined, { packageEntity }, [], []);
@@ -118,7 +118,7 @@ describe('Actions Package details store', () => {
});
});
- it('should create flash on API error', async () => {
+ it('should create alert on API error', async () => {
Api.deleteProjectPackageFile = jest.fn().mockRejectedValue();
await testAction(deletePackageFile, fileId, { packageEntity }, [], []);
expect(createAlert).toHaveBeenCalledWith({
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 801cde8582e..d0841c6110f 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
@@ -49,9 +49,7 @@ exports[`packages_list_app renders 1`] = `
Learn how to
<b-link-stub
class="gl-link"
- event="click"
href="helpUrl"
- routertag="a"
target="_blank"
>
publish and share your packages
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 a086c20a5e7..a89247c0a97 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
@@ -55,11 +55,6 @@ describe('Infrastructure Search', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('has a registry search component', () => {
mountComponent();
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 aca6b0942cc..7c7faa8a3b0 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
@@ -22,11 +22,6 @@ describe('Infrastructure Title', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('title area', () => {
beforeEach(() => {
mountComponent();
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 d237023d0cd..47d36d11e35 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
@@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import setWindowLocation from 'helpers/set_window_location_helper';
-import { createAlert, VARIANT_INFO } from '~/flash';
+import { createAlert, VARIANT_INFO } from '~/alert';
import * as commonUtils from '~/lib/utils/common_utils';
import PackageListApp from '~/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue';
import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '~/packages_and_registries/infrastructure_registry/list/constants';
@@ -14,7 +14,7 @@ import InfrastructureSearch from '~/packages_and_registries/infrastructure_regis
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
jest.mock('~/lib/utils/common_utils');
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(Vuex);
@@ -72,10 +72,6 @@ describe('packages_list_app', () => {
mountComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders', () => {
createStore({ packageCount: 1 });
mountComponent();
@@ -217,7 +213,7 @@ describe('packages_list_app', () => {
setWindowLocation(originalLocation);
});
- it(`creates a flash if the query string contains ${SHOW_DELETE_SUCCESS_ALERT}`, () => {
+ it(`creates an alert if the query string contains ${SHOW_DELETE_SUCCESS_ALERT}`, () => {
mountComponent();
expect(createAlert).toHaveBeenCalledWith({
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 0164d92ce34..51445942eaa 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
@@ -4,13 +4,13 @@ import Vue from 'vue';
import { last } from 'lodash';
import Vuex from 'vuex';
import stubChildren from 'helpers/stub_children';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import PackagesList from '~/packages_and_registries/infrastructure_registry/list/components/packages_list.vue';
import PackagesListRow from '~/packages_and_registries/infrastructure_registry/shared/package_list_row.vue';
import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
import DeletePackageModal from '~/packages_and_registries/shared/components/delete_package_modal.vue';
import { TRACKING_ACTIONS } from '~/packages_and_registries/shared/constants';
import { TRACK_CATEGORY } from '~/packages_and_registries/infrastructure_registry/shared/constants';
-import Tracking from '~/tracking';
import { packageList } from '../../mock_data';
Vue.use(Vuex);
@@ -72,11 +72,6 @@ describe('packages_list', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when is loading', () => {
beforeEach(() => {
mountComponent({
@@ -179,23 +174,23 @@ describe('packages_list', () => {
});
describe('tracking', () => {
- let eventSpy;
+ let trackingSpy = null;
beforeEach(() => {
mountComponent();
- eventSpy = jest.spyOn(Tracking, 'event');
- // 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: { package_type: 'conan' } });
- });
-
- it('deleteItemConfirmation calls event', () => {
- wrapper.vm.deleteItemConfirmation();
- expect(eventSpy).toHaveBeenCalledWith(
- TRACK_CATEGORY,
- TRACKING_ACTIONS.DELETE_PACKAGE,
- expect.any(Object),
- );
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ });
+
+ afterEach(() => {
+ unmockTracking();
+ });
+
+ it('deleteItemConfirmation calls event', async () => {
+ await findPackageListDeleteModal().vm.$emit('ok');
+
+ expect(trackingSpy).toHaveBeenCalledWith(TRACK_CATEGORY, TRACKING_ACTIONS.DELETE_PACKAGE, {
+ category: TRACK_CATEGORY,
+ });
});
});
});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js
index 2c185e040f4..4f051264172 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/list/stores/actions_spec.js
@@ -2,14 +2,14 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { MISSING_DELETE_PATH_ERROR } from '~/packages_and_registries/infrastructure_registry/list/constants';
import * as actions from '~/packages_and_registries/infrastructure_registry/list/stores/actions';
import * as types from '~/packages_and_registries/infrastructure_registry/list/stores/mutation_types';
import { DELETE_PACKAGE_ERROR_MESSAGE } from '~/packages_and_registries/shared/constants';
-jest.mock('~/flash.js');
+jest.mock('~/alert');
jest.mock('~/api.js');
describe('Actions Package list store', () => {
@@ -96,7 +96,7 @@ describe('Actions Package list store', () => {
});
});
- it('should create flash on API error', async () => {
+ it('should create alert on API error', async () => {
Api.projectPackages = jest.fn().mockRejectedValue();
await testAction(
actions.requestPackagesList,
@@ -198,7 +198,7 @@ describe('Actions Package list store', () => {
);
});
- it('should stop the loading and call create flash on api error', async () => {
+ it('should stop the loading and call create alert on api error', async () => {
mock.onDelete(payload._links.delete_api_path).replyOnce(HTTP_STATUS_BAD_REQUEST);
await testAction(
actions.requestDeletePackage,
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 721bdd34a4f..37ca420ae77 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
@@ -49,16 +49,11 @@ describe('packages_list_row', () => {
disableDelete,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders', () => {
mountComponent();
expect(wrapper.element).toMatchSnapshot();
diff --git a/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js b/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js
index 9c1ebf5a2eb..d0817a8678e 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/delete_modal_spec.js
@@ -45,7 +45,7 @@ describe('DeleteModal', () => {
it('passes actionPrimary prop', () => {
expect(findModal().props('actionPrimary')).toStrictEqual({
text: 'Permanently delete',
- attributes: [{ variant: 'danger' }, { category: 'primary' }],
+ attributes: { variant: 'danger', category: 'primary' },
});
});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap
index 67f1906f6fd..9b429c39faa 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/maven_installation_spec.js.snap
@@ -89,8 +89,9 @@ exports[`MavenInstallation maven renders all the messages 1`] = `
/>
<code-instruction-stub
+ class="gl-w-20 gl-mt-5"
copytext="Copy Maven command"
- instruction="mvn dependency:get -Dartifact=appGroup:appName:appVersion"
+ instruction="mvn install"
label="Maven Command"
trackingaction="copy_maven_command"
trackinglabel="code_instruction"
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap
index b2375da7b11..5d390730ef1 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/__snapshots__/pypi_installation_spec.js.snap
@@ -20,7 +20,7 @@ exports[`PypiInstallation renders all the messages 1`] = `
<!---->
<button
aria-expanded="false"
- aria-haspopup="true"
+ aria-haspopup="menu"
class="btn dropdown-toggle btn-default btn-md gl-button gl-dropdown-toggle"
id="__BVID__27__BV_toggle_"
type="button"
@@ -59,7 +59,6 @@ exports[`PypiInstallation renders all the messages 1`] = `
</div>
<fieldset
- aria-describedby="installation-pip-command-group__BV_description_"
class="form-group gl-form-group"
id="installation-pip-command-group"
>
@@ -75,12 +74,7 @@ exports[`PypiInstallation renders all the messages 1`] = `
<!---->
</legend>
- <div
- aria-labelledby="installation-pip-command-group__BV_label_"
- class="bv-no-focus-ring"
- role="group"
- tabindex="-1"
- >
+ <div>
<div
data-testid="pip-command"
id="installation-pip-command"
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js
index 4f3d780b149..2e59c27cc1b 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/additional_metadata_spec.js
@@ -65,11 +65,6 @@ describe('Package Additional metadata', () => {
jest.spyOn(Sentry, 'captureException').mockImplementation();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findTitle = () => wrapper.findByTestId('title');
const findMainArea = () => wrapper.findByTestId('main');
const findComponentIs = () => wrapper.findByTestId('component-is');
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js
index 0aba8f7efc7..a6298ebdea7 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/composer_installation_spec.js
@@ -34,10 +34,6 @@ describe('ComposerInstallation', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('install command switch', () => {
it('has the installation title component', () => {
createComponent();
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js
index bf9425def9a..70534b1d0a6 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/conan_installation_spec.js
@@ -33,10 +33,6 @@ describe('ConanInstallation', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders all the messages', () => {
expect(wrapper.element).toMatchSnapshot();
});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/dependency_row_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/dependency_row_spec.js
index 9aed5b90c73..19aedf120b2 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/dependency_row_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/dependency_row_spec.js
@@ -19,10 +19,6 @@ describe('DependencyRow', () => {
const dependencyVersion = () => wrapper.findByTestId('version-pattern');
const dependencyFramework = () => wrapper.findByTestId('target-framework');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('renders', () => {
it('full dependency', () => {
createComponent();
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/file_sha_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/file_sha_spec.js
index feed7a7c46c..a9428773a60 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/file_sha_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/file_sha_spec.js
@@ -23,10 +23,6 @@ describe('FileSha', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders', () => {
createComponent();
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/installation_title_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/installation_title_spec.js
index 5fe795f768e..a2d30be13c2 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/installation_title_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/installation_title_spec.js
@@ -20,10 +20,6 @@ describe('InstallationTitle', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('has a title', () => {
createComponent();
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js
index 8bb05b00e65..d35d95e319f 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/installations_commands_spec.js
@@ -40,10 +40,6 @@ describe('InstallationCommands', () => {
const pypiInstallation = () => wrapper.findComponent(PypiInstallation);
const composerInstallation = () => wrapper.findComponent(ComposerInstallation);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('installation instructions', () => {
describe.each`
packageEntity | selector
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js
index fc60039db30..5ea81dccf7d 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/maven_installation_spec.js
@@ -35,7 +35,7 @@ describe('MavenInstallation', () => {
<artifactId>appName</artifactId>
<version>appVersion</version>
</dependency>`;
- const mavenCommandStr = 'mvn dependency:get -Dartifact=appGroup:appName:appVersion';
+ const mavenCommandStr = 'mvn install';
const mavenSetupXml = `<repositories>
<repository>
<id>gitlab-maven</id>
@@ -79,10 +79,6 @@ describe('MavenInstallation', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('install command switch', () => {
it('has the installation title component', () => {
createComponent();
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/composer_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/composer_spec.js
index bb6846d354f..f2f3b8507c3 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/composer_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/composer_spec.js
@@ -18,11 +18,6 @@ describe('Composer Metadata', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findComposerTargetSha = () => wrapper.findByTestId('composer-target-sha');
const findComposerTargetShaCopyButton = () => wrapper.findComponent(ClipboardButton);
const findComposerJson = () => wrapper.findByTestId('composer-json');
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/conan_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/conan_spec.js
index e7e47401aa1..2832dc3a712 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/conan_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/conan_spec.js
@@ -19,11 +19,6 @@ describe('Conan Metadata', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findConanRecipe = () => wrapper.findByTestId('conan-recipe');
beforeEach(() => {
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/maven_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/maven_spec.js
index 8680d983042..7b253a26fc7 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/maven_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/maven_spec.js
@@ -19,11 +19,6 @@ describe('Maven Metadata', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findMavenApp = () => wrapper.findByTestId('maven-app');
const findMavenGroup = () => wrapper.findByTestId('maven-group');
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/nuget_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/nuget_spec.js
index af3692023f0..9fb467f9af1 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/nuget_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/nuget_spec.js
@@ -19,11 +19,6 @@ describe('Nuget Metadata', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findNugetSource = () => wrapper.findByTestId('nuget-source');
const findNugetLicense = () => wrapper.findByTestId('nuget-license');
const findElementLink = (container) => container.findComponent(GlLink);
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/pypi_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/pypi_spec.js
index d7c6ea8379d..67f5fbc9e80 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/metadata/pypi_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/metadata/pypi_spec.js
@@ -20,11 +20,6 @@ describe('Package Additional Metadata', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findPypiRequiredPython = () => wrapper.findByTestId('pypi-required-python');
beforeEach(() => {
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js
index 8c0e2d948ca..e711f9ee45d 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/npm_installation_spec.js
@@ -51,10 +51,6 @@ describe('NpmInstallation', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders all the messages', () => {
expect(wrapper.element).toMatchSnapshot();
});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js
index 9449c40c7c6..bcc0b78bfce 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/nuget_installation_spec.js
@@ -36,10 +36,6 @@ describe('NugetInstallation', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders all the messages', () => {
expect(wrapper.element).toMatchSnapshot();
});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js
index 529a6a22ddf..1dcac017ccf 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_files_spec.js
@@ -48,10 +48,6 @@ describe('Package Files', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('rows', () => {
it('renders a single file for an npm package', () => {
createComponent();
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js
index bb2fa9eb6f5..ed470f63b8a 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_history_spec.js
@@ -63,11 +63,6 @@ describe('Package History', () => {
jest.spyOn(Sentry, 'captureException').mockImplementation();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findPackageHistoryLoader = () => wrapper.findComponent(PackageHistoryLoader);
const findHistoryElement = (testId) => wrapper.findByTestId(testId);
const findElementLink = (container) => container.findComponent(GlLink);
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 1fda77f2aaa..ba21cdaca3b 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
@@ -38,7 +38,7 @@ describe('PackageTitle', () => {
},
provide,
directives: {
- GlResizeObserver: createMockDirective(),
+ GlResizeObserver: createMockDirective('gl-resize-observer'),
},
});
await nextTick();
@@ -55,10 +55,6 @@ describe('PackageTitle', () => {
const findSubHeaderText = () => wrapper.findByTestId('sub-header');
const findSubHeaderTimeAgo = () => wrapper.findComponent(TimeAgoTooltip);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('renders', () => {
it('without tags', async () => {
await createComponent({ ...packageData(), packageFiles: { nodes: packageFiles() } });
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js
index 27c0ab96cfc..fc7f5c80d45 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/package_versions_list_spec.js
@@ -1,14 +1,18 @@
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { stubComponent } from 'helpers/stub_component';
import DeleteModal from '~/packages_and_registries/package_registry/components/delete_modal.vue';
+import DeletePackageModal from '~/packages_and_registries/shared/components/delete_package_modal.vue';
import PackageVersionsList from '~/packages_and_registries/package_registry/components/details/package_versions_list.vue';
import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
import VersionRow from '~/packages_and_registries/package_registry/components/details/version_row.vue';
import Tracking from '~/tracking';
import {
+ CANCEL_DELETE_PACKAGE_VERSION_TRACKING_ACTION,
CANCEL_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION,
+ DELETE_PACKAGE_VERSION_TRACKING_ACTION,
DELETE_PACKAGE_VERSIONS_TRACKING_ACTION,
+ REQUEST_DELETE_PACKAGE_VERSION_TRACKING_ACTION,
REQUEST_DELETE_PACKAGE_VERSIONS_TRACKING_ACTION,
} from '~/packages_and_registries/package_registry/constants';
import { packageData } from '../../mock_data';
@@ -22,7 +26,7 @@ describe('PackageVersionsList', () => {
name: 'version 1',
}),
packageData({
- id: `gid://gitlab/Packages::Package/112`,
+ id: 'gid://gitlab/Packages::Package/112',
name: 'version 2',
}),
];
@@ -31,8 +35,10 @@ describe('PackageVersionsList', () => {
findLoader: () => wrapper.findComponent(PackagesListLoader),
findRegistryList: () => wrapper.findComponent(RegistryList),
findEmptySlot: () => wrapper.findComponent(EmptySlotStub),
- findListRow: () => wrapper.findAllComponents(VersionRow),
+ findListRow: () => wrapper.findComponent(VersionRow),
+ findAllListRow: () => wrapper.findAllComponents(VersionRow),
findDeletePackagesModal: () => wrapper.findComponent(DeleteModal),
+ findPackageListDeleteModal: () => wrapper.findComponent(DeletePackageModal),
};
const mountComponent = (props) => {
wrapper = shallowMountExtended(PackageVersionsList, {
@@ -118,16 +124,16 @@ describe('PackageVersionsList', () => {
});
it('displays package version rows', () => {
- expect(uiElements.findListRow().exists()).toEqual(true);
- expect(uiElements.findListRow()).toHaveLength(packageList.length);
+ expect(uiElements.findAllListRow().exists()).toEqual(true);
+ expect(uiElements.findAllListRow()).toHaveLength(packageList.length);
});
it('binds the correct props', () => {
- expect(uiElements.findListRow().at(0).props()).toMatchObject({
+ expect(uiElements.findAllListRow().at(0).props()).toMatchObject({
packageEntity: expect.objectContaining(packageList[0]),
});
- expect(uiElements.findListRow().at(1).props()).toMatchObject({
+ expect(uiElements.findAllListRow().at(1).props()).toMatchObject({
packageEntity: expect.objectContaining(packageList[1]),
});
});
@@ -159,6 +165,68 @@ describe('PackageVersionsList', () => {
});
});
+ describe.each`
+ description | finderFunction | deletePayload
+ ${'when the user can destroy the package'} | ${uiElements.findListRow} | ${packageList[0]}
+ ${'when the user can bulk destroy packages and deletes only one package'} | ${uiElements.findRegistryList} | ${[packageList[0]]}
+ `('$description', ({ finderFunction, deletePayload }) => {
+ let eventSpy;
+ const category = 'UI::NpmPackages';
+ const { findPackageListDeleteModal } = uiElements;
+
+ beforeEach(() => {
+ eventSpy = jest.spyOn(Tracking, 'event');
+ mountComponent({ canDestroy: true });
+ finderFunction().vm.$emit('delete', deletePayload);
+ });
+
+ it('passes itemToBeDeleted to the modal', () => {
+ expect(findPackageListDeleteModal().props('itemToBeDeleted')).toStrictEqual(packageList[0]);
+ });
+
+ it('requesting delete tracks the right action', () => {
+ expect(eventSpy).toHaveBeenCalledWith(
+ category,
+ REQUEST_DELETE_PACKAGE_VERSION_TRACKING_ACTION,
+ expect.any(Object),
+ );
+ });
+
+ describe('when modal confirms', () => {
+ beforeEach(() => {
+ findPackageListDeleteModal().vm.$emit('ok');
+ });
+
+ it('emits delete when modal confirms', () => {
+ expect(wrapper.emitted('delete')[0][0]).toEqual([packageList[0]]);
+ });
+
+ it('tracks the right action', () => {
+ expect(eventSpy).toHaveBeenCalledWith(
+ category,
+ DELETE_PACKAGE_VERSION_TRACKING_ACTION,
+ expect.any(Object),
+ );
+ });
+ });
+
+ it.each(['ok', 'cancel'])('resets itemToBeDeleted when modal emits %s', async (event) => {
+ await findPackageListDeleteModal().vm.$emit(event);
+
+ expect(findPackageListDeleteModal().props('itemToBeDeleted')).toBeNull();
+ });
+
+ it('canceling delete tracks the right action', () => {
+ findPackageListDeleteModal().vm.$emit('cancel');
+
+ expect(eventSpy).toHaveBeenCalledWith(
+ category,
+ CANCEL_DELETE_PACKAGE_VERSION_TRACKING_ACTION,
+ expect.any(Object),
+ );
+ });
+ });
+
describe('when the user can bulk destroy versions', () => {
let eventSpy;
const { findDeletePackagesModal, findRegistryList } = uiElements;
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js
index 4a27f8011df..3f4358bb3b0 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/pypi_installation_spec.js
@@ -29,10 +29,13 @@ password = <your personal access token>`;
const findInstallationTitle = () => wrapper.findComponent(InstallationTitle);
const findSetupDocsLink = () => wrapper.findByTestId('pypi-docs-link');
- function createComponent() {
+ function createComponent(props = {}) {
wrapper = mountExtended(PypiInstallation, {
propsData: {
- packageEntity,
+ packageEntity: {
+ ...packageEntity,
+ ...props,
+ },
},
stubs: {
GlSprintf,
@@ -44,10 +47,6 @@ password = <your personal access token>`;
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('install command switch', () => {
it('has the installation title component', () => {
expect(findInstallationTitle().exists()).toBe(true);
@@ -86,6 +85,12 @@ password = <your personal access token>`;
});
});
+ it('does not have a link to personal access token docs when package is public', () => {
+ createComponent({ publicPackage: true });
+
+ expect(findAccessTokenLink().exists()).toBe(false);
+ });
+
it('has a link to the docs', () => {
expect(findSetupDocsLink().attributes()).toMatchObject({
href: PYPI_HELP_PATH,
diff --git a/spec/frontend/packages_and_registries/package_registry/components/details/version_row_spec.js b/spec/frontend/packages_and_registries/package_registry/components/details/version_row_spec.js
index 67340822fa5..f7c8e909ff6 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/details/version_row_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/details/version_row_spec.js
@@ -1,4 +1,4 @@
-import { GlFormCheckbox, GlIcon, GlLink, GlSprintf, GlTruncate } from '@gitlab/ui';
+import { GlDropdownItem, GlFormCheckbox, GlIcon, GlLink, GlSprintf, GlTruncate } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
@@ -24,6 +24,7 @@ describe('VersionRow', () => {
const findPackageName = () => wrapper.findComponent(GlTruncate);
const findWarningIcon = () => wrapper.findComponent(GlIcon);
const findBulkDeleteAction = () => wrapper.findComponent(GlFormCheckbox);
+ const findDeleteDropdownItem = () => wrapper.findComponent(GlDropdownItem);
function createComponent({ packageEntity = packageVersion, selected = false } = {}) {
wrapper = shallowMountExtended(VersionRow, {
@@ -36,15 +37,11 @@ describe('VersionRow', () => {
GlTruncate,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('has a link to the version detail', () => {
createComponent();
@@ -112,6 +109,31 @@ describe('VersionRow', () => {
});
});
+ describe('delete button', () => {
+ it('does not exist when package cannot be destroyed', () => {
+ createComponent({ packageEntity: { ...packageVersion, canDestroy: false } });
+
+ expect(findDeleteDropdownItem().exists()).toBe(false);
+ });
+
+ it('exists and has the correct props', () => {
+ createComponent();
+
+ expect(findDeleteDropdownItem().exists()).toBe(true);
+ expect(findDeleteDropdownItem().attributes()).toMatchObject({
+ variant: 'danger',
+ });
+ });
+
+ it('emits the delete event when the delete button is clicked', () => {
+ createComponent();
+
+ findDeleteDropdownItem().vm.$emit('click');
+
+ expect(wrapper.emitted('delete')).toHaveLength(1);
+ });
+ });
+
describe(`when the package is in ${PACKAGE_ERROR_STATUS} status`, () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/packages_and_registries/package_registry/components/functional/delete_packages_spec.js b/spec/frontend/packages_and_registries/package_registry/components/functional/delete_packages_spec.js
index 689b53fa2a4..04546c4cea4 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/functional/delete_packages_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/functional/delete_packages_spec.js
@@ -3,7 +3,7 @@ import VueApollo from 'vue-apollo';
import waitForPromises from 'helpers/wait_for_promises';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
-import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/flash';
+import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/alert';
import DeletePackages from '~/packages_and_registries/package_registry/components/functional/delete_packages.vue';
import destroyPackagesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_packages.mutation.graphql';
@@ -14,7 +14,7 @@ import {
packagesListQuery,
} from '../../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('DeletePackages', () => {
let wrapper;
@@ -67,10 +67,6 @@ describe('DeletePackages', () => {
mutationResolver = jest.fn().mockResolvedValue(packagesDestroyMutation());
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('binds deletePackages method to the default slot', () => {
createComponent();
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 2a78cfb13f9..91417d2fc9f 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 { GlFormCheckbox, GlSprintf, GlTruncate } from '@gitlab/ui';
-import Vue, { nextTick } from 'vue';
+import Vue from 'vue';
import VueRouter from 'vue-router';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
@@ -67,15 +67,11 @@ describe('packages_list_row', () => {
selected,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders', () => {
mountComponent();
expect(wrapper.element).toMatchSnapshot();
@@ -141,7 +137,6 @@ describe('packages_list_row', () => {
findDeleteDropdown().vm.$emit('click');
- await nextTick();
expect(wrapper.emitted('delete')).toHaveLength(1);
});
});
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js
index 610640e0ca3..ae990f3ea00 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_list_spec.js
@@ -73,10 +73,6 @@ describe('packages_list', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when is loading', () => {
beforeEach(() => {
mountComponent({ isLoading: true });
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 a884959ab62..1250ecaf61f 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
@@ -46,10 +46,6 @@ describe('Package Search', () => {
extractFilterAndSorting.mockReturnValue(defaultQueryParamsMock);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('has a registry search component', async () => {
mountComponent();
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js
index b47515e15c3..1296458155a 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_title_spec.js
@@ -20,11 +20,6 @@ describe('PackageTitle', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('title area', () => {
it('exists', () => {
mountComponent();
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/publish_method_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/publish_method_spec.js
index fcbd7cc6a50..e9119b736c2 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/publish_method_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/publish_method_spec.js
@@ -19,10 +19,6 @@ describe('publish_method', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders', () => {
mountComponent();
expect(wrapper.element).toMatchSnapshot();
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js
index 8f3c8667c47..c98f5f32344 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/tokens/package_type_token_spec.js
@@ -19,11 +19,6 @@ describe('packages_filter', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('binds all of his attrs to filtered search token', () => {
mountComponent({ attrs: { foo: 'bar' } });
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 d897be1f344..19c098e1f82 100644
--- a/spec/frontend/packages_and_registries/package_registry/mock_data.js
+++ b/spec/frontend/packages_and_registries/package_registry/mock_data.js
@@ -147,6 +147,7 @@ export const packageData = (extend) => ({
conanUrl: 'http://gdk.test:3000/api/v4/projects/1/packages/conan',
pypiUrl:
'http://__token__:<your_personal_token>@gdk.test:3000/api/v4/projects/1/packages/pypi/simple',
+ publicPackage: false,
pypiSetupUrl: 'http://gdk.test:3000/api/v4/projects/1/packages/pypi',
...extend,
});
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 b494965a3cb..49f69a46395 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
@@ -6,7 +6,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import AdditionalMetadata from '~/packages_and_registries/package_registry/components/details/additional_metadata.vue';
import PackagesApp from '~/packages_and_registries/package_registry/pages/details.vue';
@@ -45,7 +45,7 @@ import {
pagination,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
useMockLocationHelper();
describe('PackagesApp', () => {
@@ -131,10 +131,6 @@ describe('PackagesApp', () => {
const findDeletePackageModal = () => wrapper.findAllComponents(DeletePackages).at(1);
const findDeletePackages = () => wrapper.findComponent(DeletePackages);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders an empty state component', async () => {
createComponent({ resolver: jest.fn().mockResolvedValue(emptyPackageDetailsQuery) });
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 a2ec527ce12..60bb055b1db 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
@@ -4,14 +4,13 @@ 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 { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
import ListPage from '~/packages_and_registries/package_registry/pages/list.vue';
import PackageTitle from '~/packages_and_registries/package_registry/components/list/package_title.vue';
import PackageSearch from '~/packages_and_registries/package_registry/components/list/package_search.vue';
import OriginalPackageList from '~/packages_and_registries/package_registry/components/list/packages_list.vue';
import DeletePackages from '~/packages_and_registries/package_registry/components/functional/delete_packages.vue';
import {
- PROJECT_RESOURCE_TYPE,
- GROUP_RESOURCE_TYPE,
GRAPHQL_PAGE_SIZE,
EMPTY_LIST_HELP_URL,
PACKAGE_HELP_URL,
@@ -21,7 +20,7 @@ import getPackagesQuery from '~/packages_and_registries/package_registry/graphql
import destroyPackagesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_packages.mutation.graphql';
import { packagesListQuery, packageData, pagination } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('PackagesListApp', () => {
let wrapper;
@@ -78,10 +77,6 @@ describe('PackagesListApp', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const waitForFirstRequest = async () => {
// emit a search update so the query is executed
findSearch().vm.$emit('update', { sort: 'NAME_DESC', filters: [] });
@@ -171,14 +166,14 @@ describe('PackagesListApp', () => {
});
describe.each`
- type | sortType
- ${PROJECT_RESOURCE_TYPE} | ${'sort'}
- ${GROUP_RESOURCE_TYPE} | ${'groupSort'}
+ type | sortType
+ ${WORKSPACE_PROJECT} | ${'sort'}
+ ${WORKSPACE_GROUP} | ${'groupSort'}
`('$type query', ({ type, sortType }) => {
let provide;
let resolver;
- const isGroupPage = type === GROUP_RESOURCE_TYPE;
+ const isGroupPage = type === WORKSPACE_GROUP;
beforeEach(() => {
provide = { ...defaultProvide, isGroupPage };
@@ -198,9 +193,13 @@ describe('PackagesListApp', () => {
});
});
- describe('empty state', () => {
+ describe.each`
+ description | resolverResponse
+ ${'empty response'} | ${packagesListQuery({ extend: { nodes: [] } })}
+ ${'error response'} | ${{ data: { group: null } }}
+ `(`$description renders empty state`, ({ resolverResponse }) => {
beforeEach(() => {
- const resolver = jest.fn().mockResolvedValue(packagesListQuery({ extend: { nodes: [] } }));
+ const resolver = jest.fn().mockResolvedValue(resolverResponse);
mountComponent({ resolver });
return waitForFirstRequest();
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 796d89231f4..6dd4b9f2d20 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
@@ -28,7 +28,7 @@ import {
dependencyProxyUpdateTllPolicyMutationMock,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/packages_and_registries/settings/group/graphql/utils/optimistic_responses');
describe('DependencyProxySettings', () => {
@@ -82,10 +82,6 @@ describe('DependencyProxySettings', () => {
.mockResolvedValue(dependencyProxyUpdateTllPolicyMutationMock());
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findSettingsBlock = () => wrapper.findComponent(SettingsBlock);
const findEnableProxyToggle = () => wrapper.findByTestId('dependency-proxy-setting-toggle');
const findEnableTtlPoliciesToggle = () =>
diff --git a/spec/frontend/packages_and_registries/settings/group/components/exceptions_input_spec.js b/spec/frontend/packages_and_registries/settings/group/components/exceptions_input_spec.js
index 86f14961690..461200e6983 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/exceptions_input_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/exceptions_input_spec.js
@@ -23,10 +23,6 @@ describe('Exceptions Input', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findInputGroup = () => wrapper.findComponent(GlFormGroup);
const findInput = () => wrapper.findComponent(GlFormInput);
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 7edc321867c..3ce8e91d43d 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
@@ -19,7 +19,7 @@ import {
dependencyProxyImageTtlPolicy,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Group Settings App', () => {
let wrapper;
@@ -55,10 +55,6 @@ describe('Group Settings App', () => {
show = jest.fn();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findAlert = () => wrapper.findComponent(GlAlert);
const findPackageSettings = () => wrapper.findComponent(PackagesSettings);
const findPackageForwardingSettings = () => wrapper.findComponent(PackagesForwardingSettings);
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 807f332f4d3..22e42f8c0ab 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
@@ -23,7 +23,7 @@ import {
groupPackageSettingsMutationErrorMock,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/packages_and_registries/settings/group/graphql/utils/optimistic_responses');
describe('Packages Settings', () => {
@@ -56,10 +56,6 @@ describe('Packages Settings', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findSettingsBlock = () => wrapper.findComponent(SettingsBlock);
const findDescription = () => wrapper.findByTestId('description');
const findMavenSettings = () => wrapper.findByTestId('maven-settings');
diff --git a/spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js b/spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js
index a0b257a9496..d57077b31c8 100644
--- a/spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js
+++ b/spec/frontend/packages_and_registries/settings/group/components/packages_forwarding_settings_spec.js
@@ -25,7 +25,7 @@ import {
mavenProps,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/packages_and_registries/settings/group/graphql/utils/optimistic_responses');
describe('Packages Forwarding Settings', () => {
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js
index 2bb99fb8e8f..49e8601da88 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/cleanup_image_tags_spec.js
@@ -61,10 +61,6 @@ describe('Cleanup image tags project settings', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('isEdited status', () => {
it.each`
description | apiResponse | workingCopy | result
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js
index cbb5aa52694..57b48407174 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_form_spec.js
@@ -124,10 +124,6 @@ describe('Container Expiration Policy Settings Form', () => {
jest.spyOn(Tracking, 'event');
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe.each`
model | finder | fieldName | type | defaultValue
${'enabled'} | ${findEnableToggle} | ${'Enable'} | ${'toggle'} | ${false}
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js
index 43484d26d76..19f25d0aef7 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/container_expiration_policy_spec.js
@@ -63,10 +63,6 @@ describe('Container expiration policy project settings', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the setting form', async () => {
mountComponentWithApollo({
resolver: jest.fn().mockResolvedValue(expirationPolicyPayload()),
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js
index ae41fdf65e0..058fe427106 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_dropdown_spec.js
@@ -32,11 +32,6 @@ describe('ExpirationDropdown', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('structure', () => {
it('has a form-select component', () => {
mountComponent();
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js
index 1cea0704154..be12d108d1e 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_input_spec.js
@@ -38,11 +38,6 @@ describe('ExpirationInput', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('structure', () => {
it('has a label', () => {
mountComponent();
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js
index 653f2a8b40e..f950a9d5add 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_run_text_spec.js
@@ -23,11 +23,6 @@ describe('ExpirationToggle', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('structure', () => {
it('has an input component', () => {
mountComponent();
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js
index 55a66cebd83..ec7b89aa927 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/expiration_toggle_spec.js
@@ -23,11 +23,6 @@ describe('ExpirationToggle', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('structure', () => {
it('has a toggle component', () => {
mountComponent();
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js
index 0fbbf4ae58f..b9c0c38bf9e 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_form_spec.js
@@ -115,7 +115,6 @@ describe('Packages Cleanup Policy Settings Form', () => {
});
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
diff --git a/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_spec.js b/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_spec.js
index 6dfeeca6862..94277d34f30 100644
--- a/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_spec.js
+++ b/spec/frontend/packages_and_registries/settings/project/settings/components/packages_cleanup_policy_spec.js
@@ -47,7 +47,6 @@ describe('Packages cleanup policy project settings', () => {
};
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
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 07d13839c61..54655acdf2a 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
@@ -20,11 +20,6 @@ describe('Registry Settings app', () => {
const findPackagesCleanupPolicy = () => wrapper.findComponent(PackagesCleanupPolicy);
const findAlert = () => wrapper.findComponent(GlAlert);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const defaultProvide = {
showContainerRegistrySettings: true,
showPackageRegistrySettings: true,
diff --git a/spec/frontend/packages_and_registries/shared/components/cli_commands_spec.js b/spec/frontend/packages_and_registries/shared/components/cli_commands_spec.js
index 18084766db9..41482e6e681 100644
--- a/spec/frontend/packages_and_registries/shared/components/cli_commands_spec.js
+++ b/spec/frontend/packages_and_registries/shared/components/cli_commands_spec.js
@@ -38,11 +38,6 @@ describe('cli_commands', () => {
mountComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('shows the correct text on the button', () => {
expect(findDropdownButton().text()).toContain(QUICK_START);
});
diff --git a/spec/frontend/packages_and_registries/shared/components/delete_package_modal_spec.js b/spec/frontend/packages_and_registries/shared/components/delete_package_modal_spec.js
index 357dab593e8..ba5ba8f9884 100644
--- a/spec/frontend/packages_and_registries/shared/components/delete_package_modal_spec.js
+++ b/spec/frontend/packages_and_registries/shared/components/delete_package_modal_spec.js
@@ -19,11 +19,6 @@ describe('DeletePackageModal', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when itemToBeDeleted prop is defined', () => {
beforeEach(() => {
mountComponent();
diff --git a/spec/frontend/packages_and_registries/shared/components/package_path_spec.js b/spec/frontend/packages_and_registries/shared/components/package_path_spec.js
index 93425d4f399..2490e9a1f6a 100644
--- a/spec/frontend/packages_and_registries/shared/components/package_path_spec.js
+++ b/spec/frontend/packages_and_registries/shared/components/package_path_spec.js
@@ -9,7 +9,7 @@ describe('PackagePath', () => {
wrapper = shallowMount(PackagePath, {
propsData,
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
@@ -24,11 +24,6 @@ describe('PackagePath', () => {
const findItem = (name) => wrapper.find(`[data-testid="${name}"]`);
const findTooltip = (w) => getBinding(w.element, 'gl-tooltip');
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe.each`
path | rootUrl | shouldExist | shouldNotExist
${'foo/bar'} | ${'/foo/bar'} | ${[]} | ${[ROOT_CHEVRON, ELLIPSIS_ICON, ELLIPSIS_CHEVRON, LEAF_LINK]}
diff --git a/spec/frontend/packages_and_registries/shared/components/packages_list_loader_spec.js b/spec/frontend/packages_and_registries/shared/components/packages_list_loader_spec.js
index 0005162e0bb..e43a9f57255 100644
--- a/spec/frontend/packages_and_registries/shared/components/packages_list_loader_spec.js
+++ b/spec/frontend/packages_and_registries/shared/components/packages_list_loader_spec.js
@@ -17,11 +17,6 @@ describe('PackagesListLoader', () => {
beforeEach(createComponent);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('desktop loader', () => {
it('produces the right loader', () => {
expect(findDesktopShapes().findAll('rect[width="1000"]')).toHaveLength(20);
diff --git a/spec/frontend/packages_and_registries/shared/components/persisted_search_spec.js b/spec/frontend/packages_and_registries/shared/components/persisted_search_spec.js
index db9f96bff39..1484377a475 100644
--- a/spec/frontend/packages_and_registries/shared/components/persisted_search_spec.js
+++ b/spec/frontend/packages_and_registries/shared/components/persisted_search_spec.js
@@ -43,10 +43,6 @@ describe('Persisted Search', () => {
extractFilterAndSorting.mockReturnValue(defaultQueryParamsMock);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('has a registry search component', async () => {
mountComponent();
diff --git a/spec/frontend/packages_and_registries/shared/components/publish_method_spec.js b/spec/frontend/packages_and_registries/shared/components/publish_method_spec.js
index fa8f8f7641a..167599a54ea 100644
--- a/spec/frontend/packages_and_registries/shared/components/publish_method_spec.js
+++ b/spec/frontend/packages_and_registries/shared/components/publish_method_spec.js
@@ -20,11 +20,6 @@ describe('publish_method', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders', () => {
mountComponent(packageWithPipeline);
expect(wrapper.element).toMatchSnapshot();
diff --git a/spec/frontend/packages_and_registries/shared/components/registry_breadcrumb_spec.js b/spec/frontend/packages_and_registries/shared/components/registry_breadcrumb_spec.js
index 15db454ac68..c1f1a25d53b 100644
--- a/spec/frontend/packages_and_registries/shared/components/registry_breadcrumb_spec.js
+++ b/spec/frontend/packages_and_registries/shared/components/registry_breadcrumb_spec.js
@@ -31,10 +31,6 @@ describe('Registry Breadcrumb', () => {
nameGenerator.mockClear();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when is rootRoute', () => {
beforeEach(() => {
mountComponent(routes[0]);
diff --git a/spec/frontend/packages_and_registries/shared/components/registry_list_spec.js b/spec/frontend/packages_and_registries/shared/components/registry_list_spec.js
index 2e2d5e26d33..a4e0d267023 100644
--- a/spec/frontend/packages_and_registries/shared/components/registry_list_spec.js
+++ b/spec/frontend/packages_and_registries/shared/components/registry_list_spec.js
@@ -36,10 +36,6 @@ describe('Registry List', () => {
const findScopedSlotFirstValue = (index) => findScopedSlots().at(index).find('span');
const findScopedSlotIsSelectedValue = (index) => findScopedSlots().at(index).find('p');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('header', () => {
it('renders the title passed in the prop', () => {
mountComponent();
@@ -111,10 +107,21 @@ describe('Registry List', () => {
expect(findDeleteSelected().text()).toBe(component.i18n.deleteSelected);
});
- it('is hidden when hiddenDelete is true', () => {
- mountComponent({ propsData: { ...defaultPropsData, hiddenDelete: true } });
+ describe('when hiddenDelete is true', () => {
+ beforeEach(() => {
+ mountComponent({ propsData: { ...defaultPropsData, hiddenDelete: true } });
+ });
- expect(findDeleteSelected().exists()).toBe(false);
+ it('is hidden', () => {
+ expect(findDeleteSelected().exists()).toBe(false);
+ });
+
+ it('populates the first slot prop correctly', async () => {
+ expect(findScopedSlots().at(0).exists()).toBe(true);
+
+ // it's the first slot
+ expect(findScopedSlotFirstValue(0).text()).toBe('false');
+ });
});
it('is disabled when isLoading is true', () => {
diff --git a/spec/frontend/packages_and_registries/shared/components/settings_block_spec.js b/spec/frontend/packages_and_registries/shared/components/settings_block_spec.js
index a4c1b989dac..664a821c275 100644
--- a/spec/frontend/packages_and_registries/shared/components/settings_block_spec.js
+++ b/spec/frontend/packages_and_registries/shared/components/settings_block_spec.js
@@ -15,10 +15,6 @@ describe('SettingsBlock', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findDefaultSlot = () => wrapper.findByTestId('default-slot');
const findTitleSlot = () => wrapper.findByTestId('title-slot');
const findDescriptionSlot = () => wrapper.findByTestId('description-slot');
diff --git a/spec/frontend/pages/admin/jobs/index/components/cancel_jobs_spec.js b/spec/frontend/pages/admin/jobs/index/components/cancel_jobs_spec.js
index ec6369e7119..de6d44eabdc 100644
--- a/spec/frontend/pages/admin/jobs/index/components/cancel_jobs_spec.js
+++ b/spec/frontend/pages/admin/jobs/index/components/cancel_jobs_spec.js
@@ -19,8 +19,8 @@ describe('CancelJobs component', () => {
const createComponent = (props = {}) => {
wrapper = shallowMountExtended(CancelJobs, {
directives: {
- GlModal: createMockDirective(),
- GlTooltip: createMockDirective(),
+ GlModal: createMockDirective('gl-modal'),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
propsData: {
url: `${TEST_HOST}/cancel_jobs_modal.vue/cancelAll`,
diff --git a/spec/frontend/pages/groups/new/components/app_spec.js b/spec/frontend/pages/groups/new/components/app_spec.js
index ab483316086..7ccded9b0f1 100644
--- a/spec/frontend/pages/groups/new/components/app_spec.js
+++ b/spec/frontend/pages/groups/new/components/app_spec.js
@@ -6,7 +6,7 @@ describe('App component', () => {
let wrapper;
const createComponent = (propsData = {}) => {
- wrapper = shallowMount(App, { propsData });
+ wrapper = shallowMount(App, { propsData: { groupsUrl: '/dashboard/groups', ...propsData } });
};
const findNewNamespacePage = () => wrapper.findComponent(NewNamespacePage);
@@ -16,24 +16,31 @@ describe('App component', () => {
.props('panels')
.find((panel) => panel.name === 'create-group-pane');
- afterEach(() => {
- wrapper.destroy();
- });
-
it('creates correct component for group creation', () => {
createComponent();
- expect(findNewNamespacePage().props('initialBreadcrumb')).toBe('New group');
+ expect(findNewNamespacePage().props('initialBreadcrumbs')).toEqual([
+ { href: '/dashboard/groups', text: 'Groups' },
+ { href: '#', text: 'New group' },
+ ]);
expect(findCreateGroupPanel().title).toBe('Create group');
});
it('creates correct component for subgroup creation', () => {
- const props = { parentGroupName: 'parent', importExistingGroupPath: '/path' };
+ const detailProps = {
+ parentGroupName: 'parent',
+ importExistingGroupPath: '/path',
+ };
+
+ const props = { ...detailProps, parentGroupUrl: '/parent' };
createComponent(props);
- expect(findNewNamespacePage().props('initialBreadcrumb')).toBe('parent');
+ expect(findNewNamespacePage().props('initialBreadcrumbs')).toEqual([
+ { href: '/parent', text: 'parent' },
+ { href: '#', text: 'New subgroup' },
+ ]);
expect(findCreateGroupPanel().title).toBe('Create subgroup');
- expect(findCreateGroupPanel().detailProps).toEqual(props);
+ expect(findCreateGroupPanel().detailProps).toEqual(detailProps);
});
});
diff --git a/spec/frontend/pages/groups/new/components/create_group_description_details_spec.js b/spec/frontend/pages/groups/new/components/create_group_description_details_spec.js
index 56a1fd03f71..35015d84085 100644
--- a/spec/frontend/pages/groups/new/components/create_group_description_details_spec.js
+++ b/spec/frontend/pages/groups/new/components/create_group_description_details_spec.js
@@ -15,10 +15,6 @@ describe('CreateGroupDescriptionDetails component', () => {
const findLinkHref = (at) => wrapper.findAllComponents(GlLink).at(at);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('creates correct component for group creation', () => {
createComponent();
diff --git a/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js b/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js
index da3954b4918..477511cde64 100644
--- a/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js
+++ b/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js
@@ -68,15 +68,10 @@ describe('BulkImportsHistoryApp', () => {
const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
- const originalApiVersion = gon.api_version;
- beforeAll(() => {
+ beforeEach(() => {
gon.api_version = 'v4';
});
- afterAll(() => {
- gon.api_version = originalApiVersion;
- });
-
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet(API_URL).reply(HTTP_STATUS_OK, DUMMY_RESPONSE, DEFAULT_HEADERS);
@@ -84,7 +79,6 @@ describe('BulkImportsHistoryApp', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
describe('general behavior', () => {
diff --git a/spec/frontend/pages/import/history/components/import_error_details_spec.js b/spec/frontend/pages/import/history/components/import_error_details_spec.js
index 628ee8d7999..239826c1458 100644
--- a/spec/frontend/pages/import/history/components/import_error_details_spec.js
+++ b/spec/frontend/pages/import/history/components/import_error_details_spec.js
@@ -21,22 +21,13 @@ describe('ImportErrorDetails', () => {
});
}
- const originalApiVersion = gon.api_version;
- beforeAll(() => {
- gon.api_version = 'v4';
- });
-
- afterAll(() => {
- gon.api_version = originalApiVersion;
- });
-
beforeEach(() => {
+ gon.api_version = 'v4';
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
describe('general behavior', () => {
diff --git a/spec/frontend/pages/import/history/components/import_history_app_spec.js b/spec/frontend/pages/import/history/components/import_history_app_spec.js
index 7d79583be19..43cbac25fe8 100644
--- a/spec/frontend/pages/import/history/components/import_history_app_spec.js
+++ b/spec/frontend/pages/import/history/components/import_history_app_spec.js
@@ -59,23 +59,14 @@ describe('ImportHistoryApp', () => {
});
}
- const originalApiVersion = gon.api_version;
- beforeAll(() => {
+ beforeEach(() => {
gon.api_version = 'v4';
gon.features = { fullPathProjectSearch: true };
- });
-
- afterAll(() => {
- gon.api_version = originalApiVersion;
- });
-
- beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
describe('general behavior', () => {
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 c30b996437d..18a0098a715 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
@@ -25,8 +25,7 @@ describe('Password prompt modal', () => {
const findField = () => wrapper.findByTestId('password-prompt-field');
const findModal = () => wrapper.findComponent(GlModal);
const findConfirmBtn = () => findModal().props('actionPrimary');
- const findConfirmBtnDisabledState = () =>
- findModal().props('actionPrimary').attributes[2].disabled;
+ const findConfirmBtnDisabledState = () => findModal().props('actionPrimary').attributes.disabled;
const findCancelBtn = () => findModal().props('actionCancel');
@@ -41,10 +40,6 @@ describe('Password prompt modal', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the password field', () => {
expect(findField().exists()).toBe(true);
});
diff --git a/spec/frontend/pages/projects/forks/new/components/app_spec.js b/spec/frontend/pages/projects/forks/new/components/app_spec.js
index 0342b94a44d..e9a94878867 100644
--- a/spec/frontend/pages/projects/forks/new/components/app_spec.js
+++ b/spec/frontend/pages/projects/forks/new/components/app_spec.js
@@ -22,10 +22,6 @@ describe('App component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays the correct svg illustration', () => {
expect(wrapper.find('img').attributes('src')).toBe('illustrations/project-create-new-sm.svg');
});
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 f0593a854b2..9dce6fde6f6 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
@@ -6,7 +6,7 @@ import AxiosMockAdapter from 'axios-mock-adapter';
import { kebabCase } from 'lodash';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import * as urlUtility from '~/lib/utils/url_utility';
import ForkForm from '~/pages/projects/forks/new/components/fork_form.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -14,7 +14,7 @@ import searchQuery from '~/pages/projects/forks/new/queries/search_forkable_name
import ProjectNamespace from '~/pages/projects/forks/new/components/project_namespace.vue';
import { START_RULE, CONTAINS_RULE } from '~/projects/project_name_rules';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
describe('ForkForm component', () => {
@@ -111,7 +111,6 @@ describe('ForkForm component', () => {
});
afterEach(() => {
- wrapper.destroy();
axiosMock.restore();
});
@@ -553,7 +552,7 @@ describe('ForkForm component', () => {
expect(urlUtility.redirectTo).toHaveBeenCalledWith(webUrl);
});
- it('display flash when POST is unsuccessful', async () => {
+ it('displays an alert when POST is unsuccessful', async () => {
const dummyError = 'Fork project failed';
jest.spyOn(axios, 'post').mockRejectedValue(dummyError);
diff --git a/spec/frontend/pages/projects/forks/new/components/project_namespace_spec.js b/spec/frontend/pages/projects/forks/new/components/project_namespace_spec.js
index 82f451ed6ef..af578b69a81 100644
--- a/spec/frontend/pages/projects/forks/new/components/project_namespace_spec.js
+++ b/spec/frontend/pages/projects/forks/new/components/project_namespace_spec.js
@@ -3,15 +3,14 @@ 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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import searchQuery from '~/pages/projects/forks/new/queries/search_forkable_namespaces.query.graphql';
import ProjectNamespace from '~/pages/projects/forks/new/components/project_namespace.vue';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('ProjectNamespace component', () => {
let wrapper;
- let originalGon;
const data = {
project: {
@@ -85,14 +84,8 @@ describe('ProjectNamespace component', () => {
findListBox().vm.$emit('shown');
};
- beforeAll(() => {
- originalGon = window.gon;
- window.gon = { gitlab_url: gitlabUrl };
- });
-
- afterAll(() => {
- window.gon = originalGon;
- wrapper.destroy();
+ beforeEach(() => {
+ gon.gitlab_url = gitlabUrl;
});
describe('Initial state', () => {
@@ -152,7 +145,7 @@ describe('ProjectNamespace component', () => {
await nextTick();
});
- it('creates a flash message and captures the error', () => {
+ it('creates an alert message and captures the error', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'Something went wrong while loading data. Please refresh the page to try again.',
captureError: true,
diff --git a/spec/frontend/pages/projects/graphs/code_coverage_spec.js b/spec/frontend/pages/projects/graphs/code_coverage_spec.js
index 5356953060a..882730d90ae 100644
--- a/spec/frontend/pages/projects/graphs/code_coverage_spec.js
+++ b/spec/frontend/pages/projects/graphs/code_coverage_spec.js
@@ -1,4 +1,4 @@
-import { GlAlert, GlListbox, GlListboxItem } from '@gitlab/ui';
+import { GlAlert, GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
import { GlAreaChart } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
@@ -22,7 +22,7 @@ describe('Code Coverage', () => {
const findAlert = () => wrapper.findComponent(GlAlert);
const findAreaChart = () => wrapper.findComponent(GlAreaChart);
- const findListBox = () => wrapper.findComponent(GlListbox);
+ const findListBox = () => wrapper.findComponent(GlCollapsibleListbox);
const findListBoxItems = () => wrapper.findAllComponents(GlListboxItem);
const findFirstListBoxItem = () => findListBoxItems().at(0);
const findSecondListBoxItem = () => findListBoxItems().at(1);
@@ -37,15 +37,10 @@ describe('Code Coverage', () => {
graphRef,
graphCsvPath,
},
- stubs: { GlListbox },
+ stubs: { GlCollapsibleListbox },
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when fetching data is successful', () => {
beforeEach(() => {
mockAxios = new MockAdapter(axios);
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 2d3b9afa8f6..07d05293a3c 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
@@ -37,10 +37,6 @@ describe('Interval Pattern Input Component', () => {
const selectCustomRadio = () => findCustomRadio().setChecked(true);
const createWrapper = (props = {}, data = {}) => {
- if (wrapper) {
- throw new Error('A wrapper already exists');
- }
-
wrapper = mount(IntervalPatternInput, {
propsData: { ...props },
data() {
@@ -64,8 +60,6 @@ describe('Interval Pattern Input Component', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
window.gl = oldWindowGl;
});
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 a633332ab65..e20c2fa47a7 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
@@ -31,10 +31,6 @@ describe('Pipeline Schedule Callout', () => {
await nextTick();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('does not render the callout', () => {
expect(findInnerContentOfCallout().exists()).toBe(false);
});
@@ -46,10 +42,6 @@ describe('Pipeline Schedule Callout', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the callout container', () => {
expect(findInnerContentOfCallout().exists()).toBe(true);
});
diff --git a/spec/frontend/pages/projects/shared/permissions/components/project_feature_settings_spec.js b/spec/frontend/pages/projects/shared/permissions/components/project_feature_settings_spec.js
index 5771e1b88e8..03c65ab4c9c 100644
--- a/spec/frontend/pages/projects/shared/permissions/components/project_feature_settings_spec.js
+++ b/spec/frontend/pages/projects/shared/permissions/components/project_feature_settings_spec.js
@@ -31,11 +31,6 @@ describe('Project Feature Settings', () => {
},
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('Hidden name input', () => {
it('should set the hidden name input if the name exists', () => {
wrapper = mountComponent();
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 6230809a6aa..91d3057aec5 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
@@ -15,10 +15,6 @@ describe('Project Setting Row', () => {
wrapper = mountComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should show the label if it is set', async () => {
wrapper.setProps({ label: 'Test label' });
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 ff20b72c72c..0812be9745e 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
@@ -140,11 +140,6 @@ describe('Settings Panel', () => {
const findMonitorVisibilityInput = () =>
findMonitorSettings().findComponent(ProjectFeatureSetting);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('Project Visibility', () => {
it('should set the project visibility help path', () => {
wrapper = mountComponent();
diff --git a/spec/frontend/pages/shared/wikis/components/wiki_alert_spec.js b/spec/frontend/pages/shared/wikis/components/wiki_alert_spec.js
index 6a18473b1a7..1858a56b0e1 100644
--- a/spec/frontend/pages/shared/wikis/components/wiki_alert_spec.js
+++ b/spec/frontend/pages/shared/wikis/components/wiki_alert_spec.js
@@ -15,11 +15,6 @@ describe('WikiAlert', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findGlAlert = () => wrapper.findComponent(GlAlert);
const findGlLink = () => wrapper.findComponent(GlLink);
const findGlSprintf = () => wrapper.findComponent(GlSprintf);
diff --git a/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js b/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js
index c8e9a31b526..8e26453b564 100644
--- a/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js
+++ b/spec/frontend/pages/shared/wikis/components/wiki_content_spec.js
@@ -31,11 +31,6 @@ describe('pages/shared/wikis/components/wiki_content', () => {
mock = new MockAdapter(axios);
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findGlAlert = () => wrapper.findComponent(GlAlert);
const findGlSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findContent = () => wrapper.find('[data-testid="wiki-page-content"]');
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 ffcfd1d9f78..1be4a974f7a 100644
--- a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js
+++ b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js
@@ -103,8 +103,6 @@ describe('WikiForm', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
- wrapper = null;
});
it('displays markdown editor', () => {
@@ -116,7 +114,6 @@ describe('WikiForm', () => {
expect.objectContaining({
value: pageInfoPersisted.content,
renderMarkdownPath: pageInfoPersisted.markdownPreviewPath,
- markdownDocsPath: pageInfoPersisted.markdownHelpPath,
uploadsPath: pageInfoPersisted.uploadsPath,
autofocus: pageInfoPersisted.persisted,
}),
@@ -126,6 +123,10 @@ describe('WikiForm', () => {
id: 'wiki_content',
name: 'wiki[content]',
});
+
+ expect(markdownEditor.vm.$attrs['markdown-docs-path']).toEqual(
+ pageInfoPersisted.markdownHelpPath,
+ );
});
it.each`
@@ -172,7 +173,7 @@ describe('WikiForm', () => {
nextTick();
- expect(findMarkdownEditor().props('enablePreview')).toBe(enabled);
+ expect(findMarkdownEditor().vm.$attrs['enable-preview']).toBe(enabled);
});
it.each`
diff --git a/spec/frontend/pdf/index_spec.js b/spec/frontend/pdf/index_spec.js
index 98946412264..23477c73ba0 100644
--- a/spec/frontend/pdf/index_spec.js
+++ b/spec/frontend/pdf/index_spec.js
@@ -7,10 +7,6 @@ describe('PDFLab component', () => {
const mountComponent = ({ pdf }) => shallowMount(PDFLab, { propsData: { pdf } });
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('without PDF data', () => {
beforeEach(() => {
wrapper = mountComponent({ pdf: '' });
diff --git a/spec/frontend/pdf/page_spec.js b/spec/frontend/pdf/page_spec.js
index 4cf83a3252d..1d5c5cd98c4 100644
--- a/spec/frontend/pdf/page_spec.js
+++ b/spec/frontend/pdf/page_spec.js
@@ -9,10 +9,6 @@ jest.mock('pdfjs-dist/webpack', () => {
describe('Page component', () => {
let wrapper;
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the page when mounting', async () => {
const testPage = {
render: jest.fn().mockReturnValue({ promise: Promise.resolve() }),
diff --git a/spec/frontend/performance_bar/components/add_request_spec.js b/spec/frontend/performance_bar/components/add_request_spec.js
index 5460feb66fe..de9cc1e8008 100644
--- a/spec/frontend/performance_bar/components/add_request_spec.js
+++ b/spec/frontend/performance_bar/components/add_request_spec.js
@@ -13,10 +13,6 @@ describe('add request form', () => {
wrapper = mount(AddRequest);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('hides the input on load', () => {
expect(findGlFormInput().exists()).toBe(false);
});
diff --git a/spec/frontend/performance_bar/components/detailed_metric_spec.js b/spec/frontend/performance_bar/components/detailed_metric_spec.js
index 5ab2c9abe5d..4194639fffe 100644
--- a/spec/frontend/performance_bar/components/detailed_metric_spec.js
+++ b/spec/frontend/performance_bar/components/detailed_metric_spec.js
@@ -38,10 +38,6 @@ describe('detailedMetric', () => {
const findAllSummaryItems = () =>
wrapper.findAllByTestId('performance-bar-summary-item').wrappers.map((w) => w.text());
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when the current request has no details', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/performance_bar/components/request_warning_spec.js b/spec/frontend/performance_bar/components/request_warning_spec.js
index 9dd8ea9f933..7b6d8ff695d 100644
--- a/spec/frontend/performance_bar/components/request_warning_spec.js
+++ b/spec/frontend/performance_bar/components/request_warning_spec.js
@@ -5,10 +5,6 @@ describe('request warning', () => {
let wrapper;
const htmlId = 'request-123';
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when the request has warnings', () => {
beforeEach(() => {
wrapper = shallowMount(RequestWarning, {
diff --git a/spec/frontend/persistent_user_callout_spec.js b/spec/frontend/persistent_user_callout_spec.js
index 6519989661f..376575a8acb 100644
--- a/spec/frontend/persistent_user_callout_spec.js
+++ b/spec/frontend/persistent_user_callout_spec.js
@@ -1,12 +1,12 @@
import MockAdapter from 'axios-mock-adapter';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import PersistentUserCallout from '~/persistent_user_callout';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('PersistentUserCallout', () => {
const dismissEndpoint = '/dismiss';
diff --git a/spec/frontend/pipeline_wizard/components/commit_spec.js b/spec/frontend/pipeline_wizard/components/commit_spec.js
index fa30b9c2b97..8f44a6c085b 100644
--- a/spec/frontend/pipeline_wizard/components/commit_spec.js
+++ b/spec/frontend/pipeline_wizard/components/commit_spec.js
@@ -74,10 +74,6 @@ describe('Pipeline Wizard - Commit Page', () => {
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);
@@ -121,10 +117,6 @@ describe('Pipeline Wizard - Commit Page', () => {
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', () => {
@@ -151,7 +143,6 @@ describe('Pipeline Wizard - Commit Page', () => {
});
afterEach(() => {
- wrapper.destroy();
jest.clearAllMocks();
});
});
@@ -178,7 +169,6 @@ describe('Pipeline Wizard - Commit Page', () => {
});
afterEach(() => {
- wrapper.destroy();
jest.clearAllMocks();
});
});
@@ -246,10 +236,6 @@ describe('Pipeline Wizard - Commit Page', () => {
await waitForPromises();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('sets up without error', async () => {
expect(consoleSpy).not.toHaveBeenCalled();
});
diff --git a/spec/frontend/pipeline_wizard/components/editor_spec.js b/spec/frontend/pipeline_wizard/components/editor_spec.js
index dd0a609043a..6d7d4363189 100644
--- a/spec/frontend/pipeline_wizard/components/editor_spec.js
+++ b/spec/frontend/pipeline_wizard/components/editor_spec.js
@@ -9,10 +9,6 @@ describe('Pages Yaml Editor wrapper', () => {
propsData: { doc: new Document({ foo: 'bar' }), filename: 'foo.yml' },
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('mount hook', () => {
beforeEach(() => {
wrapper = mount(YamlEditor, defaultOptions);
diff --git a/spec/frontend/pipeline_wizard/components/input_wrapper_spec.js b/spec/frontend/pipeline_wizard/components/input_wrapper_spec.js
index f288264a11e..7f521e2523e 100644
--- a/spec/frontend/pipeline_wizard/components/input_wrapper_spec.js
+++ b/spec/frontend/pipeline_wizard/components/input_wrapper_spec.js
@@ -33,10 +33,6 @@ describe('Pipeline Wizard -- Input Wrapper', () => {
inputChild = wrapper.findComponent(TextWidget);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('will replace its value in compiled', async () => {
await inputChild.vm.$emit('input', inputValue);
const expected = new Document({
@@ -54,10 +50,6 @@ describe('Pipeline Wizard -- Input Wrapper', () => {
});
describe('Target Path Discovery', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
scenario | template | target | expected
${'simple nested object'} | ${{ foo: { bar: { baz: '$BOO' } } }} | ${'$BOO'} | ${['foo', 'bar', 'baz']}
diff --git a/spec/frontend/pipeline_wizard/components/step_nav_spec.js b/spec/frontend/pipeline_wizard/components/step_nav_spec.js
index c6eac1386fa..8e2f0ab0281 100644
--- a/spec/frontend/pipeline_wizard/components/step_nav_spec.js
+++ b/spec/frontend/pipeline_wizard/components/step_nav_spec.js
@@ -19,10 +19,6 @@ describe('Pipeline Wizard - Step Navigation Component', () => {
nextButton = wrapper.findByTestId('next-button');
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
scenario | showBackButton | showNextButton
${'does not show prev button'} | ${false} | ${false}
diff --git a/spec/frontend/pipeline_wizard/components/widgets/checklist_spec.js b/spec/frontend/pipeline_wizard/components/widgets/checklist_spec.js
index b8e194015b0..52e5d49ec99 100644
--- a/spec/frontend/pipeline_wizard/components/widgets/checklist_spec.js
+++ b/spec/frontend/pipeline_wizard/components/widgets/checklist_spec.js
@@ -39,10 +39,6 @@ describe('Pipeline Wizard - Checklist Widget', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('creates the component', () => {
createComponent();
expect(wrapper.exists()).toBe(true);
diff --git a/spec/frontend/pipeline_wizard/components/widgets/list_spec.js b/spec/frontend/pipeline_wizard/components/widgets/list_spec.js
index c9e9f5caebe..b0eb7279a94 100644
--- a/spec/frontend/pipeline_wizard/components/widgets/list_spec.js
+++ b/spec/frontend/pipeline_wizard/components/widgets/list_spec.js
@@ -39,10 +39,6 @@ describe('Pipeline Wizard - List Widget', () => {
};
describe('component setup and interface', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it('prints the label inside the legend', () => {
createComponent();
@@ -168,10 +164,6 @@ describe('Pipeline Wizard - List Widget', () => {
});
describe('form validation', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it('does not show validation state when untouched', async () => {
createComponent({}, mountExtended);
expect(findGlFormGroup().classes()).not.toContain('is-valid');
diff --git a/spec/frontend/pipeline_wizard/components/wrapper_spec.js b/spec/frontend/pipeline_wizard/components/wrapper_spec.js
index 33c6394eb41..1056602c912 100644
--- a/spec/frontend/pipeline_wizard/components/wrapper_spec.js
+++ b/spec/frontend/pipeline_wizard/components/wrapper_spec.js
@@ -48,10 +48,6 @@ describe('Pipeline Wizard - wrapper.vue', () => {
wrapper.find(`[data-input-target="${target}"]`).find('input');
describe('display', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it('shows the steps', () => {
createComponent();
@@ -145,10 +141,6 @@ describe('Pipeline Wizard - wrapper.vue', () => {
}
});
- afterEach(() => {
- wrapper.destroy();
- });
-
if (expectCommitStepShown) {
it('does not show the step wrapper', async () => {
expect(wrapper.findComponent(WizardStep).isVisible()).toBe(false);
@@ -188,10 +180,6 @@ describe('Pipeline Wizard - wrapper.vue', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('initially shows a placeholder', async () => {
const editorContent = getEditorContent();
@@ -240,10 +228,6 @@ describe('Pipeline Wizard - wrapper.vue', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('highlight requests by the step get passed on to the editor', async () => {
const highlight = 'foo';
@@ -309,7 +293,6 @@ describe('Pipeline Wizard - wrapper.vue', () => {
});
afterEach(() => {
- wrapper.destroy();
inputField = undefined;
});
@@ -331,10 +314,6 @@ describe('Pipeline Wizard - wrapper.vue', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('emits done', () => {
expect(wrapper.emitted('done')).toBeUndefined();
diff --git a/spec/frontend/pipeline_wizard/pipeline_wizard_spec.js b/spec/frontend/pipeline_wizard/pipeline_wizard_spec.js
index 13234525159..e7bd7f686b6 100644
--- a/spec/frontend/pipeline_wizard/pipeline_wizard_spec.js
+++ b/spec/frontend/pipeline_wizard/pipeline_wizard_spec.js
@@ -24,10 +24,6 @@ describe('PipelineWizard', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('mounts without error', () => {
const consoleSpy = jest.spyOn(console, 'error');
diff --git a/spec/frontend/pipelines/components/dag/dag_annotations_spec.js b/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
index 28a08b6da0f..aecaa640266 100644
--- a/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
@@ -28,11 +28,6 @@ describe('The DAG annotations', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when there is one annotation', () => {
const currentNote = singleNote['dag-link103'];
diff --git a/spec/frontend/pipelines/components/dag/dag_graph_spec.js b/spec/frontend/pipelines/components/dag/dag_graph_spec.js
index 4619548d1bb..6b46be3dd49 100644
--- a/spec/frontend/pipelines/components/dag/dag_graph_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_graph_spec.js
@@ -36,11 +36,6 @@ describe('The DAG graph', () => {
createComponent({ graphData: parsedData });
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('in the basic case', () => {
beforeEach(() => {
/*
diff --git a/spec/frontend/pipelines/components/dag/dag_spec.js b/spec/frontend/pipelines/components/dag/dag_spec.js
index b0c26976c85..e2dc8120309 100644
--- a/spec/frontend/pipelines/components/dag/dag_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_spec.js
@@ -51,11 +51,6 @@ describe('Pipeline DAG graph wrapper', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when a query argument is undefined', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/pipelines/components/jobs/failed_jobs_app_spec.js b/spec/frontend/pipelines/components/jobs/failed_jobs_app_spec.js
index d1da7cb3acf..169e3666cbd 100644
--- a/spec/frontend/pipelines/components/jobs/failed_jobs_app_spec.js
+++ b/spec/frontend/pipelines/components/jobs/failed_jobs_app_spec.js
@@ -4,7 +4,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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import FailedJobsApp from '~/pipelines/components/jobs/failed_jobs_app.vue';
import FailedJobsTable from '~/pipelines/components/jobs/failed_jobs_table.vue';
import GetFailedJobsQuery from '~/pipelines/graphql/queries/get_failed_jobs.query.graphql';
@@ -12,7 +12,7 @@ import { mockFailedJobsQueryResponse, mockFailedJobsSummaryData } from '../../mo
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Failed Jobs App', () => {
let wrapper;
@@ -44,10 +44,6 @@ describe('Failed Jobs App', () => {
resolverSpy = jest.fn().mockResolvedValue(mockFailedJobsQueryResponse);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('loading spinner', () => {
it('displays loading spinner when fetching failed jobs', () => {
createComponent(resolverSpy);
diff --git a/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js b/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js
index 0df15afd70d..0ac3b6c9074 100644
--- a/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js
+++ b/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js
@@ -4,7 +4,7 @@ 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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { redirectTo } from '~/lib/utils/url_utility';
import FailedJobsTable from '~/pipelines/components/jobs/failed_jobs_table.vue';
import RetryFailedJobMutation from '~/pipelines/graphql/mutations/retry_failed_job.mutation.graphql';
@@ -15,7 +15,7 @@ import {
mockPreparedFailedJobsDataNoPermission,
} from '../../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility');
Vue.use(VueApollo);
@@ -45,10 +45,6 @@ describe('Failed Jobs Table', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays the failed jobs table', () => {
createComponent();
diff --git a/spec/frontend/pipelines/components/jobs/jobs_app_spec.js b/spec/frontend/pipelines/components/jobs/jobs_app_spec.js
index 9bc14266593..52df7b4500b 100644
--- a/spec/frontend/pipelines/components/jobs/jobs_app_spec.js
+++ b/spec/frontend/pipelines/components/jobs/jobs_app_spec.js
@@ -4,7 +4,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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import JobsApp from '~/pipelines/components/jobs/jobs_app.vue';
import JobsTable from '~/jobs/components/table/jobs_table.vue';
import getPipelineJobsQuery from '~/pipelines/graphql/queries/get_pipeline_jobs.query.graphql';
@@ -12,7 +12,7 @@ import { mockPipelineJobsQueryResponse } from '../../mock_data';
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Jobs app', () => {
let wrapper;
@@ -45,10 +45,6 @@ describe('Jobs app', () => {
resolverSpy = jest.fn().mockResolvedValue(mockPipelineJobsQueryResponse);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('loading spinner', () => {
const setup = async () => {
createComponent(resolverSpy);
diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js
index 5ea57c51e70..a4ecb9041c9 100644
--- a/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js
+++ b/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js
@@ -19,7 +19,7 @@ describe('Linked pipeline mini list', () => {
const createComponent = (props = {}) => {
wrapper = mount(LinkedPipelinesMiniList, {
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
propsData: {
...props,
@@ -34,11 +34,6 @@ describe('Linked pipeline mini list', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should render one linked pipeline item', () => {
expect(findLinkedPipelineMiniItem().exists()).toBe(true);
});
@@ -102,11 +97,6 @@ describe('Linked pipeline mini list', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should render three linked pipeline items', () => {
expect(findLinkedPipelineMiniItems().exists()).toBe(true);
expect(findLinkedPipelineMiniItems().length).toBe(3);
diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_mini_graph_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_mini_graph_spec.js
index 036b82530d5..e7415a6c596 100644
--- a/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_mini_graph_spec.js
+++ b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_mini_graph_spec.js
@@ -33,11 +33,6 @@ describe('Pipeline Mini Graph', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should render the pipeline stages', () => {
expect(findPipelineStages().exists()).toBe(true);
});
@@ -71,11 +66,6 @@ describe('Pipeline Mini Graph', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should have the correct props', () => {
expect(findPipelineMiniGraph().props()).toMatchObject({
downstreamPipelines: [],
@@ -118,11 +108,6 @@ describe('Pipeline Mini Graph', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should render the downstream linked pipelines mini list only', () => {
expect(findLinkedPipelineDownstream().exists()).toBe(true);
expect(findLinkedPipelineUpstream().exists()).toBe(false);
diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js
index ab2056b4035..864f2d66f60 100644
--- a/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js
+++ b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js
@@ -45,11 +45,10 @@ describe('Pipelines stage component', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
-
eventHub.$emit.mockRestore();
mock.restore();
+ // eslint-disable-next-line @gitlab/vtu-no-explicit-wrapper-destroy
+ wrapper.destroy();
});
const findCiActionBtn = () => wrapper.find('.js-ci-action');
diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js
index c123f53886e..73e810bde99 100644
--- a/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js
+++ b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js
@@ -60,9 +60,4 @@ describe('Pipeline Stages', () => {
expect(findPipelineStagesAt(0).props('isMergeTrain')).toBe(true);
expect(findPipelineStagesAt(1).props('isMergeTrain')).toBe(true);
});
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
});
diff --git a/spec/frontend/pipelines/components/pipeline_tabs_spec.js b/spec/frontend/pipelines/components/pipeline_tabs_spec.js
index c2cb95d4320..337af6c1f60 100644
--- a/spec/frontend/pipelines/components/pipeline_tabs_spec.js
+++ b/spec/frontend/pipelines/components/pipeline_tabs_spec.js
@@ -39,10 +39,6 @@ describe('The Pipeline Tabs', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Tabs', () => {
it.each`
tabName | tabComponent
diff --git a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
index ba7262353f0..51a4487a3ef 100644
--- a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
@@ -51,8 +51,6 @@ describe('Pipelines filtered search', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
- wrapper = null;
});
it('displays UI elements', () => {
diff --git a/spec/frontend/pipelines/components/pipelines_list/empty_state/ci_templates_spec.js b/spec/frontend/pipelines/components/pipelines_list/empty_state/ci_templates_spec.js
index 6531a15ab8e..b560eea4882 100644
--- a/spec/frontend/pipelines/components/pipelines_list/empty_state/ci_templates_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_list/empty_state/ci_templates_spec.js
@@ -29,11 +29,6 @@ describe('CI Templates', () => {
const findTemplateName = () => wrapper.findByTestId('template-name');
const findTemplateLogo = () => wrapper.findByTestId('template-logo');
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('renders template list', () => {
beforeEach(() => {
createWrapper();
diff --git a/spec/frontend/pipelines/components/pipelines_list/empty_state/ios_templates_spec.js b/spec/frontend/pipelines/components/pipelines_list/empty_state/ios_templates_spec.js
index 0c2938921d6..700be076e0c 100644
--- a/spec/frontend/pipelines/components/pipelines_list/empty_state/ios_templates_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_list/empty_state/ios_templates_spec.js
@@ -37,11 +37,6 @@ describe('iOS Templates', () => {
const findSetupRunnerLink = () => wrapper.findByText('Set up a runner');
const configurePipelineLink = () => wrapper.findByTestId('configure-pipeline-link');
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when ios runners are not available', () => {
beforeEach(() => {
wrapper = createWrapper({ iosRunnersAvailable: false });
diff --git a/spec/frontend/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates_spec.js b/spec/frontend/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates_spec.js
index f255e0d857f..0f4a2b1d02f 100644
--- a/spec/frontend/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates_spec.js
@@ -42,11 +42,6 @@ describe('Pipelines CI Templates', () => {
const findDocumentationLink = () => wrapper.findByTestId('documentation-link');
const findSettingsButton = () => wrapper.findByTestId('settings-button');
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('renders test template', () => {
beforeEach(() => {
wrapper = createWrapper();
diff --git a/spec/frontend/pipelines/empty_state_spec.js b/spec/frontend/pipelines/empty_state_spec.js
index 0abf7f59717..5465e4d77da 100644
--- a/spec/frontend/pipelines/empty_state_spec.js
+++ b/spec/frontend/pipelines/empty_state_spec.js
@@ -35,11 +35,6 @@ describe('Pipelines Empty State', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when user can configure CI', () => {
describe('when the ios_specific_templates experiment is active', () => {
beforeEach(() => {
diff --git a/spec/frontend/pipelines/graph/action_component_spec.js b/spec/frontend/pipelines/graph/action_component_spec.js
index e3eea503b46..890255f225e 100644
--- a/spec/frontend/pipelines/graph/action_component_spec.js
+++ b/spec/frontend/pipelines/graph/action_component_spec.js
@@ -33,7 +33,6 @@ describe('pipeline graph action component', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
describe('render', () => {
diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
index 99bccd21656..42e47a23db8 100644
--- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
+++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
@@ -101,10 +101,6 @@ describe('Pipeline graph wrapper', () => {
createComponent({ apolloProvider, data, provide, mountFn });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when data is loading', () => {
it('displays the loading icon', () => {
createComponentWithApollo();
diff --git a/spec/frontend/pipelines/graph/graph_view_selector_spec.js b/spec/frontend/pipelines/graph/graph_view_selector_spec.js
index 43587bebedf..78265165d1f 100644
--- a/spec/frontend/pipelines/graph/graph_view_selector_spec.js
+++ b/spec/frontend/pipelines/graph/graph_view_selector_spec.js
@@ -42,10 +42,6 @@ describe('the graph view selector component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when showing stage view', () => {
beforeEach(() => {
createComponent({ mountFn: mount });
diff --git a/spec/frontend/pipelines/graph/job_group_dropdown_spec.js b/spec/frontend/pipelines/graph/job_group_dropdown_spec.js
index d8afb33e148..1419a7b9982 100644
--- a/spec/frontend/pipelines/graph/job_group_dropdown_spec.js
+++ b/spec/frontend/pipelines/graph/job_group_dropdown_spec.js
@@ -69,10 +69,6 @@ describe('job group dropdown component', () => {
wrapper = mountFn(JobGroupDropdown, { propsData: { group } });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
beforeEach(() => {
createComponent({ mountFn: mount });
});
diff --git a/spec/frontend/pipelines/graph/job_item_spec.js b/spec/frontend/pipelines/graph/job_item_spec.js
index 3224c87ab6b..5cc2c76f3dd 100644
--- a/spec/frontend/pipelines/graph/job_item_spec.js
+++ b/spec/frontend/pipelines/graph/job_item_spec.js
@@ -1,10 +1,11 @@
import MockAdapter from 'axios-mock-adapter';
-import { mount } from '@vue/test-utils';
-import { nextTick } from 'vue';
-import { GlBadge, GlModal } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+import { GlBadge, GlModal, GlToast } from '@gitlab/ui';
import JobItem from '~/pipelines/components/graph/job_item.vue';
import axios from '~/lib/utils/axios_utils';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import ActionComponent from '~/pipelines/components/jobs_shared/action_component.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import {
@@ -19,12 +20,14 @@ import {
describe('pipeline graph job item', () => {
useLocalStorageSpy();
+ Vue.use(GlToast);
let wrapper;
let mockAxios;
const findJobWithoutLink = () => wrapper.findByTestId('job-without-link');
const findJobWithLink = () => wrapper.findByTestId('job-with-link');
+ const findActionVueComponent = () => wrapper.findComponent(ActionComponent);
const findActionComponent = () => wrapper.findByTestId('ci-action-component');
const findBadge = () => wrapper.findComponent(GlBadge);
const findJobLink = () => wrapper.findByTestId('job-with-link');
@@ -41,9 +44,9 @@ describe('pipeline graph job item', () => {
job: mockJob,
};
- const createWrapper = ({ props, data } = {}) => {
+ const createWrapper = ({ props, data, mountFn = mount, mocks = {} } = {}) => {
wrapper = extendedWrapper(
- mount(JobItem, {
+ mountFn(JobItem, {
data() {
return {
...data,
@@ -53,6 +56,9 @@ describe('pipeline graph job item', () => {
...defaultProps,
...props,
},
+ mocks: {
+ ...mocks,
+ },
}),
);
};
@@ -238,6 +244,37 @@ describe('pipeline graph job item', () => {
});
});
+ describe('when retrying', () => {
+ const mockToastShow = jest.fn();
+
+ beforeEach(async () => {
+ createWrapper({
+ mountFn: shallowMount,
+ data: {
+ currentSkipModalValue: true,
+ },
+ props: {
+ skipRetryModal: true,
+ job: triggerJobWithRetryAction,
+ },
+ mocks: {
+ $toast: {
+ show: mockToastShow,
+ },
+ },
+ });
+
+ jest.spyOn(wrapper.vm.$toast, 'show');
+
+ await findActionVueComponent().vm.$emit('pipelineActionRequestComplete');
+ await nextTick();
+ });
+
+ it('shows a toast message that the downstream is being created', () => {
+ expect(mockToastShow).toHaveBeenCalledTimes(1);
+ });
+ });
+
describe('highlighting', () => {
it.each`
job | jobName | expanded | link
diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
index f396fe2aff4..b5ef10dee12 100644
--- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
@@ -58,10 +58,6 @@ describe('Linked pipeline', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('rendered output', () => {
const props = {
pipeline: mockPipeline,
diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
index 63e2d8707ea..6e4b9498918 100644
--- a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
@@ -65,10 +65,6 @@ describe('Linked Pipelines Column', () => {
createComponent({ apolloProvider, mountFn, props });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('it renders correctly', () => {
beforeEach(() => {
createComponentWithApollo();
diff --git a/spec/frontend/pipelines/graph/stage_column_component_spec.js b/spec/frontend/pipelines/graph/stage_column_component_spec.js
index 19f597a7267..d4d7f1618c5 100644
--- a/spec/frontend/pipelines/graph/stage_column_component_spec.js
+++ b/spec/frontend/pipelines/graph/stage_column_component_spec.js
@@ -54,10 +54,6 @@ describe('stage column component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when mounted', () => {
beforeEach(() => {
createComponent({ method: mount });
diff --git a/spec/frontend/pipelines/graph_shared/links_inner_spec.js b/spec/frontend/pipelines/graph_shared/links_inner_spec.js
index 2c6d126e12c..50f754393fe 100644
--- a/spec/frontend/pipelines/graph_shared/links_inner_spec.js
+++ b/spec/frontend/pipelines/graph_shared/links_inner_spec.js
@@ -81,7 +81,6 @@ describe('Links Inner component', () => {
afterEach(() => {
jest.restoreAllMocks();
- wrapper.destroy();
resetHTMLFixture();
});
diff --git a/spec/frontend/pipelines/graph_shared/links_layer_spec.js b/spec/frontend/pipelines/graph_shared/links_layer_spec.js
index e2699d6ff2e..9d39c86ed5e 100644
--- a/spec/frontend/pipelines/graph_shared/links_layer_spec.js
+++ b/spec/frontend/pipelines/graph_shared/links_layer_spec.js
@@ -35,10 +35,6 @@ describe('links layer component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with show links off', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/pipelines/header_component_spec.js b/spec/frontend/pipelines/header_component_spec.js
index e583c0798f5..a4d7d0e30f8 100644
--- a/spec/frontend/pipelines/header_component_spec.js
+++ b/spec/frontend/pipelines/header_component_spec.js
@@ -71,11 +71,6 @@ describe('Pipeline details header', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('initial loading', () => {
beforeEach(() => {
wrapper = createComponent(null, { isLoading: true });
diff --git a/spec/frontend/pipelines/nav_controls_spec.js b/spec/frontend/pipelines/nav_controls_spec.js
index 2c4740df174..15de7dc51f1 100644
--- a/spec/frontend/pipelines/nav_controls_spec.js
+++ b/spec/frontend/pipelines/nav_controls_spec.js
@@ -1,23 +1,20 @@
-import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import NavControls from '~/pipelines/components/pipelines_list/nav_controls.vue';
describe('Pipelines Nav Controls', () => {
let wrapper;
const createComponent = (props) => {
- wrapper = shallowMount(NavControls, {
+ wrapper = shallowMountExtended(NavControls, {
propsData: {
...props,
},
});
};
- const findRunPipeline = () => wrapper.find('.js-run-pipeline');
-
- afterEach(() => {
- wrapper.destroy();
- });
+ const findRunPipelineButton = () => wrapper.findByTestId('run-pipeline-button');
+ const findCiLintButton = () => wrapper.findByTestId('ci-lint-button');
+ const findClearCacheButton = () => wrapper.findByTestId('clear-cache-button');
it('should render link to create a new pipeline', () => {
const mockData = {
@@ -28,9 +25,9 @@ describe('Pipelines Nav Controls', () => {
createComponent(mockData);
- const runPipeline = findRunPipeline();
- expect(runPipeline.text()).toContain('Run pipeline');
- expect(runPipeline.attributes('href')).toBe(mockData.newPipelinePath);
+ const runPipelineButton = findRunPipelineButton();
+ expect(runPipelineButton.text()).toContain('Run pipeline');
+ expect(runPipelineButton.attributes('href')).toBe(mockData.newPipelinePath);
});
it('should not render link to create pipeline if no path is provided', () => {
@@ -42,7 +39,7 @@ describe('Pipelines Nav Controls', () => {
createComponent(mockData);
- expect(findRunPipeline().exists()).toBe(false);
+ expect(findRunPipelineButton().exists()).toBe(false);
});
it('should render link for CI lint', () => {
@@ -54,9 +51,10 @@ describe('Pipelines Nav Controls', () => {
};
createComponent(mockData);
+ const ciLintButton = findCiLintButton();
- expect(wrapper.find('.js-ci-lint').text().trim()).toContain('CI lint');
- expect(wrapper.find('.js-ci-lint').attributes('href')).toBe(mockData.ciLintPath);
+ expect(ciLintButton.text()).toContain('CI lint');
+ expect(ciLintButton.attributes('href')).toBe(mockData.ciLintPath);
});
describe('Reset Runners Cache', () => {
@@ -70,16 +68,13 @@ describe('Pipelines Nav Controls', () => {
});
it('should render button for resetting runner caches', () => {
- expect(wrapper.find('.js-clear-cache').text().trim()).toContain('Clear runner caches');
+ expect(findClearCacheButton().text()).toContain('Clear runner caches');
});
- it('should emit postAction event when reset runner cache button is clicked', async () => {
- jest.spyOn(wrapper.vm, '$emit').mockImplementation(() => {});
-
- wrapper.find('.js-clear-cache').vm.$emit('click');
- await nextTick();
+ it('should emit postAction event when reset runner cache button is clicked', () => {
+ findClearCacheButton().vm.$emit('click');
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('resetRunnersCache', 'foo');
+ expect(wrapper.emitted('resetRunnersCache')).toEqual([['foo']]);
});
});
});
diff --git a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
index df10742fd93..123f2e011c3 100644
--- a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
+++ b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
@@ -39,10 +39,6 @@ describe('pipeline graph component', () => {
const findLinksLayer = () => wrapper.findComponent(LinksLayer);
const findPipelineGraph = () => wrapper.find('[data-testid="graph-container"]');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with `VALID` status', () => {
beforeEach(() => {
wrapper = createComponent({
diff --git a/spec/frontend/pipelines/pipeline_labels_spec.js b/spec/frontend/pipelines/pipeline_labels_spec.js
index ca0229b1cbe..6a37e36352b 100644
--- a/spec/frontend/pipelines/pipeline_labels_spec.js
+++ b/spec/frontend/pipelines/pipeline_labels_spec.js
@@ -30,10 +30,6 @@ describe('Pipeline label component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should not render tags when flags are not set', () => {
createComponent();
diff --git a/spec/frontend/pipelines/pipeline_multi_actions_spec.js b/spec/frontend/pipelines/pipeline_multi_actions_spec.js
index bedde71c48d..e3c9983aa52 100644
--- a/spec/frontend/pipelines/pipeline_multi_actions_spec.js
+++ b/spec/frontend/pipelines/pipeline_multi_actions_spec.js
@@ -67,8 +67,6 @@ describe('Pipeline Multi Actions Dropdown', () => {
afterEach(() => {
mockAxios.restore();
-
- wrapper.destroy();
});
it('should render the dropdown', () => {
diff --git a/spec/frontend/pipelines/pipeline_triggerer_spec.js b/spec/frontend/pipelines/pipeline_triggerer_spec.js
index 58bfb68e85c..856c0484075 100644
--- a/spec/frontend/pipelines/pipeline_triggerer_spec.js
+++ b/spec/frontend/pipelines/pipeline_triggerer_spec.js
@@ -22,15 +22,11 @@ describe('Pipelines Triggerer', () => {
...props,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findAvatarLink = () => wrapper.findComponent(GlAvatarLink);
const findAvatar = () => wrapper.findComponent(GlAvatar);
const findTriggerer = () => wrapper.findByText('API');
diff --git a/spec/frontend/pipelines/pipeline_url_spec.js b/spec/frontend/pipelines/pipeline_url_spec.js
index c62898f0c83..f00ee4a6367 100644
--- a/spec/frontend/pipelines/pipeline_url_spec.js
+++ b/spec/frontend/pipelines/pipeline_url_spec.js
@@ -35,10 +35,6 @@ describe('Pipeline Url Component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render pipeline url table cell', () => {
createComponent();
diff --git a/spec/frontend/pipelines/pipelines_actions_spec.js b/spec/frontend/pipelines/pipelines_actions_spec.js
index e034d52a33c..2db9f5c2a83 100644
--- a/spec/frontend/pipelines/pipelines_actions_spec.js
+++ b/spec/frontend/pipelines/pipelines_actions_spec.js
@@ -5,7 +5,7 @@ import { nextTick } from 'vue';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { TEST_HOST } from 'spec/test_constants';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
@@ -13,7 +13,7 @@ import PipelinesManualActions from '~/pipelines/components/pipelines_list/pipeli
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
import { TRACKING_CATEGORIES } from '~/pipelines/constants';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal');
describe('Pipelines Actions dropdown', () => {
@@ -37,9 +37,6 @@ describe('Pipelines Actions dropdown', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
-
mock.restore();
confirmAction.mockReset();
});
diff --git a/spec/frontend/pipelines/pipelines_artifacts_spec.js b/spec/frontend/pipelines/pipelines_artifacts_spec.js
index e3e54716a7b..9fedbaf9b56 100644
--- a/spec/frontend/pipelines/pipelines_artifacts_spec.js
+++ b/spec/frontend/pipelines/pipelines_artifacts_spec.js
@@ -34,11 +34,6 @@ describe('Pipelines Artifacts dropdown', () => {
const findAllGlDropdownItems = () =>
wrapper.findComponent(GlDropdown).findAllComponents(GlDropdownItem);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should render a dropdown with all the provided artifacts', () => {
createComponent();
diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js
index 2523b901506..48539d84024 100644
--- a/spec/frontend/pipelines/pipelines_spec.js
+++ b/spec/frontend/pipelines/pipelines_spec.js
@@ -11,7 +11,7 @@ import { mockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
-import { createAlert, VARIANT_WARNING } from '~/flash';
+import { createAlert, VARIANT_WARNING } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue';
@@ -25,7 +25,7 @@ import TablePagination from '~/vue_shared/components/pagination/table_pagination
import { stageReply, users, mockSearch, branches } from './mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
const mockProjectPath = 'twitter/flight';
const mockProjectId = '21';
@@ -114,7 +114,6 @@ describe('Pipelines', () => {
});
afterEach(() => {
- wrapper.destroy();
mock.reset();
window.history.pushState.mockReset();
});
diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js
index 6ec8901038b..8d2a52eb6d0 100644
--- a/spec/frontend/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_spec.js
@@ -69,12 +69,6 @@ describe('Pipelines Table', () => {
pipeline = createMockPipeline();
});
- afterEach(() => {
- wrapper.destroy();
-
- wrapper = null;
- });
-
describe('Pipelines Table', () => {
beforeEach(() => {
createComponent({ pipelines: [pipeline], viewType: 'root' });
diff --git a/spec/frontend/pipelines/test_reports/stores/actions_spec.js b/spec/frontend/pipelines/test_reports/stores/actions_spec.js
index f6287107ed0..e05d2151f0a 100644
--- a/spec/frontend/pipelines/test_reports/stores/actions_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/actions_spec.js
@@ -2,13 +2,13 @@ import MockAdapter from 'axios-mock-adapter';
import testReports from 'test_fixtures/pipelines/test_report.json';
import { TEST_HOST } from 'helpers/test_constants';
import testAction from 'helpers/vuex_action_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import * as actions from '~/pipelines/stores/test_reports/actions';
import * as types from '~/pipelines/stores/test_reports/mutation_types';
-jest.mock('~/flash.js');
+jest.mock('~/alert');
describe('Actions TestReports Store', () => {
let mock;
@@ -49,7 +49,7 @@ describe('Actions TestReports Store', () => {
);
});
- it('should create flash on API error', async () => {
+ it('should create alert on API error', async () => {
await testAction(
actions.fetchSummary,
null,
diff --git a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
index ed0cc71eb97..9c374ea817a 100644
--- a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
@@ -1,9 +1,9 @@
import testReports from 'test_fixtures/pipelines/test_report.json';
import * as types from '~/pipelines/stores/test_reports/mutation_types';
import mutations from '~/pipelines/stores/test_reports/mutations';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
-jest.mock('~/flash.js');
+jest.mock('~/alert');
describe('Mutations TestReports Store', () => {
let mockState;
@@ -58,7 +58,7 @@ describe('Mutations TestReports Store', () => {
expect(mockState.errorMessage).toBe(message);
});
- it('should show a flash message otherwise', () => {
+ it('should show an alert message otherwise', () => {
mutations[types.SET_SUITE_ERROR](mockState, {});
expect(createAlert).toHaveBeenCalled();
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 f194864447c..f8663408817 100644
--- a/spec/frontend/pipelines/test_reports/test_case_details_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_case_details_spec.js
@@ -45,11 +45,6 @@ describe('Test case details', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('required details', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/pipelines/test_reports/test_reports_spec.js b/spec/frontend/pipelines/test_reports/test_reports_spec.js
index 9b9ee4172f9..c8c917a1b9e 100644
--- a/spec/frontend/pipelines/test_reports/test_reports_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_reports_spec.js
@@ -60,10 +60,6 @@ describe('Test reports app', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when component is created', () => {
it('should call fetchSummary when pipeline has test report', () => {
createComponent();
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 da13df833e7..8eb83f17f4d 100644
--- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
@@ -65,10 +65,6 @@ describe('Test reports suite table', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render a message when there are no test cases', () => {
createComponent({ suite: [] });
diff --git a/spec/frontend/pipelines/time_ago_spec.js b/spec/frontend/pipelines/time_ago_spec.js
index f0da0df2ba6..efb1bf09d20 100644
--- a/spec/frontend/pipelines/time_ago_spec.js
+++ b/spec/frontend/pipelines/time_ago_spec.js
@@ -30,11 +30,6 @@ describe('Timeago component', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const duration = () => wrapper.find('.duration');
const finishedAt = () => wrapper.find('.finished-at');
const findInProgress = () => wrapper.findByTestId('pipeline-in-progress');
diff --git a/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
index caa66502e11..d518519a424 100644
--- a/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
@@ -71,11 +71,6 @@ describe('Pipeline Branch Name Token', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('passes config correctly', () => {
expect(findFilteredSearchToken().props('config')).toEqual(defaultProps.config);
});
diff --git a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
index c090fd353f7..cf4ccb5ce43 100644
--- a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
@@ -45,11 +45,6 @@ describe('Pipeline Status Token', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('passes config correctly', () => {
expect(findFilteredSearchToken().props('config')).toEqual(defaultProps.config);
});
diff --git a/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
index 7311a5d2f5a..88c88d8f16f 100644
--- a/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
@@ -53,11 +53,6 @@ describe('Pipeline Branch Name Token', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('passes config correctly', () => {
expect(findFilteredSearchToken().props('config')).toEqual(defaultProps.config);
});
diff --git a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
index c763bfe1b27..e9ec684a350 100644
--- a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
@@ -52,11 +52,6 @@ describe('Pipeline Trigger Author Token', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('passes config correctly', () => {
expect(findFilteredSearchToken().props('config')).toEqual(defaultProps.config);
});
diff --git a/spec/frontend/popovers/components/popovers_spec.js b/spec/frontend/popovers/components/popovers_spec.js
index 1299e7277d1..7f247fbbd4f 100644
--- a/spec/frontend/popovers/components/popovers_spec.js
+++ b/spec/frontend/popovers/components/popovers_spec.js
@@ -33,11 +33,6 @@ describe('popovers/components/popovers.vue', () => {
const allPopovers = () => wrapper.findAllComponents(GlPopover);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('addPopovers', () => {
it('attaches popovers to the targets specified', async () => {
const target = createPopoverTarget();
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 e4a316e1ee7..9a8f82f0028 100644
--- a/spec/frontend/profile/account/components/delete_account_modal_spec.js
+++ b/spec/frontend/profile/account/components/delete_account_modal_spec.js
@@ -40,12 +40,6 @@ describe('DeleteAccountModal component', () => {
vm = wrapper.vm;
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- vm = null;
- });
-
const findElements = () => {
const confirmation = vm.confirmWithPassword ? 'password' : 'username';
return {
diff --git a/spec/frontend/profile/account/components/update_username_spec.js b/spec/frontend/profile/account/components/update_username_spec.js
index fa0e86a7b05..d922820601e 100644
--- a/spec/frontend/profile/account/components/update_username_spec.js
+++ b/spec/frontend/profile/account/components/update_username_spec.js
@@ -1,14 +1,15 @@
import { GlModal } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
+import waitForPromises from 'helpers/wait_for_promises';
import { TEST_HOST } from 'helpers/test_constants';
-import { createAlert } from '~/flash';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import UpdateUsername from '~/profile/account/components/update_username.vue';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('UpdateUsername component', () => {
const rootUrl = TEST_HOST;
@@ -21,8 +22,10 @@ describe('UpdateUsername component', () => {
let wrapper;
let axiosMock;
+ const findNewUsernameInput = () => wrapper.findByTestId('new-username-input');
+
const createComponent = (props = {}) => {
- wrapper = shallowMount(UpdateUsername, {
+ wrapper = shallowMountExtended(UpdateUsername, {
propsData: {
...defaultProps,
...props,
@@ -39,8 +42,8 @@ describe('UpdateUsername component', () => {
});
afterEach(() => {
- wrapper.destroy();
axiosMock.restore();
+ Vue.config.errorHandler = null;
});
const findElements = () => {
@@ -56,6 +59,13 @@ describe('UpdateUsername component', () => {
};
};
+ const clickModalWithErrorResponse = () => {
+ Vue.config.errorHandler = jest.fn(); // silence thrown error
+ const { modal } = findElements();
+ modal.vm.$emit('primary');
+ return waitForPromises();
+ };
+
it('has a disabled button if the username was not changed', async () => {
const { openModalBtn } = findElements();
@@ -80,11 +90,7 @@ describe('UpdateUsername component', () => {
beforeEach(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({ newUsername });
-
- await nextTick();
+ await findNewUsernameInput().setValue(newUsername);
});
it('confirmation modal contains proper header and body', async () => {
@@ -100,14 +106,15 @@ describe('UpdateUsername component', () => {
axiosMock.onPut(actionUrl).replyOnce(() => [HTTP_STATUS_OK, { message: 'Username changed' }]);
jest.spyOn(axios, 'put');
- await wrapper.vm.onConfirm();
- await nextTick();
+ const { modal } = findElements();
+ modal.vm.$emit('primary');
+ await waitForPromises();
expect(axios.put).toHaveBeenCalledWith(actionUrl, { user: { username: newUsername } });
});
it('sets the username after a successful update', async () => {
- const { input, openModalBtn } = findElements();
+ const { input, openModalBtn, modal } = findElements();
axiosMock.onPut(actionUrl).replyOnce(() => {
expect(input.attributes('disabled')).toBe('disabled');
@@ -117,8 +124,8 @@ describe('UpdateUsername component', () => {
return [HTTP_STATUS_OK, { message: 'Username changed' }];
});
- await wrapper.vm.onConfirm();
- await nextTick();
+ modal.vm.$emit('primary');
+ await waitForPromises();
expect(input.attributes('disabled')).toBe(undefined);
expect(openModalBtn.props('disabled')).toBe(true);
@@ -136,7 +143,8 @@ describe('UpdateUsername component', () => {
return [HTTP_STATUS_BAD_REQUEST, { message: 'Invalid username' }];
});
- await expect(wrapper.vm.onConfirm()).rejects.toThrow();
+ await clickModalWithErrorResponse();
+
expect(input.attributes('disabled')).toBe(undefined);
expect(openModalBtn.props('disabled')).toBe(false);
expect(openModalBtn.props('loading')).toBe(false);
@@ -147,7 +155,7 @@ describe('UpdateUsername component', () => {
return [HTTP_STATUS_BAD_REQUEST, { message: 'Invalid username' }];
});
- await expect(wrapper.vm.onConfirm()).rejects.toThrow();
+ await clickModalWithErrorResponse();
expect(createAlert).toHaveBeenCalledWith({
message: 'Invalid username',
@@ -159,7 +167,7 @@ describe('UpdateUsername component', () => {
return [HTTP_STATUS_BAD_REQUEST];
});
- await expect(wrapper.vm.onConfirm()).rejects.toThrow();
+ await clickModalWithErrorResponse();
expect(createAlert).toHaveBeenCalledWith({
message: 'An error occurred while updating your username, please try again.',
diff --git a/spec/frontend/profile/components/activity_calendar_spec.js b/spec/frontend/profile/components/activity_calendar_spec.js
new file mode 100644
index 00000000000..fb9dc7b22f7
--- /dev/null
+++ b/spec/frontend/profile/components/activity_calendar_spec.js
@@ -0,0 +1,120 @@
+import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
+import * as GitLabUIUtils from '@gitlab/ui/dist/utils';
+
+import ActivityCalendar from '~/profile/components/activity_calendar.vue';
+import AjaxCache from '~/lib/utils/ajax_cache';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { useFakeDate } from 'helpers/fake_date';
+import { userCalendarResponse } from '../mock_data';
+
+jest.mock('~/lib/utils/ajax_cache');
+jest.mock('@gitlab/ui/dist/utils');
+
+describe('ActivityCalendar', () => {
+ // Feb 21st, 2023
+ useFakeDate(2023, 1, 21);
+
+ let wrapper;
+
+ const defaultProvide = {
+ userCalendarPath: '/users/root/calendar.json',
+ utcOffset: '0',
+ };
+
+ const createComponent = () => {
+ wrapper = mountExtended(ActivityCalendar, { provide: defaultProvide });
+ };
+
+ const mockSuccessfulApiRequest = () =>
+ AjaxCache.retrieve.mockResolvedValueOnce(userCalendarResponse);
+ const mockUnsuccessfulApiRequest = () => AjaxCache.retrieve.mockRejectedValueOnce();
+
+ const findCalendar = () => wrapper.findByTestId('contrib-calendar');
+
+ describe('when API request is loading', () => {
+ beforeEach(() => {
+ AjaxCache.retrieve.mockReturnValueOnce(new Promise(() => {}));
+ });
+
+ it('renders loading icon', () => {
+ createComponent();
+
+ expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ });
+ });
+
+ describe('when API request is successful', () => {
+ beforeEach(() => {
+ mockSuccessfulApiRequest();
+ });
+
+ it('renders the calendar', async () => {
+ createComponent();
+
+ await waitForPromises();
+
+ expect(findCalendar().exists()).toBe(true);
+ expect(wrapper.findByText(ActivityCalendar.i18n.calendarHint).exists()).toBe(true);
+ });
+
+ describe('when window is resized', () => {
+ it('re-renders the calendar', async () => {
+ createComponent();
+
+ await waitForPromises();
+
+ mockSuccessfulApiRequest();
+ window.innerWidth = 1200;
+ window.dispatchEvent(new Event('resize'));
+
+ await waitForPromises();
+
+ expect(findCalendar().exists()).toBe(true);
+ expect(AjaxCache.retrieve).toHaveBeenCalledTimes(2);
+ });
+ });
+ });
+
+ describe('when API request is not successful', () => {
+ beforeEach(() => {
+ mockUnsuccessfulApiRequest();
+ });
+
+ it('renders error', async () => {
+ createComponent();
+
+ await waitForPromises();
+
+ expect(wrapper.findComponent(GlAlert).exists()).toBe(true);
+ });
+
+ describe('when retry button is clicked', () => {
+ it('retries API request', async () => {
+ createComponent();
+
+ await waitForPromises();
+
+ mockSuccessfulApiRequest();
+
+ await wrapper.findByRole('button', { name: ActivityCalendar.i18n.retry }).trigger('click');
+
+ await waitForPromises();
+
+ expect(findCalendar().exists()).toBe(true);
+ });
+ });
+ });
+
+ describe('when screen is extra small', () => {
+ beforeEach(() => {
+ GitLabUIUtils.GlBreakpointInstance.getBreakpointSize.mockReturnValueOnce('xs');
+ });
+
+ it('does not render the calendar', () => {
+ createComponent();
+
+ expect(findCalendar().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/profile/components/followers_tab_spec.js b/spec/frontend/profile/components/followers_tab_spec.js
index 4af428c4e0c..9cc5bdea9be 100644
--- a/spec/frontend/profile/components/followers_tab_spec.js
+++ b/spec/frontend/profile/components/followers_tab_spec.js
@@ -1,4 +1,4 @@
-import { GlTab } from '@gitlab/ui';
+import { GlBadge, GlTab } from '@gitlab/ui';
import { s__ } from '~/locale';
import FollowersTab from '~/profile/components/followers_tab.vue';
@@ -8,12 +8,25 @@ describe('FollowersTab', () => {
let wrapper;
const createComponent = () => {
- wrapper = shallowMountExtended(FollowersTab);
+ wrapper = shallowMountExtended(FollowersTab, {
+ provide: {
+ followers: 2,
+ },
+ });
};
- it('renders `GlTab` and sets `title` prop', () => {
+ it('renders `GlTab` and sets title', () => {
createComponent();
- expect(wrapper.findComponent(GlTab).attributes('title')).toBe(s__('UserProfile|Followers'));
+ expect(wrapper.findComponent(GlTab).element.textContent).toContain(
+ s__('UserProfile|Followers'),
+ );
+ });
+
+ it('renders `GlBadge`, sets size and content', () => {
+ createComponent();
+
+ expect(wrapper.findComponent(GlBadge).attributes('size')).toBe('sm');
+ expect(wrapper.findComponent(GlBadge).element.textContent).toBe('2');
});
});
diff --git a/spec/frontend/profile/components/following_tab_spec.js b/spec/frontend/profile/components/following_tab_spec.js
index 75123274ccb..c9d56360c3e 100644
--- a/spec/frontend/profile/components/following_tab_spec.js
+++ b/spec/frontend/profile/components/following_tab_spec.js
@@ -1,4 +1,4 @@
-import { GlTab } from '@gitlab/ui';
+import { GlBadge, GlTab } from '@gitlab/ui';
import { s__ } from '~/locale';
import FollowingTab from '~/profile/components/following_tab.vue';
@@ -8,12 +8,25 @@ describe('FollowingTab', () => {
let wrapper;
const createComponent = () => {
- wrapper = shallowMountExtended(FollowingTab);
+ wrapper = shallowMountExtended(FollowingTab, {
+ provide: {
+ followees: 3,
+ },
+ });
};
- it('renders `GlTab` and sets `title` prop', () => {
+ it('renders `GlTab` and sets title', () => {
createComponent();
- expect(wrapper.findComponent(GlTab).attributes('title')).toBe(s__('UserProfile|Following'));
+ expect(wrapper.findComponent(GlTab).element.textContent).toContain(
+ s__('UserProfile|Following'),
+ );
+ });
+
+ it('renders `GlBadge`, sets size and content', () => {
+ createComponent();
+
+ expect(wrapper.findComponent(GlBadge).attributes('size')).toBe('sm');
+ expect(wrapper.findComponent(GlBadge).element.textContent).toBe('3');
});
});
diff --git a/spec/frontend/profile/components/overview_tab_spec.js b/spec/frontend/profile/components/overview_tab_spec.js
index eb27515bca3..d4cb1dfd15d 100644
--- a/spec/frontend/profile/components/overview_tab_spec.js
+++ b/spec/frontend/profile/components/overview_tab_spec.js
@@ -3,6 +3,7 @@ import { GlTab } from '@gitlab/ui';
import { s__ } from '~/locale';
import OverviewTab from '~/profile/components/overview_tab.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import ActivityCalendar from '~/profile/components/activity_calendar.vue';
describe('OverviewTab', () => {
let wrapper;
@@ -16,4 +17,10 @@ describe('OverviewTab', () => {
expect(wrapper.findComponent(GlTab).attributes('title')).toBe(s__('UserProfile|Overview'));
});
+
+ it('renders `ActivityCalendar` component', () => {
+ createComponent();
+
+ expect(wrapper.findComponent(ActivityCalendar).exists()).toBe(true);
+ });
});
diff --git a/spec/frontend/profile/components/user_achievements_spec.js b/spec/frontend/profile/components/user_achievements_spec.js
new file mode 100644
index 00000000000..5b584eff362
--- /dev/null
+++ b/spec/frontend/profile/components/user_achievements_spec.js
@@ -0,0 +1,102 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import getUserAchievementsEmptyResponse from 'test_fixtures/graphql/get_user_achievements_empty_response.json';
+import getUserAchievementsLongResponse from 'test_fixtures/graphql/get_user_achievements_long_response.json';
+import getUserAchievementsResponse from 'test_fixtures/graphql/get_user_achievements_with_avatar_and_description_response.json';
+import getUserAchievementsNoAvatarResponse from 'test_fixtures/graphql/get_user_achievements_without_avatar_or_description_response.json';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import UserAchievements from '~/profile/components/user_achievements.vue';
+import getUserAchievements from '~/profile/components//graphql/get_user_achievements.query.graphql';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+
+const USER_ID = 123;
+const ROOT_URL = 'https://gitlab.com/';
+const PLACEHOLDER_URL = 'https://gitlab.com/assets/gitlab_logo.png';
+const userAchievement1 = getUserAchievementsResponse.data.user.userAchievements.nodes[0];
+
+Vue.use(VueApollo);
+
+describe('UserAchievements', () => {
+ let wrapper;
+
+ const getUserAchievementsQueryHandler = jest.fn().mockResolvedValue(getUserAchievementsResponse);
+ const achievement = () => wrapper.findByTestId('user-achievement');
+
+ const createComponent = ({ queryHandler = getUserAchievementsQueryHandler } = {}) => {
+ const fakeApollo = createMockApollo([[getUserAchievements, queryHandler]]);
+
+ wrapper = mountExtended(UserAchievements, {
+ apolloProvider: fakeApollo,
+ provide: {
+ rootUrl: ROOT_URL,
+ userId: USER_ID,
+ },
+ });
+ };
+
+ it('renders no achievements on reject', async () => {
+ createComponent({ queryHandler: jest.fn().mockRejectedValue('ERROR') });
+
+ await waitForPromises();
+
+ expect(wrapper.findAllByTestId('user-achievement').length).toBe(0);
+ });
+
+ it('renders no achievements when none are present', async () => {
+ createComponent({
+ queryHandler: jest.fn().mockResolvedValue(getUserAchievementsEmptyResponse),
+ });
+
+ await waitForPromises();
+
+ expect(wrapper.findAllByTestId('user-achievement').length).toBe(0);
+ });
+
+ it('only renders 3 achievements when more are present', async () => {
+ createComponent({ queryHandler: jest.fn().mockResolvedValue(getUserAchievementsLongResponse) });
+
+ await waitForPromises();
+
+ expect(wrapper.findAllByTestId('user-achievement').length).toBe(3);
+ });
+
+ it('renders achievement correctly', async () => {
+ createComponent();
+
+ await waitForPromises();
+
+ expect(achievement().text()).toContain(userAchievement1.achievement.name);
+ expect(achievement().text()).toContain(
+ `Awarded ${timeagoMixin.methods.timeFormatted(userAchievement1.createdAt)} by`,
+ );
+ expect(achievement().text()).toContain(userAchievement1.achievement.namespace.fullPath);
+ expect(achievement().text()).toContain(userAchievement1.achievement.description);
+ expect(achievement().find('img').attributes('src')).toBe(
+ userAchievement1.achievement.avatarUrl,
+ );
+ });
+
+ it('renders a placeholder when no avatar is present', async () => {
+ gon.gitlab_logo = PLACEHOLDER_URL;
+ createComponent({
+ queryHandler: jest.fn().mockResolvedValue(getUserAchievementsNoAvatarResponse),
+ });
+
+ await waitForPromises();
+
+ expect(achievement().find('img').attributes('src')).toBe(PLACEHOLDER_URL);
+ });
+
+ it('does not render a description when none is present', async () => {
+ gon.gitlab_logo = PLACEHOLDER_URL;
+ createComponent({
+ queryHandler: jest.fn().mockResolvedValue(getUserAchievementsNoAvatarResponse),
+ });
+
+ await waitForPromises();
+
+ expect(wrapper.findAllByTestId('achievement-description').length).toBe(0);
+ });
+});
diff --git a/spec/frontend/profile/mock_data.js b/spec/frontend/profile/mock_data.js
new file mode 100644
index 00000000000..7106ea84619
--- /dev/null
+++ b/spec/frontend/profile/mock_data.js
@@ -0,0 +1,22 @@
+export const userCalendarResponse = {
+ '2022-11-18': 13,
+ '2022-11-19': 21,
+ '2022-11-20': 14,
+ '2022-11-21': 15,
+ '2022-11-22': 20,
+ '2022-11-23': 21,
+ '2022-11-24': 15,
+ '2022-11-25': 14,
+ '2022-11-26': 16,
+ '2022-11-27': 13,
+ '2022-11-28': 4,
+ '2022-11-29': 1,
+ '2022-11-30': 1,
+ '2022-12-13': 1,
+ '2023-01-10': 3,
+ '2023-01-11': 2,
+ '2023-01-20': 1,
+ '2023-02-02': 1,
+ '2023-02-06': 2,
+ '2023-02-07': 2,
+};
diff --git a/spec/frontend/profile/preferences/components/diffs_colors_preview_spec.js b/spec/frontend/profile/preferences/components/diffs_colors_preview_spec.js
index e60602ab336..e69bfad765a 100644
--- a/spec/frontend/profile/preferences/components/diffs_colors_preview_spec.js
+++ b/spec/frontend/profile/preferences/components/diffs_colors_preview_spec.js
@@ -12,11 +12,6 @@ describe('DiffsColorsPreview component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders diff colors preview', () => {
expect(wrapper.element).toMatchSnapshot();
});
diff --git a/spec/frontend/profile/preferences/components/diffs_colors_spec.js b/spec/frontend/profile/preferences/components/diffs_colors_spec.js
index 02f501a0b06..e80851d0629 100644
--- a/spec/frontend/profile/preferences/components/diffs_colors_spec.js
+++ b/spec/frontend/profile/preferences/components/diffs_colors_spec.js
@@ -29,11 +29,6 @@ describe('DiffsColors component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('mounts', () => {
createComponent();
diff --git a/spec/frontend/profile/preferences/components/integration_view_spec.js b/spec/frontend/profile/preferences/components/integration_view_spec.js
index f650bee7fda..b809f2f4aed 100644
--- a/spec/frontend/profile/preferences/components/integration_view_spec.js
+++ b/spec/frontend/profile/preferences/components/integration_view_spec.js
@@ -38,11 +38,6 @@ describe('IntegrationView component', () => {
const findHiddenField = () =>
wrapper.findByTestId('profile-preferences-integration-hidden-field');
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should render the form group legend correctly', () => {
wrapper = createComponent();
diff --git a/spec/frontend/profile/preferences/components/profile_preferences_spec.js b/spec/frontend/profile/preferences/components/profile_preferences_spec.js
index 91cd868daac..21167dccda9 100644
--- a/spec/frontend/profile/preferences/components/profile_preferences_spec.js
+++ b/spec/frontend/profile/preferences/components/profile_preferences_spec.js
@@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import { createAlert, VARIANT_DANGER, VARIANT_INFO } from '~/flash';
+import { createAlert, VARIANT_DANGER, VARIANT_INFO } from '~/alert';
import IntegrationView from '~/profile/preferences/components/integration_view.vue';
import ProfilePreferences from '~/profile/preferences/components/profile_preferences.vue';
import { i18n } from '~/profile/preferences/constants';
@@ -17,7 +17,7 @@ import {
lightModeThemeId2,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
const expectedUrl = '/foo';
useMockLocationHelper();
@@ -83,11 +83,6 @@ describe('ProfilePreferences component', () => {
document.body.classList.add('content-wrapper');
}
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should not render Integrations section', () => {
wrapper = createComponent();
const views = wrapper.findAllComponents(IntegrationView);
diff --git a/spec/frontend/profile/utils_spec.js b/spec/frontend/profile/utils_spec.js
new file mode 100644
index 00000000000..43537afe169
--- /dev/null
+++ b/spec/frontend/profile/utils_spec.js
@@ -0,0 +1,15 @@
+import { getVisibleCalendarPeriod } from '~/profile/utils';
+import { CALENDAR_PERIOD_12_MONTHS, CALENDAR_PERIOD_6_MONTHS } from '~/profile/constants';
+
+describe('getVisibleCalendarPeriod', () => {
+ it.each`
+ width | expected
+ ${1000} | ${CALENDAR_PERIOD_12_MONTHS}
+ ${900} | ${CALENDAR_PERIOD_6_MONTHS}
+ `('returns $expected when container width is $width', ({ width, expected }) => {
+ const container = document.createElement('div');
+ jest.spyOn(container, 'getBoundingClientRect').mockReturnValueOnce({ width });
+
+ expect(getVisibleCalendarPeriod(container)).toBe(expected);
+ });
+});
diff --git a/spec/frontend/projects/clusters_deprecation_slert/components/clusters_deprecation_alert_spec.js b/spec/frontend/projects/clusters_deprecation_slert/components/clusters_deprecation_alert_spec.js
index d230b96ad82..68ea3a4dc4d 100644
--- a/spec/frontend/projects/clusters_deprecation_slert/components/clusters_deprecation_alert_spec.js
+++ b/spec/frontend/projects/clusters_deprecation_slert/components/clusters_deprecation_alert_spec.js
@@ -26,10 +26,6 @@ describe('ClustersDeprecationAlert', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
it('should render a non-dismissible warning alert', () => {
expect(findAlert().props()).toMatchObject({
diff --git a/spec/frontend/projects/commit/components/branches_dropdown_spec.js b/spec/frontend/projects/commit/components/branches_dropdown_spec.js
index 6aa5a9a5a3a..0e68bd21cd4 100644
--- a/spec/frontend/projects/commit/components/branches_dropdown_spec.js
+++ b/spec/frontend/projects/commit/components/branches_dropdown_spec.js
@@ -12,7 +12,7 @@ describe('BranchesDropdown', () => {
let store;
const spyFetchBranches = jest.fn();
- const createComponent = (props, state = { isFetching: false }) => {
+ const createComponent = (props, state = { isFetching: false, branch: '_main_' }) => {
store = new Vuex.Store({
getters: {
joinedBranches: () => ['_main_', '_branch_1_', '_branch_2_'],
@@ -41,8 +41,6 @@ describe('BranchesDropdown', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
spyFetchBranches.mockReset();
});
diff --git a/spec/frontend/projects/commit/components/form_modal_spec.js b/spec/frontend/projects/commit/components/form_modal_spec.js
index c59cf700e0d..84cb30953c3 100644
--- a/spec/frontend/projects/commit/components/form_modal_spec.js
+++ b/spec/frontend/projects/commit/components/form_modal_spec.js
@@ -55,7 +55,6 @@ describe('CommitFormModal', () => {
});
afterEach(() => {
- wrapper.destroy();
axiosMock.restore();
});
@@ -166,7 +165,7 @@ describe('CommitFormModal', () => {
it('Changes the target_project_id input value', async () => {
createComponent(shallowMount, {}, {}, { isCherryPick: true });
- findProjectsDropdown().vm.$emit('selectProject', '_changed_project_value_');
+ findProjectsDropdown().vm.$emit('input', '_changed_project_value_');
await nextTick();
diff --git a/spec/frontend/projects/commit/components/projects_dropdown_spec.js b/spec/frontend/projects/commit/components/projects_dropdown_spec.js
index 0e213ff388a..baf2ea2656f 100644
--- a/spec/frontend/projects/commit/components/projects_dropdown_spec.js
+++ b/spec/frontend/projects/commit/components/projects_dropdown_spec.js
@@ -1,6 +1,6 @@
import { GlCollapsibleListbox } 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 ProjectsDropdown from '~/projects/commit/components/projects_dropdown.vue';
@@ -38,7 +38,6 @@ describe('ProjectsDropdown', () => {
const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox);
afterEach(() => {
- wrapper.destroy();
spyFetchProjects.mockReset();
});
@@ -48,20 +47,24 @@ describe('ProjectsDropdown', () => {
});
describe('Custom events', () => {
- it('should emit selectProject if a project is clicked', () => {
+ it('should emit input if a project is clicked', () => {
findDropdown().vm.$emit('select', '1');
- expect(wrapper.emitted('selectProject')).toEqual([['1']]);
+ expect(wrapper.emitted('input')).toEqual([['1']]);
});
});
});
describe('Case insensitive for search term', () => {
beforeEach(() => {
- createComponent('_PrOjEcT_1_');
+ createComponent('_PrOjEcT_1_', { targetProjectId: '1' });
});
- it('renders only the project searched for', () => {
+ it('renders only the project searched for', async () => {
+ findDropdown().vm.$emit('search', '_project_1_');
+
+ await nextTick();
+
expect(findDropdown().props('items')).toEqual([{ text: '_project_1_', value: '1' }]);
});
});
diff --git a/spec/frontend/projects/commit/store/actions_spec.js b/spec/frontend/projects/commit/store/actions_spec.js
index 008710984b9..d48f9fd6fc0 100644
--- a/spec/frontend/projects/commit/store/actions_spec.js
+++ b/spec/frontend/projects/commit/store/actions_spec.js
@@ -1,6 +1,6 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { PROJECT_BRANCHES_ERROR } from '~/projects/commit/constants';
import * as actions from '~/projects/commit/store/actions';
@@ -8,7 +8,7 @@ import * as types from '~/projects/commit/store/mutation_types';
import getInitialState from '~/projects/commit/store/state';
import mockData from '../mock_data';
-jest.mock('~/flash.js');
+jest.mock('~/alert');
describe('Commit form modal store actions', () => {
let axiosMock;
@@ -63,7 +63,7 @@ describe('Commit form modal store actions', () => {
);
});
- it('should show flash error and set error in state on fetchBranches failure', async () => {
+ it('should show alert error and set error in state on fetchBranches failure', async () => {
jest.spyOn(axios, 'get').mockRejectedValue();
await testAction(actions.fetchBranches, {}, state, [], [{ type: 'requestBranches' }]);
diff --git a/spec/frontend/projects/commits/components/author_select_spec.js b/spec/frontend/projects/commits/components/author_select_spec.js
index 907e0e226b6..ff1d860fd53 100644
--- a/spec/frontend/projects/commits/components/author_select_spec.js
+++ b/spec/frontend/projects/commits/components/author_select_spec.js
@@ -54,7 +54,6 @@ describe('Author Select', () => {
});
afterEach(() => {
- wrapper.destroy();
resetHTMLFixture();
});
diff --git a/spec/frontend/projects/commits/store/actions_spec.js b/spec/frontend/projects/commits/store/actions_spec.js
index bae9c48fc1e..f5184e59420 100644
--- a/spec/frontend/projects/commits/store/actions_spec.js
+++ b/spec/frontend/projects/commits/store/actions_spec.js
@@ -1,13 +1,13 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import actions from '~/projects/commits/store/actions';
import * as types from '~/projects/commits/store/mutation_types';
import createState from '~/projects/commits/store/state';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Project commits actions', () => {
let state;
@@ -34,8 +34,8 @@ describe('Project commits actions', () => {
]));
});
- describe('shows a flash message when there is an error', () => {
- it('creates a flash', () => {
+ describe('shows an alert message when there is an error', () => {
+ it('creates an alert', () => {
const mockDispatchContext = { dispatch: () => {}, commit: () => {}, state };
actions.receiveAuthorsError(mockDispatchContext);
diff --git a/spec/frontend/projects/compare/components/app_spec.js b/spec/frontend/projects/compare/components/app_spec.js
index 9b052a17caa..ee96f46ea0c 100644
--- a/spec/frontend/projects/compare/components/app_spec.js
+++ b/spec/frontend/projects/compare/components/app_spec.js
@@ -21,11 +21,6 @@ describe('CompareApp component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
beforeEach(() => {
createComponent();
});
diff --git a/spec/frontend/projects/compare/components/repo_dropdown_spec.js b/spec/frontend/projects/compare/components/repo_dropdown_spec.js
index 21cca857c6a..0b1085470b8 100644
--- a/spec/frontend/projects/compare/components/repo_dropdown_spec.js
+++ b/spec/frontend/projects/compare/components/repo_dropdown_spec.js
@@ -16,11 +16,6 @@ describe('RepoDropdown component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findGlDropdown = () => wrapper.findComponent(GlDropdown);
const findHiddenInput = () => wrapper.find('input[type="hidden"]');
diff --git a/spec/frontend/projects/compare/components/revision_card_spec.js b/spec/frontend/projects/compare/components/revision_card_spec.js
index b23bd91ceda..3c9c61c8903 100644
--- a/spec/frontend/projects/compare/components/revision_card_spec.js
+++ b/spec/frontend/projects/compare/components/revision_card_spec.js
@@ -16,11 +16,6 @@ describe('RepoDropdown component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
beforeEach(() => {
createComponent();
});
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 53763bd7d8f..645d0483a5f 100644
--- a/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js
+++ b/spec/frontend/projects/compare/components/revision_dropdown_legacy_spec.js
@@ -1,8 +1,8 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
-import { nextTick } from 'vue';
-import { createAlert } from '~/flash';
+import waitForPromises from 'helpers/wait_for_promises';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import RevisionDropdown from '~/projects/compare/components/revision_dropdown_legacy.vue';
@@ -14,7 +14,7 @@ const defaultProps = {
paramsBranch: 'main',
};
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('RevisionDropdown component', () => {
let wrapper;
@@ -35,11 +35,14 @@ describe('RevisionDropdown component', () => {
});
afterEach(() => {
- wrapper.destroy();
axiosMock.restore();
});
const findGlDropdown = () => wrapper.findComponent(GlDropdown);
+ const findBranchesDropdownItem = () =>
+ wrapper.findAllComponents('[data-testid="branches-dropdown-item"]');
+ const findTagsDropdownItem = () =>
+ wrapper.findAllComponents('[data-testid="tags-dropdown-item"]');
it('sets hidden input', () => {
expect(wrapper.find('input[type="hidden"]').attributes('value')).toBe(
@@ -58,10 +61,21 @@ describe('RevisionDropdown component', () => {
createComponent();
- await axios.waitForAll();
+ expect(findBranchesDropdownItem()).toHaveLength(0);
+ expect(findTagsDropdownItem()).toHaveLength(0);
- expect(wrapper.vm.branches).toEqual(Branches);
- expect(wrapper.vm.tags).toEqual(Tags);
+ await waitForPromises();
+
+ Branches.forEach((branch, index) => {
+ expect(findBranchesDropdownItem().at(index).text()).toBe(branch);
+ });
+
+ Tags.forEach((tag, index) => {
+ expect(findTagsDropdownItem().at(index).text()).toBe(tag);
+ });
+
+ expect(findBranchesDropdownItem()).toHaveLength(Branches.length);
+ expect(findTagsDropdownItem()).toHaveLength(Tags.length);
});
it('sets branches and tags to be an empty array when no tags or branches are given', async () => {
@@ -70,16 +84,17 @@ describe('RevisionDropdown component', () => {
Tags: undefined,
});
- await axios.waitForAll();
+ await waitForPromises();
- expect(wrapper.vm.branches).toEqual([]);
- expect(wrapper.vm.tags).toEqual([]);
+ expect(findBranchesDropdownItem()).toHaveLength(0);
+ expect(findTagsDropdownItem()).toHaveLength(0);
});
- it('shows flash message on error', async () => {
+ it('shows alert message on error', async () => {
axiosMock.onGet('some/invalid/path').replyOnce(HTTP_STATUS_NOT_FOUND);
- await wrapper.vm.fetchBranchesAndTags();
+ await waitForPromises();
+
expect(createAlert).toHaveBeenCalled();
});
@@ -102,17 +117,19 @@ describe('RevisionDropdown component', () => {
it('emits a "selectRevision" event when a revision is selected', async () => {
const findGlDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findFirstGlDropdownItem = () => findGlDropdownItems().at(0);
+ const branchName = 'some-branch';
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ branches: ['some-branch'] });
+ axiosMock.onGet(defaultProps.refsProjectPath).replyOnce(HTTP_STATUS_OK, {
+ Branches: [branchName],
+ });
- await nextTick();
+ createComponent();
+ await waitForPromises();
findFirstGlDropdownItem().vm.$emit('click');
expect(wrapper.emitted()).toEqual({
- selectRevision: [[{ direction: 'from', revision: 'some-branch' }]],
+ selectRevision: [[{ direction: 'from', revision: branchName }]],
});
});
});
diff --git a/spec/frontend/projects/compare/components/revision_dropdown_spec.js b/spec/frontend/projects/compare/components/revision_dropdown_spec.js
index db4a1158996..3a256682549 100644
--- a/spec/frontend/projects/compare/components/revision_dropdown_spec.js
+++ b/spec/frontend/projects/compare/components/revision_dropdown_spec.js
@@ -2,13 +2,14 @@ import { GlDropdown, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
-import { createAlert } from '~/flash';
+import waitForPromises from 'helpers/wait_for_promises';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import RevisionDropdown from '~/projects/compare/components/revision_dropdown.vue';
import { revisionDropdownDefaultProps as defaultProps } from './mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('RevisionDropdown component', () => {
let wrapper;
@@ -32,12 +33,15 @@ describe('RevisionDropdown component', () => {
});
afterEach(() => {
- wrapper.destroy();
axiosMock.restore();
});
const findGlDropdown = () => wrapper.findComponent(GlDropdown);
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+ const findBranchesDropdownItem = () =>
+ wrapper.findAllComponents('[data-testid="branches-dropdown-item"]');
+ const findTagsDropdownItem = () =>
+ wrapper.findAllComponents('[data-testid="tags-dropdown-item"]');
it('sets hidden input', () => {
createComponent();
@@ -57,17 +61,29 @@ describe('RevisionDropdown component', () => {
createComponent();
- await axios.waitForAll();
- expect(wrapper.vm.branches).toEqual(Branches);
- expect(wrapper.vm.tags).toEqual(Tags);
+ expect(findBranchesDropdownItem()).toHaveLength(0);
+ expect(findTagsDropdownItem()).toHaveLength(0);
+
+ await waitForPromises();
+
+ expect(findBranchesDropdownItem()).toHaveLength(Branches.length);
+ expect(findTagsDropdownItem()).toHaveLength(Tags.length);
+
+ Branches.forEach((branch, index) => {
+ expect(findBranchesDropdownItem().at(index).text()).toBe(branch);
+ });
+
+ Tags.forEach((tag, index) => {
+ expect(findTagsDropdownItem().at(index).text()).toBe(tag);
+ });
});
- it('shows flash message on error', async () => {
+ it('shows alert message on error', async () => {
axiosMock.onGet('some/invalid/path').replyOnce(HTTP_STATUS_NOT_FOUND);
createComponent();
+ await waitForPromises();
- await wrapper.vm.fetchBranchesAndTags();
expect(createAlert).toHaveBeenCalled();
});
@@ -83,17 +99,17 @@ describe('RevisionDropdown component', () => {
refsProjectPath: newRefsProjectPath,
});
- await axios.waitForAll();
+ await waitForPromises();
expect(axios.get).toHaveBeenLastCalledWith(newRefsProjectPath);
});
describe('search', () => {
- it('shows flash message on error', async () => {
+ it('shows alert message on error', async () => {
axiosMock.onGet('some/invalid/path').replyOnce(HTTP_STATUS_NOT_FOUND);
createComponent();
+ await waitForPromises();
- await wrapper.vm.searchBranchesAndTags();
expect(createAlert).toHaveBeenCalled();
});
@@ -108,7 +124,7 @@ describe('RevisionDropdown component', () => {
const mockSearchTerm = 'foobar';
createComponent();
findSearchBox().vm.$emit('input', mockSearchTerm);
- await axios.waitForAll();
+ await waitForPromises();
expect(axios.get).toHaveBeenCalledWith(
defaultProps.refsProjectPath,
@@ -141,8 +157,14 @@ describe('RevisionDropdown component', () => {
});
it('emits `selectRevision` event when another revision is selected', async () => {
+ jest.spyOn(axios, 'get').mockResolvedValue({
+ data: {
+ Branches: ['some-branch'],
+ Tags: [],
+ },
+ });
+
createComponent();
- wrapper.vm.branches = ['some-branch'];
await nextTick();
findGlDropdown().findAllComponents(GlDropdownItem).at(0).vm.$emit('click');
diff --git a/spec/frontend/projects/components/project_delete_button_spec.js b/spec/frontend/projects/components/project_delete_button_spec.js
index 49e3218e5bc..bae76e7eeb6 100644
--- a/spec/frontend/projects/components/project_delete_button_spec.js
+++ b/spec/frontend/projects/components/project_delete_button_spec.js
@@ -33,11 +33,6 @@ describe('Project remove modal', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('initialized', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/projects/components/shared/delete_button_spec.js b/spec/frontend/projects/components/shared/delete_button_spec.js
index 097b18025a3..364a29d0e41 100644
--- a/spec/frontend/projects/components/shared/delete_button_spec.js
+++ b/spec/frontend/projects/components/shared/delete_button_spec.js
@@ -45,11 +45,6 @@ describe('Project remove modal', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('intialized', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/projects/details/upload_button_spec.js b/spec/frontend/projects/details/upload_button_spec.js
index 50638755260..e9b11ce544a 100644
--- a/spec/frontend/projects/details/upload_button_spec.js
+++ b/spec/frontend/projects/details/upload_button_spec.js
@@ -27,10 +27,6 @@ describe('UploadButton', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays an upload button', () => {
expect(wrapper.findComponent(GlButton).exists()).toBe(true);
});
diff --git a/spec/frontend/projects/new/components/app_spec.js b/spec/frontend/projects/new/components/app_spec.js
index f6edbab3cca..5b2dc25077e 100644
--- a/spec/frontend/projects/new/components/app_spec.js
+++ b/spec/frontend/projects/new/components/app_spec.js
@@ -6,12 +6,12 @@ describe('Experimental new project creation app', () => {
let wrapper;
const createComponent = (propsData) => {
- wrapper = shallowMount(App, { propsData });
+ wrapper = shallowMount(App, {
+ propsData: { projectsUrl: '/dashboard/projects', ...propsData },
+ });
};
- afterEach(() => {
- wrapper.destroy();
- });
+ const findNewNamespacePage = () => wrapper.findComponent(NewNamespacePage);
it('passes custom new project guideline text to underlying component', () => {
const DEMO_GUIDELINES = 'Demo guidelines';
@@ -34,11 +34,28 @@ describe('Experimental new project creation app', () => {
expect(
Boolean(
- wrapper
- .findComponent(NewNamespacePage)
+ findNewNamespacePage()
.props()
.panels.find((p) => p.name === 'cicd_for_external_repo'),
),
).toBe(isCiCdAvailable);
});
+
+ it('creates correct breadcrumbs for top-level projects', () => {
+ createComponent();
+
+ expect(findNewNamespacePage().props('initialBreadcrumbs')).toEqual([
+ { href: '/dashboard/projects', text: 'Projects' },
+ { href: '#', text: 'New project' },
+ ]);
+ });
+
+ it('creates correct breadcrumbs for projects within groups', () => {
+ createComponent({ parentGroupUrl: '/parent-group', parentGroupName: 'Parent Group' });
+
+ expect(findNewNamespacePage().props('initialBreadcrumbs')).toEqual([
+ { href: '/parent-group', text: 'Parent Group' },
+ { href: '#', text: 'New project' },
+ ]);
+ });
});
diff --git a/spec/frontend/projects/new/components/deployment_target_select_spec.js b/spec/frontend/projects/new/components/deployment_target_select_spec.js
index f3b22d4a1b9..bec738f7765 100644
--- a/spec/frontend/projects/new/components/deployment_target_select_spec.js
+++ b/spec/frontend/projects/new/components/deployment_target_select_spec.js
@@ -47,7 +47,6 @@ describe('Deployment target select', () => {
});
afterEach(() => {
- wrapper.destroy();
resetHTMLFixture();
});
diff --git a/spec/frontend/projects/new/components/new_project_push_tip_popover_spec.js b/spec/frontend/projects/new/components/new_project_push_tip_popover_spec.js
index 16b4493c622..1a43dcb682b 100644
--- a/spec/frontend/projects/new/components/new_project_push_tip_popover_spec.js
+++ b/spec/frontend/projects/new/components/new_project_push_tip_popover_spec.js
@@ -37,7 +37,6 @@ describe('New project push tip popover', () => {
});
afterEach(() => {
- wrapper.destroy();
resetHTMLFixture();
});
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 67532cea61e..fa720f4487c 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
@@ -3,8 +3,8 @@ import {
GlDropdown,
GlDropdownItem,
GlDropdownSectionHeader,
- GlSearchBoxByType,
GlTruncate,
+ GlSearchBoxByType,
} from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
@@ -12,6 +12,7 @@ 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 { stubComponent } from 'helpers/stub_component';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import eventHub from '~/projects/new/event_hub';
import NewProjectUrlSelect from '~/projects/new/components/new_project_url_select.vue';
@@ -68,6 +69,7 @@ describe('NewProjectUrlSelect component', () => {
};
let mockQueryResponse;
+ let focusInputSpy;
const mountComponent = ({
search = '',
@@ -78,6 +80,7 @@ describe('NewProjectUrlSelect component', () => {
mockQueryResponse = jest.fn().mockResolvedValue({ data: queryResponse });
const requestHandlers = [[searchQuery, mockQueryResponse]];
const apolloProvider = createMockApollo(requestHandlers);
+ focusInputSpy = jest.fn();
return mountFn(NewProjectUrlSelect, {
apolloProvider,
@@ -87,13 +90,17 @@ describe('NewProjectUrlSelect component', () => {
search,
};
},
+ stubs: {
+ GlSearchBoxByType: stubComponent(GlSearchBoxByType, {
+ methods: { focusInput: focusInputSpy },
+ }),
+ },
});
};
const findButtonLabel = () => wrapper.findComponent(GlButton);
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findSelectedPath = () => wrapper.findComponent(GlTruncate);
- const findInput = () => wrapper.findComponent(GlSearchBoxByType);
const findHiddenNamespaceInput = () => wrapper.find(`[name="${defaultProvide.inputName}`);
const findHiddenSelectedNamespaceInput = () =>
@@ -111,10 +118,6 @@ describe('NewProjectUrlSelect component', () => {
await waitForPromises();
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the root url as a label', () => {
wrapper = mountComponent();
@@ -177,13 +180,11 @@ describe('NewProjectUrlSelect component', () => {
});
it('focuses on the input when the dropdown is opened', async () => {
- wrapper = mountComponent({ mountFn: mount });
-
- const spy = jest.spyOn(findInput().vm, 'focusInput');
+ wrapper = mountComponent();
await showDropdown();
- expect(spy).toHaveBeenCalledTimes(1);
+ expect(focusInputSpy).toHaveBeenCalledTimes(1);
});
it('renders expected dropdown items', async () => {
diff --git a/spec/frontend/projects/pipelines/charts/components/__snapshots__/ci_cd_analytics_area_chart_spec.js.snap b/spec/frontend/projects/pipelines/charts/components/__snapshots__/ci_cd_analytics_area_chart_spec.js.snap
index fc51825f15b..1545c52d7cb 100644
--- a/spec/frontend/projects/pipelines/charts/components/__snapshots__/ci_cd_analytics_area_chart_spec.js.snap
+++ b/spec/frontend/projects/pipelines/charts/components/__snapshots__/ci_cd_analytics_area_chart_spec.js.snap
@@ -8,20 +8,19 @@ exports[`CiCdAnalyticsAreaChart matches the snapshot 1`] = `
Some title
</p>
- <div>
- <glareachart-stub
- annotations=""
- data="[object Object],[object Object]"
- height="300"
- legendaveragetext="Avg"
- legendcurrenttext="Current"
- legendlayout="inline"
- legendmaxtext="Max"
- legendmintext="Min"
- option="[object Object]"
- thresholds=""
- width="0"
- />
- </div>
+ <glareachart-stub
+ annotations=""
+ data="[object Object],[object Object]"
+ height="300"
+ legendaveragetext="Avg"
+ legendcurrenttext="Current"
+ legendlayout="inline"
+ legendmaxtext="Max"
+ legendmintext="Min"
+ option="[object Object]"
+ responsive=""
+ thresholds=""
+ width="auto"
+ />
</div>
`;
diff --git a/spec/frontend/projects/pipelines/charts/components/app_spec.js b/spec/frontend/projects/pipelines/charts/components/app_spec.js
index d8876349c5e..94f421239da 100644
--- a/spec/frontend/projects/pipelines/charts/components/app_spec.js
+++ b/spec/frontend/projects/pipelines/charts/components/app_spec.js
@@ -49,10 +49,6 @@ describe('ProjectsPipelinesChartsApp', () => {
);
}
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGlTabs = () => wrapper.findComponent(GlTabs);
const findAllGlTabs = () => wrapper.findAllComponents(GlTab);
const findGlTabAtIndex = (index) => findAllGlTabs().at(index);
diff --git a/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_area_chart_spec.js b/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_area_chart_spec.js
index 2b523467379..5fc121b5c9f 100644
--- a/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_area_chart_spec.js
+++ b/spec/frontend/projects/pipelines/charts/components/ci_cd_analytics_area_chart_spec.js
@@ -28,11 +28,6 @@ describe('CiCdAnalyticsAreaChart', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
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 8fb59f38ee1..ab2a12219e5 100644
--- a/spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js
+++ b/spec/frontend/projects/pipelines/charts/components/pipeline_charts_spec.js
@@ -37,11 +37,6 @@ describe('~/projects/pipelines/charts/components/pipeline_charts.vue', () => {
await waitForPromises();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('overall statistics', () => {
it('displays the statistics list', () => {
const list = wrapper.findComponent(StatisticsList);
diff --git a/spec/frontend/projects/pipelines/charts/components/statistics_list_spec.js b/spec/frontend/projects/pipelines/charts/components/statistics_list_spec.js
index 57a864cb2c4..24dbc628ce6 100644
--- a/spec/frontend/projects/pipelines/charts/components/statistics_list_spec.js
+++ b/spec/frontend/projects/pipelines/charts/components/statistics_list_spec.js
@@ -21,10 +21,6 @@ describe('StatisticsList', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays the counts data with labels', () => {
expect(wrapper.element).toMatchSnapshot();
});
diff --git a/spec/frontend/projects/prune_unreachable_objects_button_spec.js b/spec/frontend/projects/prune_unreachable_objects_button_spec.js
index b345f264ca7..012b19ea3d3 100644
--- a/spec/frontend/projects/prune_unreachable_objects_button_spec.js
+++ b/spec/frontend/projects/prune_unreachable_objects_button_spec.js
@@ -22,16 +22,11 @@ describe('Project remove modal', () => {
wrapper = shallowMountExtended(PruneObjectsButton, {
propsData: defaultProps,
directives: {
- GlModal: createMockDirective(),
+ GlModal: createMockDirective('gl-modal'),
},
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('intialized', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/projects/settings/branch_rules/components/edit/branch_dropdown_spec.js b/spec/frontend/projects/settings/branch_rules/components/edit/branch_dropdown_spec.js
index 11f219c1f90..6d3317a5f78 100644
--- a/spec/frontend/projects/settings/branch_rules/components/edit/branch_dropdown_spec.js
+++ b/spec/frontend/projects/settings/branch_rules/components/edit/branch_dropdown_spec.js
@@ -8,10 +8,10 @@ import BranchDropdown, {
import createMockApollo from 'helpers/mock_apollo_helper';
import branchesQuery from '~/projects/settings/branch_rules/queries/branches.query.graphql';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Branch dropdown', () => {
let wrapper;
@@ -46,10 +46,6 @@ describe('Branch dropdown', () => {
beforeEach(() => createComponent());
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a GlDropdown component with the correct props', () => {
expect(findGlDropdown().props()).toMatchObject({ text: value });
});
diff --git a/spec/frontend/projects/settings/branch_rules/components/edit/index_spec.js b/spec/frontend/projects/settings/branch_rules/components/edit/index_spec.js
index 21e63fdb24d..e9982872e03 100644
--- a/spec/frontend/projects/settings/branch_rules/components/edit/index_spec.js
+++ b/spec/frontend/projects/settings/branch_rules/components/edit/index_spec.js
@@ -24,10 +24,6 @@ describe('Edit branch rule', () => {
beforeEach(() => createComponent());
- afterEach(() => {
- wrapper.destroy();
- });
-
it('gets the branch param from url', () => {
expect(getParameterByName).toHaveBeenCalledWith('branch');
});
diff --git a/spec/frontend/projects/settings/branch_rules/components/edit/protections/index_spec.js b/spec/frontend/projects/settings/branch_rules/components/edit/protections/index_spec.js
index ee90ff8318f..14edaf31a1f 100644
--- a/spec/frontend/projects/settings/branch_rules/components/edit/protections/index_spec.js
+++ b/spec/frontend/projects/settings/branch_rules/components/edit/protections/index_spec.js
@@ -26,10 +26,6 @@ describe('Branch Protections', () => {
beforeEach(() => createComponent());
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a heading', () => {
expect(findHeading().text()).toBe(i18n.protections);
});
diff --git a/spec/frontend/projects/settings/branch_rules/components/edit/protections/merge_protections_spec.js b/spec/frontend/projects/settings/branch_rules/components/edit/protections/merge_protections_spec.js
index b5fdc46d600..ca561ef87ec 100644
--- a/spec/frontend/projects/settings/branch_rules/components/edit/protections/merge_protections_spec.js
+++ b/spec/frontend/projects/settings/branch_rules/components/edit/protections/merge_protections_spec.js
@@ -24,10 +24,6 @@ describe('Merge Protections', () => {
beforeEach(() => createComponent());
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a form group with the correct label', () => {
expect(findFormGroup().text()).toContain(i18n.allowedToMerge);
});
diff --git a/spec/frontend/projects/settings/branch_rules/components/edit/protections/push_protections_spec.js b/spec/frontend/projects/settings/branch_rules/components/edit/protections/push_protections_spec.js
index 60bb7a51dcb..82998640f17 100644
--- a/spec/frontend/projects/settings/branch_rules/components/edit/protections/push_protections_spec.js
+++ b/spec/frontend/projects/settings/branch_rules/components/edit/protections/push_protections_spec.js
@@ -24,10 +24,6 @@ describe('Push Protections', () => {
beforeEach(() => createComponent());
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a form group with the correct label', () => {
expect(findFormGroup().attributes('label')).toBe(i18n.allowedToPush);
});
diff --git a/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js
index 714e0df596e..077995ab6e4 100644
--- a/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js
+++ b/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js
@@ -9,6 +9,10 @@ import Protection from '~/projects/settings/branch_rules/components/view/protect
import {
I18N,
ALL_BRANCHES_WILDCARD,
+ REQUIRED_ICON,
+ NOT_REQUIRED_ICON,
+ REQUIRED_ICON_CLASS,
+ NOT_REQUIRED_ICON_CLASS,
} from '~/projects/settings/branch_rules/components/view/constants';
import branchRulesQuery from 'ee_else_ce/projects/settings/branch_rules/queries/branch_rules_details.query.graphql';
import { sprintf } from '~/locale';
@@ -19,7 +23,7 @@ import {
jest.mock('~/lib/utils/url_utility', () => ({
getParameterByName: jest.fn().mockReturnValue('main'),
- mergeUrlParams: jest.fn().mockReturnValue('/branches?state=all&search=main'),
+ mergeUrlParams: jest.fn().mockReturnValue('/branches?state=all&search=%5Emain%24'),
joinPaths: jest.fn(),
}));
@@ -39,12 +43,13 @@ describe('View branch rules', () => {
let fakeApollo;
const projectPath = 'test/testing';
const protectedBranchesPath = 'protected/branches';
- const branchProtectionsMockRequestHandler = jest
- .fn()
- .mockResolvedValue(branchProtectionsMockResponse);
+ const branchProtectionsMockRequestHandler = (response = branchProtectionsMockResponse) =>
+ jest.fn().mockResolvedValue(response);
- const createComponent = async () => {
- fakeApollo = createMockApollo([[branchRulesQuery, branchProtectionsMockRequestHandler]]);
+ const createComponent = async (mockResponse) => {
+ fakeApollo = createMockApollo([
+ [branchRulesQuery, branchProtectionsMockRequestHandler(mockResponse)],
+ ]);
wrapper = shallowMountExtended(RuleView, {
apolloProvider: fakeApollo,
@@ -57,13 +62,13 @@ describe('View branch rules', () => {
beforeEach(() => createComponent());
- afterEach(() => wrapper.destroy());
-
const findBranchName = () => wrapper.findByTestId('branch');
const findBranchTitle = () => wrapper.findByTestId('branch-title');
const findBranchProtectionTitle = () => wrapper.findByText(I18N.protectBranchTitle);
const findBranchProtections = () => wrapper.findAllComponents(Protection);
- const findForcePushTitle = () => wrapper.findByText(I18N.allowForcePushDescription);
+ const findForcePushIcon = () => wrapper.findByTestId('force-push-icon');
+ const findForcePushTitle = (title) => wrapper.findByText(title);
+ const findForcePushDescription = () => wrapper.findByText(I18N.forcePushDescription);
const findApprovalsTitle = () => wrapper.findByText(I18N.approvalsTitle);
const findStatusChecksTitle = () => wrapper.findByText(I18N.statusChecksTitle);
const findMatchingBranchesLink = () =>
@@ -94,9 +99,12 @@ describe('View branch rules', () => {
});
it('renders matching branches link', () => {
+ const mergeUrlParams = jest.spyOn(util, 'mergeUrlParams');
const matchingBranchesLink = findMatchingBranchesLink();
+
+ expect(mergeUrlParams).toHaveBeenCalledWith({ state: 'all', search: `^main$` }, '');
expect(matchingBranchesLink.exists()).toBe(true);
- expect(matchingBranchesLink.attributes().href).toBe('/branches?state=all&search=main');
+ expect(matchingBranchesLink.attributes().href).toBe('/branches?state=all&search=%5Emain%24');
});
it('renders a branch protection title', () => {
@@ -123,9 +131,23 @@ describe('View branch rules', () => {
});
});
- it('renders force push protection', () => {
- expect(findForcePushTitle().exists()).toBe(true);
- });
+ it.each`
+ allowForcePush | iconName | iconClass | title
+ ${true} | ${REQUIRED_ICON} | ${REQUIRED_ICON_CLASS} | ${I18N.allowForcePushTitle}
+ ${false} | ${NOT_REQUIRED_ICON} | ${NOT_REQUIRED_ICON_CLASS} | ${I18N.doesNotAllowForcePushTitle}
+ `(
+ 'renders force push section with the correct icon, title and description',
+ async ({ allowForcePush, iconName, iconClass, title }) => {
+ const mockResponse = branchProtectionsMockResponse;
+ mockResponse.data.project.branchRules.nodes[0].branchProtection.allowForcePush = allowForcePush;
+ await createComponent(mockResponse);
+
+ expect(findForcePushIcon().props('name')).toBe(iconName);
+ expect(findForcePushIcon().attributes('class')).toBe(iconClass);
+ expect(findForcePushTitle(title).exists()).toBe(true);
+ expect(findForcePushDescription().exists()).toBe(true);
+ },
+ );
it('renders a branch protection component for merge rules', () => {
expect(findBranchProtections().at(1).props()).toMatchObject({
diff --git a/spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js
index a98b156f94e..1bfd04e10a1 100644
--- a/spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js
+++ b/spec/frontend/projects/settings/branch_rules/components/view/protection_row_spec.js
@@ -18,8 +18,6 @@ describe('Branch rule protection row', () => {
beforeEach(() => createComponent());
- afterEach(() => wrapper.destroy());
-
const findTitle = () => wrapper.findByText(protectionRowPropsMock.title);
const findAvatarsInline = () => wrapper.findComponent(GlAvatarsInline);
const findAvatarLinks = () => wrapper.findAllComponents(GlAvatarLink);
diff --git a/spec/frontend/projects/settings/branch_rules/components/view/protection_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/protection_spec.js
index caf967b4257..f10d8d6d770 100644
--- a/spec/frontend/projects/settings/branch_rules/components/view/protection_spec.js
+++ b/spec/frontend/projects/settings/branch_rules/components/view/protection_spec.js
@@ -16,8 +16,6 @@ describe('Branch rule protection', () => {
beforeEach(() => createComponent());
- afterEach(() => wrapper.destroy());
-
const findCard = () => wrapper.findComponent(GlCard);
const findHeader = () => wrapper.findByText(protectionPropsMock.header);
const findLink = () => wrapper.findComponent(GlLink);
diff --git a/spec/frontend/projects/settings/components/default_branch_selector_spec.js b/spec/frontend/projects/settings/components/default_branch_selector_spec.js
index ca9a72663d2..c1412d01b53 100644
--- a/spec/frontend/projects/settings/components/default_branch_selector_spec.js
+++ b/spec/frontend/projects/settings/components/default_branch_selector_spec.js
@@ -19,10 +19,6 @@ describe('projects/settings/components/default_branch_selector', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
beforeEach(() => {
buildWrapper();
});
diff --git a/spec/frontend/projects/settings/components/new_access_dropdown_spec.js b/spec/frontend/projects/settings/components/new_access_dropdown_spec.js
index 26297d0c3ff..f3e536de703 100644
--- a/spec/frontend/projects/settings/components/new_access_dropdown_spec.js
+++ b/spec/frontend/projects/settings/components/new_access_dropdown_spec.js
@@ -89,15 +89,12 @@ describe('Access Level Dropdown', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownToggleLabel = () => findDropdown().props('text');
const findAllDropdownItems = () => findDropdown().findAllComponents(GlDropdownItem);
const findAllDropdownHeaders = () => findDropdown().findAllComponents(GlDropdownSectionHeader);
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+ const findDeployKeyDropdownItem = () => wrapper.findByTestId('deploy_key-dropdown-item');
const findDropdownItemWithText = (items, text) =>
items.filter((item) => item.text().includes(text)).at(0);
@@ -142,6 +139,21 @@ describe('Access Level Dropdown', () => {
it('renders dropdown item for each access level type', () => {
expect(findAllDropdownItems()).toHaveLength(12);
});
+
+ it.each`
+ accessLevel | shouldRenderDeployKeyItems
+ ${ACCESS_LEVELS.PUSH} | ${true}
+ ${ACCESS_LEVELS.CREATE} | ${true}
+ ${ACCESS_LEVELS.MERGE} | ${false}
+ `(
+ 'conditionally renders deploy keys based on access levels',
+ async ({ accessLevel, shouldRenderDeployKeyItems }) => {
+ createComponent({ accessLevel });
+ await waitForPromises();
+
+ expect(findDeployKeyDropdownItem().exists()).toBe(shouldRenderDeployKeyItems);
+ },
+ );
});
describe('toggleLabel', () => {
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 f82ad80135e..0ec0e981d65 100644
--- a/spec/frontend/projects/settings/components/shared_runners_toggle_spec.js
+++ b/spec/frontend/projects/settings/components/shared_runners_toggle_spec.js
@@ -9,7 +9,7 @@ import SharedRunnersToggleComponent from '~/projects/settings/components/shared_
const TEST_UPDATE_PATH = '/test/update_shared_runners';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('projects/settings/components/shared_runners', () => {
let wrapper;
@@ -41,8 +41,6 @@ describe('projects/settings/components/shared_runners', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
mockAxios.restore();
});
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 e091f3e25c3..d8c2cf83f38 100644
--- a/spec/frontend/projects/settings/components/transfer_project_form_spec.js
+++ b/spec/frontend/projects/settings/components/transfer_project_form_spec.js
@@ -31,10 +31,6 @@ describe('Transfer project form', () => {
const findTransferLocations = () => wrapper.findComponent(TransferLocations);
const findConfirmDanger = () => wrapper.findComponent(ConfirmDanger);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the namespace selector and passes `groupTransferLocationsApiMethod` prop', () => {
createComponent();
diff --git a/spec/frontend/projects/settings/repository/branch_rules/app_spec.js b/spec/frontend/projects/settings/repository/branch_rules/app_spec.js
index 56b39f04580..dd534bec25d 100644
--- a/spec/frontend/projects/settings/repository/branch_rules/app_spec.js
+++ b/spec/frontend/projects/settings/repository/branch_rules/app_spec.js
@@ -7,7 +7,7 @@ import { mountExtended } from 'helpers/vue_test_utils_helper';
import BranchRules from '~/projects/settings/repository/branch_rules/app.vue';
import BranchRule from '~/projects/settings/repository/branch_rules/components/branch_rule.vue';
import branchRulesQuery from 'ee_else_ce/projects/settings/repository/branch_rules/graphql/queries/branch_rules.query.graphql';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import {
branchRulesMockResponse,
appProvideMock,
@@ -22,7 +22,7 @@ import { expandSection } from '~/settings_panels';
import { scrollToElement } from '~/lib/utils/common_utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/settings_panels');
jest.mock('~/lib/utils/common_utils');
@@ -41,7 +41,7 @@ describe('Branch rules app', () => {
apolloProvider: fakeApollo,
provide: appProvideMock,
stubs: { GlModal: stubComponent(GlModal, { template: RENDER_ALL_SLOTS_TEMPLATE }) },
- directives: { GlModal: createMockDirective() },
+ directives: { GlModal: createMockDirective('gl-modal') },
});
await waitForPromises();
diff --git a/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js b/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js
index 8d0fd390e35..8bea84f4429 100644
--- a/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js
+++ b/spec/frontend/projects/settings/repository/branch_rules/components/branch_rule_spec.js
@@ -71,8 +71,10 @@ describe('Branch rule', () => {
});
it('renders a detail button with the correct href', () => {
+ const encodedBranchName = encodeURIComponent(branchRulePropsMock.name);
+
expect(findDetailsButton().attributes('href')).toBe(
- `${branchRuleProvideMock.branchRulesPath}?branch=${branchRulePropsMock.name}`,
+ `${branchRuleProvideMock.branchRulesPath}?branch=${encodedBranchName}`,
);
});
});
diff --git a/spec/frontend/projects/settings/repository/branch_rules/mock_data.js b/spec/frontend/projects/settings/repository/branch_rules/mock_data.js
index de7f6c8b88d..d169397241d 100644
--- a/spec/frontend/projects/settings/repository/branch_rules/mock_data.js
+++ b/spec/frontend/projects/settings/repository/branch_rules/mock_data.js
@@ -74,7 +74,7 @@ export const branchRuleProvideMock = {
};
export const branchRulePropsMock = {
- name: 'main',
+ name: 'branch-with-$speci@l-#-chars',
isDefault: true,
matchingBranchesCount: 1,
branchProtection: {
diff --git a/spec/frontend/projects/settings/topics/components/topics_token_selector_spec.js b/spec/frontend/projects/settings/topics/components/topics_token_selector_spec.js
index 8b8e7d1454d..4b94c179f74 100644
--- a/spec/frontend/projects/settings/topics/components/topics_token_selector_spec.js
+++ b/spec/frontend/projects/settings/topics/components/topics_token_selector_spec.js
@@ -64,7 +64,6 @@ describe('TopicsTokenSelector', () => {
});
afterEach(() => {
- wrapper.destroy();
div.remove();
input.remove();
});
diff --git a/spec/frontend/projects/settings/utils_spec.js b/spec/frontend/projects/settings/utils_spec.js
index 319aa4000b5..d85f43778b1 100644
--- a/spec/frontend/projects/settings/utils_spec.js
+++ b/spec/frontend/projects/settings/utils_spec.js
@@ -1,4 +1,5 @@
-import { getAccessLevels } from '~/projects/settings/utils';
+import { getAccessLevels, generateRefDestinationPath } from '~/projects/settings/utils';
+import setWindowLocation from 'helpers/set_window_location_helper';
import { pushAccessLevelsMockResponse, pushAccessLevelsMockResult } from './mock_data';
describe('Utils', () => {
@@ -8,4 +9,25 @@ describe('Utils', () => {
expect(pushAccessLevels).toEqual(pushAccessLevelsMockResult);
});
});
+
+ describe('generateRefDestinationPath', () => {
+ const projectRootPath = 'http://test.host/root/Project1';
+ const settingsCi = '-/settings/ci_cd';
+
+ it.each`
+ currentPath | selectedRef | result
+ ${`${projectRootPath}`} | ${undefined} | ${`${projectRootPath}`}
+ ${`${projectRootPath}`} | ${'test'} | ${`${projectRootPath}`}
+ ${`${projectRootPath}/${settingsCi}`} | ${'test'} | ${`${projectRootPath}/${settingsCi}?ref=test`}
+ ${`${projectRootPath}/${settingsCi}`} | ${'branch-hyphen'} | ${`${projectRootPath}/${settingsCi}?ref=branch-hyphen`}
+ ${`${projectRootPath}/${settingsCi}`} | ${'test/branch'} | ${`${projectRootPath}/${settingsCi}?ref=test%2Fbranch`}
+ ${`${projectRootPath}/${settingsCi}`} | ${'test/branch-hyphen'} | ${`${projectRootPath}/${settingsCi}?ref=test%2Fbranch-hyphen`}
+ `(
+ 'generates the correct destination path for the `$selectedRef` ref and current url $currentPath by outputting $result',
+ ({ currentPath, selectedRef, result }) => {
+ setWindowLocation(currentPath);
+ expect(generateRefDestinationPath(selectedRef)).toBe(result);
+ },
+ );
+ });
});
diff --git a/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js b/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js
index 5fc9f9ba629..4d0d2191176 100644
--- a/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js
+++ b/spec/frontend/projects/settings_service_desk/components/service_desk_root_spec.js
@@ -41,7 +41,6 @@ describe('ServiceDeskRoot', () => {
afterEach(() => {
axiosMock.restore();
- wrapper.destroy();
if (spy) {
spy.mockRestore();
}
diff --git a/spec/frontend/projects/terraform_notification/terraform_notification_spec.js b/spec/frontend/projects/terraform_notification/terraform_notification_spec.js
index 6576ce70d60..1d0faebbcb2 100644
--- a/spec/frontend/projects/terraform_notification/terraform_notification_spec.js
+++ b/spec/frontend/projects/terraform_notification/terraform_notification_spec.js
@@ -41,10 +41,6 @@ describe('TerraformNotificationBanner', () => {
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when user has already dismissed the banner', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/protected_branches/protected_branch_edit_spec.js b/spec/frontend/protected_branches/protected_branch_edit_spec.js
index b4029d94980..4141d000a1c 100644
--- a/spec/frontend/protected_branches/protected_branch_edit_spec.js
+++ b/spec/frontend/protected_branches/protected_branch_edit_spec.js
@@ -2,12 +2,12 @@ import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import ProtectedBranchEdit from '~/protected_branches/protected_branch_edit';
-jest.mock('~/flash');
+jest.mock('~/alert');
const TEST_URL = `${TEST_HOST}/url`;
const FORCE_PUSH_TOGGLE_TESTID = 'force-push-toggle';
@@ -149,7 +149,7 @@ describe('ProtectedBranchEdit', () => {
toggle.click();
});
- it('flashes error', async () => {
+ it('alerts error', async () => {
await axios.waitForAll();
expect(createAlert).toHaveBeenCalled();
diff --git a/spec/frontend/ref/components/__snapshots__/ref_selector_spec.js.snap b/spec/frontend/ref/components/__snapshots__/ref_selector_spec.js.snap
deleted file mode 100644
index 5053778369e..00000000000
--- a/spec/frontend/ref/components/__snapshots__/ref_selector_spec.js.snap
+++ /dev/null
@@ -1,80 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Ref selector component footer slot passes the expected slot props 1`] = `
-Object {
- "isLoading": false,
- "matches": Object {
- "branches": Object {
- "error": null,
- "list": Array [
- Object {
- "default": false,
- "name": "add_images_and_changes",
- "value": undefined,
- },
- Object {
- "default": false,
- "name": "conflict-contains-conflict-markers",
- "value": undefined,
- },
- Object {
- "default": false,
- "name": "deleted-image-test",
- "value": undefined,
- },
- Object {
- "default": false,
- "name": "diff-files-image-to-symlink",
- "value": undefined,
- },
- Object {
- "default": false,
- "name": "diff-files-symlink-to-image",
- "value": undefined,
- },
- Object {
- "default": false,
- "name": "markdown",
- "value": undefined,
- },
- Object {
- "default": true,
- "name": "master",
- "value": undefined,
- },
- ],
- "totalCount": 123,
- },
- "commits": Object {
- "error": null,
- "list": Array [
- Object {
- "name": "b83d6e39",
- "subtitle": "Merge branch 'branch-merged' into 'master'",
- "value": "b83d6e391c22777fca1ed3012fce84f633d7fed0",
- },
- ],
- "totalCount": 1,
- },
- "tags": Object {
- "error": null,
- "list": Array [
- Object {
- "name": "v1.1.1",
- "value": undefined,
- },
- Object {
- "name": "v1.1.0",
- "value": undefined,
- },
- Object {
- "name": "v1.0.0",
- "value": undefined,
- },
- ],
- "totalCount": 456,
- },
- },
- "query": "abcd1234",
-}
-`;
diff --git a/spec/frontend/ref/components/ref_selector_spec.js b/spec/frontend/ref/components/ref_selector_spec.js
index 40d3a291074..6b90827f9c2 100644
--- a/spec/frontend/ref/components/ref_selector_spec.js
+++ b/spec/frontend/ref/components/ref_selector_spec.js
@@ -4,9 +4,9 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { merge, last } from 'lodash';
import Vuex from 'vuex';
+import tags from 'test_fixtures/api/tags/tags.json';
import commit from 'test_fixtures/api/commits/commit.json';
import branches from 'test_fixtures/api/branches/branches.json';
-import tags from 'test_fixtures/api/tags/tags.json';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { trimText } from 'helpers/text_helper';
import {
@@ -33,6 +33,8 @@ describe('Ref selector component', () => {
const fixtures = { branches, tags, commit };
const projectId = '8';
+ const totalBranchesCount = 123;
+ const totalTagsCount = 456;
let wrapper;
let branchesApiCallSpy;
@@ -69,10 +71,14 @@ describe('Ref selector component', () => {
branchesApiCallSpy = jest
.fn()
- .mockReturnValue([HTTP_STATUS_OK, fixtures.branches, { [X_TOTAL_HEADER]: '123' }]);
+ .mockReturnValue([
+ HTTP_STATUS_OK,
+ fixtures.branches,
+ { [X_TOTAL_HEADER]: totalBranchesCount },
+ ]);
tagsApiCallSpy = jest
.fn()
- .mockReturnValue([HTTP_STATUS_OK, fixtures.tags, { [X_TOTAL_HEADER]: '456' }]);
+ .mockReturnValue([HTTP_STATUS_OK, fixtures.tags, { [X_TOTAL_HEADER]: totalTagsCount }]);
commitApiCallSpy = jest.fn().mockReturnValue([HTTP_STATUS_OK, fixtures.commit]);
requestSpies = { branchesApiCallSpy, tagsApiCallSpy, commitApiCallSpy };
@@ -690,7 +696,46 @@ describe('Ref selector component', () => {
// is updated. For the sake of this test, we'll just test the last call, which
// represents the final state of the slot props.
const lastCallProps = last(createFooter.mock.calls)[0];
- expect(lastCallProps).toMatchSnapshot();
+ expect(lastCallProps.isLoading).toBe(false);
+ expect(lastCallProps.query).toBe('abcd1234');
+
+ const branchesList = fixtures.branches.map((branch) => {
+ return {
+ default: branch.default,
+ name: branch.name,
+ };
+ });
+
+ const commitsList = [
+ {
+ name: fixtures.commit.short_id,
+ subtitle: fixtures.commit.title,
+ value: fixtures.commit.id,
+ },
+ ];
+
+ const tagsList = fixtures.tags.map((tag) => {
+ return {
+ name: tag.name,
+ };
+ });
+
+ const expectedMatches = {
+ branches: {
+ list: branchesList,
+ totalCount: totalBranchesCount,
+ },
+ commits: {
+ list: commitsList,
+ totalCount: 1,
+ },
+ tags: {
+ list: tagsList,
+ totalCount: totalTagsCount,
+ },
+ };
+
+ expect(lastCallProps.matches).toMatchObject(expectedMatches);
});
});
});
diff --git a/spec/frontend/releases/components/app_edit_new_spec.js b/spec/frontend/releases/components/app_edit_new_spec.js
index bd61e4537f9..dcb6a3293a6 100644
--- a/spec/frontend/releases/components/app_edit_new_spec.js
+++ b/spec/frontend/releases/components/app_edit_new_spec.js
@@ -16,6 +16,7 @@ import AssetLinksForm from '~/releases/components/asset_links_form.vue';
import ConfirmDeleteModal from '~/releases/components/confirm_delete_modal.vue';
import { BACK_URL_PARAM } from '~/releases/constants';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+import { ValidationResult } from '~/lib/utils/ref_validator';
const originalRelease = originalOneReleaseForEditingQueryResponse.data.project.release;
const originalMilestones = originalRelease.milestones;
@@ -58,6 +59,7 @@ describe('Release edit/new component', () => {
assets: {
links: [],
},
+ tagNameValidation: new ValidationResult(),
}),
formattedReleaseNotes: () => 'these notes are formatted',
};
@@ -101,11 +103,6 @@ describe('Release edit/new component', () => {
release = convertOneReleaseGraphQLResponse(originalOneReleaseForEditingQueryResponse).data;
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findSubmitButton = () => wrapper.find('button[type=submit]');
const findForm = () => wrapper.find('form');
diff --git a/spec/frontend/releases/components/app_index_spec.js b/spec/frontend/releases/components/app_index_spec.js
index ef3bd5ca873..7a0e9fb7326 100644
--- a/spec/frontend/releases/components/app_index_spec.js
+++ b/spec/frontend/releases/components/app_index_spec.js
@@ -6,7 +6,7 @@ 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 '~/releases/graphql/queries/all_releases.query.graphql';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { historyPushState } from '~/lib/utils/common_utils';
import { sprintf, __ } from '~/locale';
import ReleasesIndexApp from '~/releases/components/app_index.vue';
@@ -20,7 +20,7 @@ import { deleteReleaseSessionKey } from '~/releases/util';
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
let mockQueryParams;
jest.mock('~/lib/utils/common_utils', () => ({
@@ -114,7 +114,7 @@ describe('app_index.vue', () => {
const toDescription = (bool) => (bool ? 'does' : 'does not');
describe.each`
- description | singleResponseFn | fullResponseFn | loadingIndicator | emptyState | flashMessage | releaseCount | pagination
+ description | singleResponseFn | fullResponseFn | loadingIndicator | emptyState | alertMessage | releaseCount | pagination
${'both requests loading'} | ${getInProgressResponse} | ${getInProgressResponse} | ${true} | ${false} | ${false} | ${0} | ${false}
${'both requests failed'} | ${getErrorResponse} | ${getErrorResponse} | ${false} | ${false} | ${true} | ${0} | ${false}
${'both requests loaded'} | ${getSingleRequestLoadedResponse} | ${getFullRequestLoadedResponse} | ${false} | ${false} | ${false} | ${2} | ${true}
@@ -134,7 +134,7 @@ describe('app_index.vue', () => {
fullResponseFn,
loadingIndicator,
emptyState,
- flashMessage,
+ alertMessage,
releaseCount,
pagination,
}) => {
@@ -154,9 +154,9 @@ describe('app_index.vue', () => {
expect(findEmptyState().exists()).toBe(emptyState);
});
- it(`${toDescription(flashMessage)} show a flash message`, async () => {
+ it(`${toDescription(alertMessage)} show a flash message`, async () => {
await waitForPromises();
- if (flashMessage) {
+ if (alertMessage) {
expect(createAlert).toHaveBeenCalledWith({
message: ReleasesIndexApp.i18n.errorMessage,
captureError: true,
diff --git a/spec/frontend/releases/components/app_show_spec.js b/spec/frontend/releases/components/app_show_spec.js
index efe72e8000a..942280cb6a2 100644
--- a/spec/frontend/releases/components/app_show_spec.js
+++ b/spec/frontend/releases/components/app_show_spec.js
@@ -4,14 +4,14 @@ 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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { popCreateReleaseNotification } from '~/releases/release_notification_service';
import ReleaseShowApp from '~/releases/components/app_show.vue';
import ReleaseBlock from '~/releases/components/release_block.vue';
import ReleaseSkeletonLoader from '~/releases/components/release_skeleton_loader.vue';
import oneReleaseQuery from '~/releases/graphql/queries/one_release.query.graphql';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/releases/release_notification_service');
Vue.use(VueApollo);
@@ -33,11 +33,6 @@ describe('Release show component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findLoadingSkeleton = () => wrapper.findComponent(ReleaseSkeletonLoader);
const findReleaseBlock = () => wrapper.findComponent(ReleaseBlock);
@@ -54,13 +49,13 @@ describe('Release show component', () => {
};
const expectNoFlash = () => {
- it('does not show a flash message', () => {
+ it('does not show an alert message', () => {
expect(createAlert).not.toHaveBeenCalled();
});
};
const expectFlashWithMessage = (message) => {
- it(`shows a flash message that reads "${message}"`, () => {
+ it(`shows an alert message that reads "${message}"`, () => {
expect(createAlert).toHaveBeenCalledWith({
message,
captureError: true,
@@ -152,7 +147,7 @@ describe('Release show component', () => {
beforeEach(async () => {
// As we return a release as `null`, Apollo also throws an error to the console
// about the missing field. We need to suppress console.error in order to check
- // that flash message was called
+ // that alert message was called
// eslint-disable-next-line no-console
console.error = jest.fn();
diff --git a/spec/frontend/releases/components/asset_links_form_spec.js b/spec/frontend/releases/components/asset_links_form_spec.js
index b1e9d8d1256..8eee9acd808 100644
--- a/spec/frontend/releases/components/asset_links_form_spec.js
+++ b/spec/frontend/releases/components/asset_links_form_spec.js
@@ -60,11 +60,6 @@ describe('Release edit component', () => {
release = commonUtils.convertObjectPropsToCamelCase(originalRelease, { deep: true });
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('with a basic store state', () => {
beforeEach(() => {
factory();
diff --git a/spec/frontend/releases/components/confirm_delete_modal_spec.js b/spec/frontend/releases/components/confirm_delete_modal_spec.js
index f7c526c1ced..b4699302779 100644
--- a/spec/frontend/releases/components/confirm_delete_modal_spec.js
+++ b/spec/frontend/releases/components/confirm_delete_modal_spec.js
@@ -42,10 +42,6 @@ describe('~/releases/components/confirm_delete_modal.vue', () => {
factory();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('button', () => {
it('should open the modal on click', async () => {
await wrapper.findByRole('button', { name: 'Delete' }).trigger('click');
diff --git a/spec/frontend/releases/components/evidence_block_spec.js b/spec/frontend/releases/components/evidence_block_spec.js
index 69443cb7a11..42eac31e5ac 100644
--- a/spec/frontend/releases/components/evidence_block_spec.js
+++ b/spec/frontend/releases/components/evidence_block_spec.js
@@ -27,10 +27,6 @@ describe('Evidence Block', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the evidence icon', () => {
expect(wrapper.findComponent(GlIcon).props('name')).toBe('review-list');
});
diff --git a/spec/frontend/releases/components/issuable_stats_spec.js b/spec/frontend/releases/components/issuable_stats_spec.js
index 3ac75e138ee..c8cdf9cb951 100644
--- a/spec/frontend/releases/components/issuable_stats_spec.js
+++ b/spec/frontend/releases/components/issuable_stats_spec.js
@@ -34,11 +34,6 @@ describe('~/releases/components/issuable_stats.vue', () => {
};
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('matches snapshot', () => {
createComponent();
diff --git a/spec/frontend/releases/components/release_block_footer_spec.js b/spec/frontend/releases/components/release_block_footer_spec.js
index 19b41d05a44..12e3807c9fa 100644
--- a/spec/frontend/releases/components/release_block_footer_spec.js
+++ b/spec/frontend/releases/components/release_block_footer_spec.js
@@ -33,11 +33,6 @@ describe('Release block footer', () => {
release = cloneDeep(originalRelease);
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const commitInfoSection = () => wrapper.find('.js-commit-info');
const commitInfoSectionLink = () => commitInfoSection().findComponent(GlLink);
const tagInfoSection = () => wrapper.find('.js-tag-info');
diff --git a/spec/frontend/releases/components/release_block_header_spec.js b/spec/frontend/releases/components/release_block_header_spec.js
index fc421776d60..dd39a1bce53 100644
--- a/spec/frontend/releases/components/release_block_header_spec.js
+++ b/spec/frontend/releases/components/release_block_header_spec.js
@@ -25,10 +25,6 @@ describe('Release block header', () => {
release = convertObjectPropsToCamelCase(originalRelease, { deep: true });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findHeader = () => wrapper.find('h2');
const findHeaderLink = () => findHeader().findComponent(GlLink);
const findEditButton = () => wrapper.find('.js-edit-button');
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 541d487091c..b8030ae1fd2 100644
--- a/spec/frontend/releases/components/release_block_milestone_info_spec.js
+++ b/spec/frontend/releases/components/release_block_milestone_info_spec.js
@@ -25,11 +25,6 @@ describe('Release block milestone info', () => {
milestones = convertObjectPropsToCamelCase(originalMilestones, { deep: true });
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const milestoneProgressBarContainer = () => wrapper.find('.js-milestone-progress-bar-container');
const milestoneListContainer = () => wrapper.find('.js-milestone-list-container');
const issuesContainer = () => wrapper.find('[data-testid="issue-stats"]');
diff --git a/spec/frontend/releases/components/release_block_spec.js b/spec/frontend/releases/components/release_block_spec.js
index f1b8554fbc3..3355b5ab2c3 100644
--- a/spec/frontend/releases/components/release_block_spec.js
+++ b/spec/frontend/releases/components/release_block_spec.js
@@ -39,10 +39,6 @@ describe('Release block', () => {
release = convertOneReleaseGraphQLResponse(originalOneReleaseQueryResponse).data;
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with default props', () => {
beforeEach(() => factory(release));
diff --git a/spec/frontend/releases/components/releases_pagination_spec.js b/spec/frontend/releases/components/releases_pagination_spec.js
index 59be808c802..923d84ae2b3 100644
--- a/spec/frontend/releases/components/releases_pagination_spec.js
+++ b/spec/frontend/releases/components/releases_pagination_spec.js
@@ -29,10 +29,6 @@ describe('releases_pagination.vue', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const singlePageInfo = {
hasPreviousPage: false,
hasNextPage: false,
diff --git a/spec/frontend/releases/components/releases_sort_spec.js b/spec/frontend/releases/components/releases_sort_spec.js
index c6e1846d252..92199896ab4 100644
--- a/spec/frontend/releases/components/releases_sort_spec.js
+++ b/spec/frontend/releases/components/releases_sort_spec.js
@@ -17,10 +17,6 @@ describe('releases_sort.vue', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findSorting = () => wrapper.findComponent(GlSorting);
const findSortingItems = () => wrapper.findAllComponents(GlSortingItem);
const findReleasedDateItem = () =>
diff --git a/spec/frontend/releases/components/tag_field_exsting_spec.js b/spec/frontend/releases/components/tag_field_exsting_spec.js
index 8105aa4f6f2..0e896eb645c 100644
--- a/spec/frontend/releases/components/tag_field_exsting_spec.js
+++ b/spec/frontend/releases/components/tag_field_exsting_spec.js
@@ -37,11 +37,6 @@ describe('releases/components/tag_field_existing', () => {
};
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('default', () => {
it('shows the tag name', () => {
createComponent();
diff --git a/spec/frontend/releases/components/tag_field_new_spec.js b/spec/frontend/releases/components/tag_field_new_spec.js
index fcba0da3462..2508495429c 100644
--- a/spec/frontend/releases/components/tag_field_new_spec.js
+++ b/spec/frontend/releases/components/tag_field_new_spec.js
@@ -9,6 +9,7 @@ import { __ } from '~/locale';
import TagFieldNew from '~/releases/components/tag_field_new.vue';
import createStore from '~/releases/stores';
import createEditNewModule from '~/releases/stores/modules/edit_new';
+import { i18n } from '~/releases/constants';
const TEST_TAG_NAME = 'test-tag-name';
const TEST_TAG_MESSAGE = 'Test tag message';
@@ -81,7 +82,6 @@ describe('releases/components/tag_field_new', () => {
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
});
@@ -210,9 +210,7 @@ describe('releases/components/tag_field_new', () => {
store.state.editNew.existingRelease = {};
await expectValidationMessageToBe('shown');
- expect(findTagNameFormGroup().text()).toContain(
- __('Selected tag is already in use. Choose another option.'),
- );
+ expect(findTagNameFormGroup().text()).toContain(i18n.tagIsAlredyInUseMessage);
});
});
@@ -222,7 +220,7 @@ describe('releases/components/tag_field_new', () => {
findTagNameDropdown().vm.$emit('hide');
await expectValidationMessageToBe('shown');
- expect(findTagNameFormGroup().text()).toContain(__('Tag name is required.'));
+ expect(findTagNameFormGroup().text()).toContain(i18n.tagNameIsRequiredMessage);
});
});
});
diff --git a/spec/frontend/releases/components/tag_field_spec.js b/spec/frontend/releases/components/tag_field_spec.js
index 85a40f02c53..8509c347291 100644
--- a/spec/frontend/releases/components/tag_field_spec.js
+++ b/spec/frontend/releases/components/tag_field_spec.js
@@ -24,11 +24,6 @@ describe('releases/components/tag_field', () => {
const findTagFieldNew = () => wrapper.findComponent(TagFieldNew);
const findTagFieldExisting = () => wrapper.findComponent(TagFieldExisting);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when an existing release is being edited', () => {
beforeEach(() => {
createComponent({ isExistingRelease: true });
diff --git a/spec/frontend/releases/release_notification_service_spec.js b/spec/frontend/releases/release_notification_service_spec.js
index 2344d4b929a..a90bfa3dcbd 100644
--- a/spec/frontend/releases/release_notification_service_spec.js
+++ b/spec/frontend/releases/release_notification_service_spec.js
@@ -2,9 +2,9 @@ import {
popCreateReleaseNotification,
putCreateReleaseNotification,
} from '~/releases/release_notification_service';
-import { createAlert, VARIANT_SUCCESS } from '~/flash';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('~/releases/release_notification_service', () => {
const projectPath = 'test-project-path';
@@ -35,7 +35,7 @@ describe('~/releases/release_notification_service', () => {
expect(item).toBe(null);
});
- it('should create a flash message', () => {
+ it('should create an alert message', () => {
expect(createAlert).toHaveBeenCalledTimes(1);
expect(createAlert).toHaveBeenCalledWith({
message: `Release ${releaseName} has been successfully created.`,
@@ -49,7 +49,7 @@ describe('~/releases/release_notification_service', () => {
popCreateReleaseNotification(projectPath);
});
- it('should not create a flash message', () => {
+ it('should not create an alert message', () => {
expect(createAlert).toHaveBeenCalledTimes(0);
});
});
diff --git a/spec/frontend/releases/stores/modules/detail/actions_spec.js b/spec/frontend/releases/stores/modules/detail/actions_spec.js
index ca3b2d5f734..2fca3396a1f 100644
--- a/spec/frontend/releases/stores/modules/detail/actions_spec.js
+++ b/spec/frontend/releases/stores/modules/detail/actions_spec.js
@@ -2,7 +2,7 @@ import { cloneDeep } from 'lodash';
import originalOneReleaseForEditingQueryResponse from 'test_fixtures/graphql/releases/graphql/queries/one_release_for_editing.query.graphql.json';
import testAction from 'helpers/vuex_action_helper';
import { getTag } from '~/api/tags_api';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { redirectTo } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import { ASSET_LINK_TYPE } from '~/releases/constants';
@@ -21,7 +21,7 @@ import {
jest.mock('~/api/tags_api');
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/releases/release_notification_service');
@@ -154,7 +154,7 @@ describe('Release edit/new actions', () => {
]);
});
- it(`shows a flash message`, () => {
+ it(`shows an alert message`, () => {
return actions.fetchRelease({ commit: jest.fn(), state, rootState: state }).then(() => {
expect(createAlert).toHaveBeenCalledTimes(1);
expect(createAlert).toHaveBeenCalledWith({
@@ -380,7 +380,7 @@ describe('Release edit/new actions', () => {
]);
});
- it(`shows a flash message`, () => {
+ it(`shows an alert message`, () => {
return actions
.createRelease({ commit: jest.fn(), dispatch: jest.fn(), state, getters: {} })
.then(() => {
@@ -406,7 +406,7 @@ describe('Release edit/new actions', () => {
]);
});
- it(`shows a flash message`, () => {
+ it(`shows an alert message`, () => {
return actions
.createRelease({ commit: jest.fn(), dispatch: jest.fn(), state, getters: {} })
.then(() => {
@@ -538,7 +538,7 @@ describe('Release edit/new actions', () => {
expect(commit.mock.calls).toEqual([[types.RECEIVE_SAVE_RELEASE_ERROR, error]]);
});
- it('shows a flash message', async () => {
+ it('shows an alert message', async () => {
await actions.updateRelease({ commit, dispatch, state, getters });
expect(createAlert).toHaveBeenCalledTimes(1);
@@ -558,7 +558,7 @@ describe('Release edit/new actions', () => {
]);
});
- it('shows a flash message', async () => {
+ it('shows an alert message', async () => {
await actions.updateRelease({ commit, dispatch, state, getters });
expect(createAlert).toHaveBeenCalledTimes(1);
@@ -711,7 +711,7 @@ describe('Release edit/new actions', () => {
expect(commit.mock.calls).toContainEqual([types.RECEIVE_SAVE_RELEASE_ERROR, error]);
});
- it('shows a flash message', async () => {
+ it('shows an alert message', async () => {
await actions.deleteRelease({ commit, dispatch, state, getters });
expect(createAlert).toHaveBeenCalledTimes(1);
@@ -747,7 +747,7 @@ describe('Release edit/new actions', () => {
]);
});
- it('shows a flash message', async () => {
+ it('shows an alert message', async () => {
await actions.deleteRelease({ commit, dispatch, state, getters });
expect(createAlert).toHaveBeenCalledTimes(1);
@@ -778,7 +778,7 @@ describe('Release edit/new actions', () => {
expect(getTag).toHaveBeenCalledWith(state.projectId, tagName);
});
- it('creates a flash on error', async () => {
+ it('creates an alert on error', async () => {
error = new Error();
getTag.mockRejectedValue(error);
diff --git a/spec/frontend/releases/stores/modules/detail/getters_spec.js b/spec/frontend/releases/stores/modules/detail/getters_spec.js
index f8b87ec71dc..649e772f956 100644
--- a/spec/frontend/releases/stores/modules/detail/getters_spec.js
+++ b/spec/frontend/releases/stores/modules/detail/getters_spec.js
@@ -1,5 +1,16 @@
import { s__ } from '~/locale';
import * as getters from '~/releases/stores/modules/edit_new/getters';
+import { i18n } from '~/releases/constants';
+import { validateTag, ValidationResult } from '~/lib/utils/ref_validator';
+
+jest.mock('~/lib/utils/ref_validator', () => {
+ const original = jest.requireActual('~/lib/utils/ref_validator');
+ return {
+ __esModule: true,
+ ValidationResult: original.ValidationResult,
+ validateTag: jest.fn(() => new original.ValidationResult()),
+ };
+});
describe('Release edit/new getters', () => {
describe('releaseLinksToCreate', () => {
@@ -59,23 +70,23 @@ describe('Release edit/new getters', () => {
});
describe('validationErrors', () => {
+ const validState = {
+ release: {
+ tagName: 'test-tag-name',
+ assets: {
+ links: [
+ { id: 1, url: 'https://example.com/valid', name: 'Link 1' },
+ { id: 2, url: '', name: '' },
+ { id: 3, url: '', name: ' ' },
+ { id: 4, url: ' ', name: '' },
+ { id: 5, url: ' ', name: ' ' },
+ ],
+ },
+ },
+ };
describe('when the form is valid', () => {
+ const state = validState;
it('returns no validation errors', () => {
- const state = {
- release: {
- tagName: 'test-tag-name',
- assets: {
- links: [
- { id: 1, url: 'https://example.com/valid', name: 'Link 1' },
- { id: 2, url: '', name: '' },
- { id: 3, url: '', name: ' ' },
- { id: 4, url: ' ', name: '' },
- { id: 5, url: ' ', name: ' ' },
- ],
- },
- },
- };
-
const expectedErrors = {
assets: {
links: {
@@ -88,7 +99,27 @@ describe('Release edit/new getters', () => {
},
};
- expect(getters.validationErrors(state)).toEqual(expectedErrors);
+ expect(getters.validationErrors(state).assets).toEqual(expectedErrors.assets);
+ expect(getters.validationErrors(state).tagNameValidation.isValid).toBe(true);
+ });
+ });
+
+ describe('when validating tag', () => {
+ const state = validState;
+ it('validateTag is called with right parameters', () => {
+ getters.validationErrors(state);
+ expect(validateTag).toHaveBeenCalledWith(state.release.tagName);
+ });
+
+ it('validation error is correctly returned', () => {
+ const validationError = new ValidationResult();
+ const errorText = 'Tag format validation error';
+ validationError.addValidationError(errorText);
+ validateTag.mockReturnValue(validationError);
+
+ const result = getters.validationErrors(state);
+ expect(validateTag).toHaveBeenCalledWith(state.release.tagName);
+ expect(result.tagNameValidation.validationErrors).toContain(errorText);
});
});
@@ -140,19 +171,17 @@ describe('Release edit/new getters', () => {
});
it('returns a validation error if the tag name is empty', () => {
- const expectedErrors = {
- isTagNameEmpty: true,
- };
-
- expect(actualErrors).toMatchObject(expectedErrors);
+ expect(actualErrors.tagNameValidation.isValid).toBe(false);
+ expect(actualErrors.tagNameValidation.validationErrors).toContain(
+ i18n.tagNameIsRequiredMessage,
+ );
});
it('returns a validation error if the tag has an existing release', () => {
- const expectedErrors = {
- existingRelease: true,
- };
-
- expect(actualErrors).toMatchObject(expectedErrors);
+ expect(actualErrors.tagNameValidation.isValid).toBe(false);
+ expect(actualErrors.tagNameValidation.validationErrors).toContain(
+ i18n.tagIsAlredyInUseMessage,
+ );
});
it('returns a validation error if links share a URL', () => {
diff --git a/spec/frontend/repository/commits_service_spec.js b/spec/frontend/repository/commits_service_spec.js
index e56975d021a..22ef552c2f9 100644
--- a/spec/frontend/repository/commits_service_spec.js
+++ b/spec/frontend/repository/commits_service_spec.js
@@ -2,11 +2,11 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { loadCommits, isRequested, resetRequestedCommits } from '~/repository/commits_service';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { I18N_COMMIT_DATA_FETCH_ERROR } from '~/repository/constants';
import { refWithSpecialCharMock } from './mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('commits service', () => {
let mock;
diff --git a/spec/frontend/repository/components/blob_button_group_spec.js b/spec/frontend/repository/components/blob_button_group_spec.js
index 33a85c04fcf..96dedd54126 100644
--- a/spec/frontend/repository/components/blob_button_group_spec.js
+++ b/spec/frontend/repository/components/blob_button_group_spec.js
@@ -38,10 +38,6 @@ describe('BlobButtonGroup component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findDeleteBlobModal = () => wrapper.findComponent(DeleteBlobModal);
const findUploadBlobModal = () => wrapper.findComponent(UploadBlobModal);
const findDeleteButton = () => wrapper.findByTestId('delete');
diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js
index 03a8ee6ac5d..a588251c4bd 100644
--- a/spec/frontend/repository/components/blob_content_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_content_viewer_spec.js
@@ -175,7 +175,6 @@ describe('Blob content viewer component', () => {
});
afterEach(() => {
- wrapper.destroy();
mockAxios.reset();
});
@@ -482,11 +481,6 @@ describe('Blob content viewer component', () => {
repository: { empty },
} = projectMock;
- afterEach(() => {
- delete gon.current_user_id;
- delete gon.current_username;
- });
-
it('renders component', async () => {
window.gon.current_user_id = 1;
window.gon.current_username = 'root';
diff --git a/spec/frontend/repository/components/blob_controls_spec.js b/spec/frontend/repository/components/blob_controls_spec.js
index 0d52542397f..3ced5f6c4d2 100644
--- a/spec/frontend/repository/components/blob_controls_spec.js
+++ b/spec/frontend/repository/components/blob_controls_spec.js
@@ -50,8 +50,6 @@ describe('Blob controls component', () => {
beforeEach(() => createComponent());
- afterEach(() => wrapper.destroy());
-
it('renders a find button with the correct href', () => {
expect(findFindButton().attributes('href')).toBe('find/file.js');
});
diff --git a/spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js
index 599443bf862..b4f4b0058de 100644
--- a/spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_viewers/lfs_viewer_spec.js
@@ -21,8 +21,6 @@ describe('LFS Viewer', () => {
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.',
diff --git a/spec/frontend/repository/components/blob_viewers/notebook_viewer_spec.js b/spec/frontend/repository/components/blob_viewers/notebook_viewer_spec.js
index 51f3d31ec72..5d37692bf90 100644
--- a/spec/frontend/repository/components/blob_viewers/notebook_viewer_spec.js
+++ b/spec/frontend/repository/components/blob_viewers/notebook_viewer_spec.js
@@ -1,7 +1,6 @@
-import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import NotebookViewer from '~/repository/components/blob_viewers/notebook_viewer.vue';
-import notebookLoader from '~/blob/notebook';
+import Notebook from '~/blob/notebook/notebook_viewer.vue';
jest.mock('~/blob/notebook');
@@ -17,24 +16,11 @@ describe('Notebook Viewer', () => {
});
};
- const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
- const findNotebookWrapper = () => wrapper.findByTestId('notebook');
+ const findNotebook = () => wrapper.findComponent(Notebook);
beforeEach(() => createComponent());
- it('calls the notebook loader', () => {
- expect(notebookLoader).toHaveBeenCalledWith({
- el: wrapper.vm.$refs.viewer,
- relativeRawPath: ROOT_RELATIVE_PATH,
- });
- });
-
- it('renders a loading icon component', () => {
- expect(findLoadingIcon().props('size')).toBe('lg');
- });
-
- it('renders the notebook wrapper', () => {
- expect(findNotebookWrapper().exists()).toBe(true);
- expect(findNotebookWrapper().attributes('data-endpoint')).toBe(DEFAULT_BLOB_DATA.rawPath);
+ it('renders a Notebook component', () => {
+ expect(findNotebook().props('endpoint')).toBe(DEFAULT_BLOB_DATA.rawPath);
});
});
diff --git a/spec/frontend/repository/components/breadcrumbs_spec.js b/spec/frontend/repository/components/breadcrumbs_spec.js
index c2f34f79f89..8b7a7d91125 100644
--- a/spec/frontend/repository/components/breadcrumbs_spec.js
+++ b/spec/frontend/repository/components/breadcrumbs_spec.js
@@ -42,10 +42,6 @@ describe('Repository breadcrumbs component', () => {
const findUploadBlobModal = () => wrapper.findComponent(UploadBlobModal);
const findNewDirectoryModal = () => wrapper.findComponent(NewDirectoryModal);
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
path | linkCount
${'/'} | ${1}
diff --git a/spec/frontend/repository/components/delete_blob_modal_spec.js b/spec/frontend/repository/components/delete_blob_modal_spec.js
index b5996816ad8..9ca45bfb655 100644
--- a/spec/frontend/repository/components/delete_blob_modal_spec.js
+++ b/spec/frontend/repository/components/delete_blob_modal_spec.js
@@ -49,10 +49,6 @@ describe('DeleteBlobModal', () => {
await findCommitTextarea().vm.$emit('input', commitText);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders Modal component', () => {
createComponent();
@@ -187,7 +183,7 @@ describe('DeleteBlobModal', () => {
});
it('disables submit button', async () => {
- expect(findModal().props('actionPrimary').attributes[0]).toEqual(
+ expect(findModal().props('actionPrimary').attributes).toEqual(
expect.objectContaining({ disabled: true }),
);
});
@@ -207,7 +203,7 @@ describe('DeleteBlobModal', () => {
});
it('enables submit button', async () => {
- expect(findModal().props('actionPrimary').attributes[0]).toEqual(
+ expect(findModal().props('actionPrimary').attributes).toEqual(
expect.objectContaining({ disabled: false }),
);
});
diff --git a/spec/frontend/repository/components/directory_download_links_spec.js b/spec/frontend/repository/components/directory_download_links_spec.js
index 72c4165c2e9..3739829c759 100644
--- a/spec/frontend/repository/components/directory_download_links_spec.js
+++ b/spec/frontend/repository/components/directory_download_links_spec.js
@@ -16,10 +16,6 @@ function factory(currentPath) {
}
describe('Repository directory download links component', () => {
- afterEach(() => {
- vm.destroy();
- });
-
it.each`
path
${'app'}
diff --git a/spec/frontend/repository/components/fork_info_spec.js b/spec/frontend/repository/components/fork_info_spec.js
index f327a8cfae7..7a2b03a8d8f 100644
--- a/spec/frontend/repository/components/fork_info_spec.js
+++ b/spec/frontend/repository/components/fork_info_spec.js
@@ -1,42 +1,75 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import { GlSkeletonLoader, GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
+import { GlSkeletonLoader, GlIcon, GlLink, GlSprintf, GlButton, GlLoadingIcon } from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
+import { stubComponent } from 'helpers/stub_component';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import ForkInfo, { i18n } from '~/repository/components/fork_info.vue';
+import ConflictsModal from '~/repository/components/fork_sync_conflicts_modal.vue';
import forkDetailsQuery from '~/repository/queries/fork_details.query.graphql';
+import syncForkMutation from '~/repository/mutations/sync_fork.mutation.graphql';
import { propsForkInfo } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('ForkInfo component', () => {
let wrapper;
let mockResolver;
const forkInfoError = new Error('Something went wrong');
const projectId = 'gid://gitlab/Project/1';
+ const showMock = jest.fn();
+ const synchronizeFork = true;
Vue.use(VueApollo);
- const createCommitData = ({ ahead = 3, behind = 7 }) => {
+ const createForkDetailsData = (
+ forkDetails = { ahead: 3, behind: 7, isSyncing: false, hasConflicts: false },
+ ) => {
return {
data: {
- project: { id: projectId, forkDetails: { ahead, behind, __typename: 'ForkDetails' } },
+ project: { id: projectId, forkDetails },
},
};
};
- const createComponent = (props = {}, data = {}, isRequestFailed = false) => {
+ const createSyncForkDetailsData = (
+ forkDetails = { ahead: 3, behind: 7, isSyncing: false, hasConflicts: false },
+ ) => {
+ return {
+ data: {
+ projectSyncFork: { details: forkDetails, errors: [] },
+ },
+ };
+ };
+
+ const createComponent = (props = {}, data = {}, mutationData = {}, isRequestFailed = false) => {
mockResolver = isRequestFailed
? jest.fn().mockRejectedValue(forkInfoError)
- : jest.fn().mockResolvedValue(createCommitData(data));
+ : jest.fn().mockResolvedValue(createForkDetailsData(data));
wrapper = shallowMountExtended(ForkInfo, {
- apolloProvider: createMockApollo([[forkDetailsQuery, mockResolver]]),
+ apolloProvider: createMockApollo([
+ [forkDetailsQuery, mockResolver],
+ [syncForkMutation, jest.fn().mockResolvedValue(createSyncForkDetailsData(mutationData))],
+ ]),
propsData: { ...propsForkInfo, ...props },
- stubs: { GlSprintf },
+ stubs: {
+ GlSprintf,
+ GlButton,
+ ConflictsModal: stubComponent(ConflictsModal, {
+ template:
+ '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>',
+ methods: { show: showMock },
+ }),
+ },
+ provide: {
+ glFeatures: {
+ synchronizeFork,
+ },
+ },
});
return waitForPromises();
};
@@ -44,6 +77,8 @@ describe('ForkInfo component', () => {
const findLink = () => wrapper.findComponent(GlLink);
const findSkeleton = () => wrapper.findComponent(GlSkeletonLoader);
const findIcon = () => wrapper.findComponent(GlIcon);
+ const findUpdateForkButton = () => wrapper.findComponent(GlButton);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findDivergenceMessage = () => wrapper.findByTestId('divergence-message');
const findInaccessibleMessage = () => wrapper.findByTestId('inaccessible-project');
const findCompareLinks = () => findDivergenceMessage().findAllComponents(GlLink);
@@ -87,14 +122,50 @@ describe('ForkInfo component', () => {
expect(link.attributes('href')).toBe(propsForkInfo.sourcePath);
});
- it('renders unknown divergence message when divergence is unknown', async () => {
- await createComponent({}, { ahead: null, behind: null });
- expect(findDivergenceMessage().text()).toBe(i18n.unknown);
+ describe('Unknown divergence', () => {
+ beforeEach(async () => {
+ await createComponent(
+ {},
+ { ahead: null, behind: null, isSyncing: false, hasConflicts: false },
+ );
+ });
+
+ it('renders unknown divergence message when divergence is unknown', async () => {
+ expect(findDivergenceMessage().text()).toBe(i18n.unknown);
+ });
+
+ it('renders Update Fork button', async () => {
+ expect(findUpdateForkButton().exists()).toBe(true);
+ expect(findUpdateForkButton().text()).toBe(i18n.sync);
+ });
+ });
+
+ describe('Up to date divergence', () => {
+ beforeEach(async () => {
+ await createComponent({}, { ahead: 0, behind: 0, isSyncing: false, hasConflicts: false });
+ });
+
+ it('renders up to date message when fork is up to date', async () => {
+ expect(findDivergenceMessage().text()).toBe(i18n.upToDate);
+ });
+
+ it('does not render Update Fork button', async () => {
+ expect(findUpdateForkButton().exists()).toBe(false);
+ });
});
- it('renders up to date message when divergence is unknown', async () => {
- await createComponent({}, { ahead: 0, behind: 0 });
- expect(findDivergenceMessage().text()).toBe(i18n.upToDate);
+ describe('Limited visibility project', () => {
+ beforeEach(async () => {
+ await createComponent({}, null);
+ });
+
+ it('renders limited visibility messsage when forkDetails are empty', async () => {
+ expect(findDivergenceMessage().text()).toBe(i18n.limitedVisibility);
+ });
+
+ it('does not render Update Fork button', async () => {
+ expect(findUpdateForkButton().exists()).toBe(false);
+ });
});
describe.each([
@@ -104,6 +175,7 @@ describe('ForkInfo component', () => {
message: '3 commits behind, 7 commits ahead of the upstream repository.',
firstLink: propsForkInfo.behindComparePath,
secondLink: propsForkInfo.aheadComparePath,
+ hasButton: true,
},
{
ahead: 7,
@@ -111,6 +183,7 @@ describe('ForkInfo component', () => {
message: '7 commits ahead of the upstream repository.',
firstLink: propsForkInfo.aheadComparePath,
secondLink: '',
+ hasButton: false,
},
{
ahead: 0,
@@ -118,12 +191,13 @@ describe('ForkInfo component', () => {
message: '3 commits behind the upstream repository.',
firstLink: propsForkInfo.behindComparePath,
secondLink: '',
+ hasButton: true,
},
])(
'renders correct divergence message for ahead: $ahead, behind: $behind divergence commits',
- ({ ahead, behind, message, firstLink, secondLink }) => {
+ ({ ahead, behind, message, firstLink, secondLink, hasButton }) => {
beforeEach(async () => {
- await createComponent({}, { ahead, behind });
+ await createComponent({}, { ahead, behind, isSyncing: false, hasConflicts: false });
});
it('displays correct text', () => {
@@ -138,9 +212,38 @@ describe('ForkInfo component', () => {
expect(links.at(1).attributes('href')).toBe(secondLink);
}
});
+
+ it('renders Update Fork button when fork is behind', () => {
+ expect(findUpdateForkButton().exists()).toBe(hasButton);
+ if (hasButton) {
+ expect(findUpdateForkButton().text()).toBe(i18n.sync);
+ }
+ });
},
);
+ describe('when sync is not possible due to conflicts', () => {
+ it('opens Conflicts Modal', async () => {
+ await createComponent({}, { ahead: 7, behind: 3, isSyncing: false, hasConflicts: true });
+ findUpdateForkButton().vm.$emit('click');
+ expect(showMock).toHaveBeenCalled();
+ });
+ });
+
+ describe('projectSyncFork mutation', () => {
+ it('changes button to have loading state', async () => {
+ await createComponent(
+ {},
+ { ahead: 0, behind: 3, isSyncing: false, hasConflicts: false },
+ { ahead: 0, behind: 3, isSyncing: true, hasConflicts: false },
+ );
+ expect(findLoadingIcon().exists()).toBe(false);
+ findUpdateForkButton().vm.$emit('click');
+ await waitForPromises();
+ expect(findLoadingIcon().exists()).toBe(true);
+ });
+ });
+
it('renders alert with error message when request fails', async () => {
await createComponent({}, {}, true);
expect(createAlert).toHaveBeenCalledWith({
diff --git a/spec/frontend/repository/components/fork_suggestion_spec.js b/spec/frontend/repository/components/fork_suggestion_spec.js
index 36a48a3fdb8..a9e5c18c0a9 100644
--- a/spec/frontend/repository/components/fork_suggestion_spec.js
+++ b/spec/frontend/repository/components/fork_suggestion_spec.js
@@ -14,8 +14,6 @@ describe('ForkSuggestion component', () => {
beforeEach(() => createComponent());
- afterEach(() => wrapper.destroy());
-
const { i18n } = ForkSuggestion;
const findMessage = () => wrapper.findByTestId('message');
const findForkButton = () => wrapper.findByTestId('fork');
diff --git a/spec/frontend/repository/components/fork_sync_conflicts_modal_spec.js b/spec/frontend/repository/components/fork_sync_conflicts_modal_spec.js
new file mode 100644
index 00000000000..f97c970275b
--- /dev/null
+++ b/spec/frontend/repository/components/fork_sync_conflicts_modal_spec.js
@@ -0,0 +1,42 @@
+import { GlModal } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import ConflictsModal, { i18n } from '~/repository/components/fork_sync_conflicts_modal.vue';
+import { propsConflictsModal } from '../mock_data';
+
+describe('ConflictsModal', () => {
+ let wrapper;
+
+ function createComponent({ props = {} } = {}) {
+ wrapper = shallowMount(ConflictsModal, {
+ propsData: props,
+ stubs: { GlModal },
+ });
+ }
+
+ beforeEach(() => {
+ createComponent({ props: propsConflictsModal });
+ });
+
+ const findModal = () => wrapper.findComponent(GlModal);
+ const findInstructions = () => wrapper.findAll('[ data-testid="resolve-conflict-instructions"]');
+
+ it('renders a modal', () => {
+ expect(findModal().exists()).toBe(true);
+ });
+
+ it('passes title as a prop to a gl-modal component', () => {
+ expect(findModal().props().title).toBe(i18n.modalTitle);
+ });
+
+ it('renders a selection of markdown fields', () => {
+ expect(findInstructions().length).toBe(3);
+ });
+
+ it('renders a source url in a first intruction', () => {
+ expect(findInstructions().at(0).text()).toContain(propsConflictsModal.sourcePath);
+ });
+
+ it('renders default branch name in a first intruction', () => {
+ expect(findInstructions().at(0).text()).toContain(propsConflictsModal.sourceDefaultBranch);
+ });
+});
diff --git a/spec/frontend/repository/components/last_commit_spec.js b/spec/frontend/repository/components/last_commit_spec.js
index 7226e7baa36..f16edcb0b7c 100644
--- a/spec/frontend/repository/components/last_commit_spec.js
+++ b/spec/frontend/repository/components/last_commit_spec.js
@@ -6,6 +6,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import LastCommit from '~/repository/components/last_commit.vue';
+import SignatureBadge from '~/commit/components/signature_badge.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import pathLastCommitQuery from 'shared_queries/repository/path_last_commit.query.graphql';
import { refMock } from '../mock_data';
@@ -20,7 +21,7 @@ const findUserAvatarLink = () => wrapper.findComponent(UserAvatarLink);
const findLastCommitLabel = () => wrapper.findByTestId('last-commit-id-label');
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findCommitRowDescription = () => wrapper.find('.commit-row-description');
-const findStatusBox = () => wrapper.find('.signature-badge');
+const findStatusBox = () => wrapper.findComponent(SignatureBadge);
const findItemTitle = () => wrapper.find('.item-title');
const defaultPipelineEdges = [
@@ -56,7 +57,7 @@ const createCommitData = ({
pipelineEdges = defaultPipelineEdges,
author = defaultAuthor,
descriptionHtml = '',
- signatureHtml = null,
+ signature = null,
message = defaultMessage,
}) => {
return {
@@ -84,7 +85,7 @@ const createCommitData = ({
authorName: 'Test',
authorGravatar: 'https://test.com',
author,
- signatureHtml,
+ signature,
pipelines: {
__typename: 'PipelineConnection',
edges: pipelineEdges,
@@ -110,11 +111,13 @@ const createComponent = async (data = {}) => {
apolloProvider: createMockApollo([[pathLastCommitQuery, mockResolver]]),
propsData: { currentPath },
mixins: [{ data: () => ({ ref: refMock }) }],
+ stubs: {
+ SignatureBadge,
+ },
});
};
afterEach(() => {
- wrapper.destroy();
mockResolver = null;
});
@@ -204,23 +207,19 @@ describe('Repository last commit component', () => {
});
it('renders the signature HTML as returned by the backend', async () => {
+ const signatureResponse = {
+ __typename: 'GpgSignature',
+ gpgKeyPrimaryKeyid: 'xxx',
+ verificationStatus: 'VERIFIED',
+ };
createComponent({
- signatureHtml: `<a
- class="btn signature-badge"
- data-content="signature-content"
- data-html="true"
- data-placement="top"
- data-title="signature-title"
- data-toggle="popover"
- role="button"
- tabindex="0"
- ><span class="gl-badge badge badge-pill badge-success md">Verified</span></a>`,
+ signature: {
+ ...signatureResponse,
+ },
});
await waitForPromises();
- expect(findStatusBox().html()).toBe(
- `<a class="btn signature-badge" data-content="signature-content" data-html="true" data-placement="top" data-title="signature-title" data-toggle="popover" role="button" tabindex="0"><span class="gl-badge badge badge-pill badge-success md">Verified</span></a>`,
- );
+ expect(findStatusBox().props()).toMatchObject({ signature: signatureResponse });
});
it('sets correct CSS class if the commit message is empty', async () => {
diff --git a/spec/frontend/repository/components/new_directory_modal_spec.js b/spec/frontend/repository/components/new_directory_modal_spec.js
index 4e5c9a685c4..c920159375f 100644
--- a/spec/frontend/repository/components/new_directory_modal_spec.js
+++ b/spec/frontend/repository/components/new_directory_modal_spec.js
@@ -4,12 +4,12 @@ import { nextTick } from 'vue';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { visitUrl } from '~/lib/utils/url_utility';
import NewDirectoryModal from '~/repository/components/new_directory_modal.vue';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn(),
}));
@@ -76,10 +76,6 @@ describe('NewDirectoryModal', () => {
await waitForPromises();
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders modal component', () => {
createComponent();
@@ -185,10 +181,10 @@ describe('NewDirectoryModal', () => {
it('disables submit button', async () => {
await fillForm({ dirName: '', branchName: '', commitMessage: '' });
- expect(findModal().props('actionPrimary').attributes[0].disabled).toBe(true);
+ expect(findModal().props('actionPrimary').attributes.disabled).toBe(true);
});
- it('creates a flash error', async () => {
+ it('creates an alert error', async () => {
mock.onPost(initialProps.path).timeout();
await fillForm({ dirName: 'foo', branchName: 'master', commitMessage: 'foo' });
diff --git a/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap b/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap
deleted file mode 100644
index 48a4feca1e5..00000000000
--- a/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap
+++ /dev/null
@@ -1,42 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Repository file preview component renders file HTML 1`] = `
-<article
- class="file-holder limited-width-container readme-holder"
->
- <div
- class="js-file-title file-title-flex-parent"
- >
- <div
- class="file-header-content"
- >
- <gl-icon-stub
- name="doc-text"
- size="16"
- />
-
- <gl-link-stub
- href="http://test.com"
- >
- <strong>
- README.md
- </strong>
- </gl-link-stub>
- </div>
- </div>
-
- <div
- class="blob-viewer"
- data-qa-selector="blob_viewer_content"
- itemprop="about"
- >
- <div>
- <div
- class="blob"
- >
- test
- </div>
- </div>
- </div>
-</article>
-`;
diff --git a/spec/frontend/repository/components/preview/index_spec.js b/spec/frontend/repository/components/preview/index_spec.js
index d4c746b67d6..8a88c5b9c61 100644
--- a/spec/frontend/repository/components/preview/index_spec.js
+++ b/spec/frontend/repository/components/preview/index_spec.js
@@ -1,77 +1,60 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
import { handleLocationHash } from '~/lib/utils/common_utils';
+import waitForPromises from 'helpers/wait_for_promises';
import Preview from '~/repository/components/preview/index.vue';
+const PROPS_DATA = {
+ blob: {
+ webPath: 'http://test.com',
+ name: 'README.md',
+ },
+};
+
+const MOCK_README_DATA = {
+ __typename: 'ReadmeFile',
+ html: '<div class="blob">test</div>',
+};
+
jest.mock('~/lib/utils/common_utils');
-let vm;
-let $apollo;
+Vue.use(VueApollo);
+
+let wrapper;
+let mockApollo;
+let mockReadmeData;
-function factory(blob, loading) {
- $apollo = {
- queries: {
- readme: {
- query: jest.fn().mockReturnValue(Promise.resolve({})),
- loading,
- },
- },
- };
+const mockResolvers = {
+ Query: {
+ readme: () => mockReadmeData(),
+ },
+};
- vm = shallowMount(Preview, {
- propsData: {
- blob,
- },
- mocks: {
- $apollo,
- },
+function createComponent() {
+ mockApollo = createMockApollo([], mockResolvers);
+
+ return shallowMount(Preview, {
+ propsData: PROPS_DATA,
+ apolloProvider: mockApollo,
});
}
describe('Repository file preview component', () => {
- afterEach(() => {
- vm.destroy();
- });
-
- it('renders file HTML', async () => {
- factory({
- webPath: 'http://test.com',
- name: 'README.md',
- });
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- vm.setData({ readme: { html: '<div class="blob">test</div>' } });
-
- await nextTick();
- expect(vm.element).toMatchSnapshot();
+ beforeEach(() => {
+ mockReadmeData = jest.fn();
+ wrapper = createComponent();
+ mockReadmeData.mockResolvedValue(MOCK_README_DATA);
});
it('handles hash after render', async () => {
- factory({
- webPath: 'http://test.com',
- name: 'README.md',
- });
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- vm.setData({ readme: { html: '<div class="blob">test</div>' } });
-
- await nextTick();
+ await waitForPromises();
expect(handleLocationHash).toHaveBeenCalled();
});
it('renders loading icon', async () => {
- factory(
- {
- webPath: 'http://test.com',
- name: 'README.md',
- },
- true,
- );
-
- await nextTick();
- expect(vm.findComponent(GlLoadingIcon).exists()).toBe(true);
+ expect(wrapper.findComponent(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 8b987551b33..f7be367887c 100644
--- a/spec/frontend/repository/components/table/index_spec.js
+++ b/spec/frontend/repository/components/table/index_spec.js
@@ -88,10 +88,6 @@ function factory({ path, isLoading = false, hasMore = true, entries = {}, commit
const findTableRows = () => vm.findAllComponents(TableRow);
describe('Repository table component', () => {
- afterEach(() => {
- vm.destroy();
- });
-
it.each`
path | ref
${'/'} | ${'main'}
diff --git a/spec/frontend/repository/components/table/parent_row_spec.js b/spec/frontend/repository/components/table/parent_row_spec.js
index 03fb4242e40..77822a148b7 100644
--- a/spec/frontend/repository/components/table/parent_row_spec.js
+++ b/spec/frontend/repository/components/table/parent_row_spec.js
@@ -26,10 +26,6 @@ function factory(path, loadingPath) {
}
describe('Repository parent row component', () => {
- afterEach(() => {
- vm.destroy();
- });
-
it.each`
path | to
${'app'} | ${'/-/tree/main/'}
diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js
index 5d9138ab9cd..055616d6e8e 100644
--- a/spec/frontend/repository/components/table/row_spec.js
+++ b/spec/frontend/repository/components/table/row_spec.js
@@ -28,7 +28,7 @@ function factory(propsData = {}) {
rowNumber: 123,
},
directives: {
- GlHoverLoad: createMockDirective(),
+ GlHoverLoad: createMockDirective('gl-hover-load'),
},
mocks: {
$router,
@@ -47,10 +47,6 @@ describe('Repository table row component', () => {
const findRouterLink = () => vm.findComponent(RouterLinkStub);
const findIntersectionObserver = () => vm.findComponent(GlIntersectionObserver);
- afterEach(() => {
- vm.destroy();
- });
-
it('renders table row', async () => {
factory({
id: '1',
diff --git a/spec/frontend/repository/components/tree_content_spec.js b/spec/frontend/repository/components/tree_content_spec.js
index f694c8e9166..9597d8a7b77 100644
--- a/spec/frontend/repository/components/tree_content_spec.js
+++ b/spec/frontend/repository/components/tree_content_spec.js
@@ -1,12 +1,11 @@
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';
import TreeContent from 'jh_else_ce/repository/components/tree_content.vue';
import { loadCommits, isRequested, resetRequestedCommits } from '~/repository/commits_service';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { i18n } from '~/repository/constants';
import { graphQLErrors } from '../mock_data';
@@ -15,15 +14,15 @@ jest.mock('~/repository/commits_service', () => ({
isRequested: jest.fn(),
resetRequestedCommits: jest.fn(),
}));
-jest.mock('~/flash');
+jest.mock('~/alert');
let vm;
let $apollo;
const mockResponse = jest.fn().mockReturnValue(Promise.resolve({ data: {} }));
-function factory(path, appoloMockResponse = mockResponse) {
+function factory(path, apolloMockResponse = mockResponse) {
$apollo = {
- query: appoloMockResponse,
+ query: apolloMockResponse,
};
vm = shallowMount(TreeContent, {
@@ -33,22 +32,12 @@ function factory(path, appoloMockResponse = mockResponse) {
mocks: {
$apollo,
},
- provide: {
- glFeatures: {
- increasePageSizeExponentially: true,
- paginatedTreeGraphqlQuery: true,
- },
- },
});
}
describe('Repository table component', () => {
const findFileTable = () => vm.findComponent(FileTable);
- afterEach(() => {
- vm.destroy();
- });
-
it('renders file preview', async () => {
factory('/');
@@ -171,37 +160,6 @@ describe('Repository table component', () => {
expect(findFileTable().props('hasMore')).toBe(limitReached);
});
-
- it.each`
- fetchCounter | pageSize
- ${0} | ${10}
- ${2} | ${30}
- ${4} | ${50}
- ${6} | ${70}
- ${8} | ${90}
- ${10} | ${100}
- ${20} | ${100}
- ${100} | ${100}
- ${200} | ${100}
- `('exponentially increases page size, to a maximum of 100', ({ fetchCounter, pageSize }) => {
- factory('/');
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- vm.setData({ fetchCounter });
-
- vm.vm.fetchFiles();
-
- expect($apollo.query).toHaveBeenCalledWith({
- query: paginatedTreeQuery,
- variables: {
- pageSize,
- nextPageCursor: '',
- path: '/',
- projectPath: '',
- ref: '',
- },
- });
- });
});
describe('commit data', () => {
diff --git a/spec/frontend/repository/components/upload_blob_modal_spec.js b/spec/frontend/repository/components/upload_blob_modal_spec.js
index 9de0666f27a..319321cfcb4 100644
--- a/spec/frontend/repository/components/upload_blob_modal_spec.js
+++ b/spec/frontend/repository/components/upload_blob_modal_spec.js
@@ -4,13 +4,13 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { visitUrl } from '~/lib/utils/url_utility';
import UploadBlobModal from '~/repository/components/upload_blob_modal.vue';
import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn(),
joinPaths: () => '/new_upload',
@@ -53,14 +53,9 @@ describe('UploadBlobModal', () => {
const findBranchName = () => wrapper.findComponent(GlFormInput);
const findMrToggle = () => wrapper.findComponent(GlToggle);
const findUploadDropzone = () => wrapper.findComponent(UploadDropzone);
- const actionButtonDisabledState = () => findModal().props('actionPrimary').attributes[0].disabled;
- const cancelButtonDisabledState = () => findModal().props('actionCancel').attributes[0].disabled;
- const actionButtonLoadingState = () => findModal().props('actionPrimary').attributes[0].loading;
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
+ const actionButtonDisabledState = () => findModal().props('actionPrimary').attributes.disabled;
+ const cancelButtonDisabledState = () => findModal().props('actionCancel').attributes.disabled;
+ const actionButtonLoadingState = () => findModal().props('actionPrimary').attributes.loading;
describe.each`
canPushCode | displayBranchName | displayForkedBranchMessage
@@ -110,9 +105,7 @@ describe('UploadBlobModal', () => {
if (canPushCode) {
describe('when changing the branch name', () => {
it('displays the MR toggle', 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({ target: 'Not main' });
+ createComponent({ targetBranch: 'Not main' });
await nextTick();
@@ -123,12 +116,10 @@ describe('UploadBlobModal', () => {
describe('completed form', () => {
beforeEach(() => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- file: { type: 'jpg' },
- filePreviewURL: 'http://file.com?format=jpg',
- });
+ findUploadDropzone().vm.$emit(
+ 'change',
+ new File(['http://file.com?format=jpg'], 'file.jpg'),
+ );
});
it('enables the upload button when the form is completed', () => {
@@ -184,7 +175,7 @@ describe('UploadBlobModal', () => {
await waitForPromises();
});
- it('creates a flash error', () => {
+ it('creates an alert error', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'Error uploading file. Please try again.',
});
@@ -199,13 +190,6 @@ describe('UploadBlobModal', () => {
);
describe('blob file submission type', () => {
- const submitForm = async () => {
- wrapper.vm.uploadFile = jest.fn();
- wrapper.vm.replaceFile = jest.fn();
- wrapper.vm.submitForm();
- await nextTick();
- };
-
const submitRequest = async () => {
mock = new MockAdapter(axios);
findModal().vm.$emit('primary', mockEvent);
@@ -225,13 +209,6 @@ describe('UploadBlobModal', () => {
expect(findModal().props('actionPrimary').text).toBe('Upload file');
});
- it('calls the default uploadFile when the form submit', async () => {
- await submitForm();
-
- expect(wrapper.vm.uploadFile).toHaveBeenCalled();
- expect(wrapper.vm.replaceFile).not.toHaveBeenCalled();
- });
-
it('makes a POST request', async () => {
await submitRequest();
@@ -261,13 +238,6 @@ describe('UploadBlobModal', () => {
expect(findModal().props('actionPrimary').text).toBe(primaryBtnText);
});
- it('calls the replaceFile when the form submit', async () => {
- await submitForm();
-
- expect(wrapper.vm.replaceFile).toHaveBeenCalled();
- expect(wrapper.vm.uploadFile).not.toHaveBeenCalled();
- });
-
it('makes a PUT request', async () => {
await submitRequest();
diff --git a/spec/frontend/repository/mixins/highlight_mixin_spec.js b/spec/frontend/repository/mixins/highlight_mixin_spec.js
index 7c48fe440d2..5f872749581 100644
--- a/spec/frontend/repository/mixins/highlight_mixin_spec.js
+++ b/spec/frontend/repository/mixins/highlight_mixin_spec.js
@@ -44,8 +44,6 @@ describe('HighlightMixin', () => {
beforeEach(() => createComponent());
- afterEach(() => wrapper.destroy());
-
describe('initHighlightWorker', () => {
const firstSeventyLines = contentArray.slice(0, LINES_PER_CHUNK).join('\n');
diff --git a/spec/frontend/repository/mock_data.js b/spec/frontend/repository/mock_data.js
index 04ffe52bc3f..418a93a10cc 100644
--- a/spec/frontend/repository/mock_data.js
+++ b/spec/frontend/repository/mock_data.js
@@ -126,3 +126,9 @@ export const propsForkInfo = {
aheadComparePath: '/nataliia/myGitLab/-/compare/main...ref?from_project_id=1',
behindComparePath: 'gitlab-org/gitlab/-/compare/ref...main?from_project_id=2',
};
+
+export const propsConflictsModal = {
+ sourceDefaultBranch: 'branch-name',
+ sourceName: 'source-name',
+ sourcePath: 'path/to/project',
+};
diff --git a/spec/frontend/repository/pages/blob_spec.js b/spec/frontend/repository/pages/blob_spec.js
index 4fe6188370e..366523e2b8b 100644
--- a/spec/frontend/repository/pages/blob_spec.js
+++ b/spec/frontend/repository/pages/blob_spec.js
@@ -16,10 +16,6 @@ describe('Repository blob page component', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('has a Blob Content Viewer component', () => {
expect(findBlobContentViewer().exists()).toBe(true);
expect(findBlobContentViewer().props('path')).toBe(path);
diff --git a/spec/frontend/repository/pages/index_spec.js b/spec/frontend/repository/pages/index_spec.js
index 559257d414c..e50557e7d61 100644
--- a/spec/frontend/repository/pages/index_spec.js
+++ b/spec/frontend/repository/pages/index_spec.js
@@ -13,8 +13,6 @@ describe('Repository index page component', () => {
}
afterEach(() => {
- wrapper.destroy();
-
updateElementsVisibility.mockClear();
});
diff --git a/spec/frontend/repository/pages/tree_spec.js b/spec/frontend/repository/pages/tree_spec.js
index 36662696c91..b1529d77c7d 100644
--- a/spec/frontend/repository/pages/tree_spec.js
+++ b/spec/frontend/repository/pages/tree_spec.js
@@ -12,8 +12,6 @@ describe('Repository tree page component', () => {
}
afterEach(() => {
- wrapper.destroy();
-
updateElementsVisibility.mockClear();
});
diff --git a/spec/frontend/saved_replies/components/__snapshots__/list_item_spec.js.snap b/spec/frontend/saved_replies/components/__snapshots__/list_item_spec.js.snap
index 3abdfcdaf20..204afc744e7 100644
--- a/spec/frontend/saved_replies/components/__snapshots__/list_item_spec.js.snap
+++ b/spec/frontend/saved_replies/components/__snapshots__/list_item_spec.js.snap
@@ -7,9 +7,39 @@ exports[`Saved replies list item component renders list item 1`] = `
<div
class="gl-display-flex gl-align-items-center"
>
- <strong>
+ <strong
+ data-testid="saved-reply-name"
+ >
test
</strong>
+
+ <div
+ class="gl-ml-auto"
+ >
+ <gl-button-stub
+ aria-label="Edit"
+ buttontextclasses=""
+ category="primary"
+ class="gl-mr-3"
+ data-testid="saved-reply-edit-btn"
+ icon="pencil"
+ size="medium"
+ title="Edit"
+ to="[object Object]"
+ variant="default"
+ />
+
+ <gl-button-stub
+ aria-label="Delete"
+ buttontextclasses=""
+ category="secondary"
+ data-testid="saved-reply-delete-btn"
+ icon="remove"
+ size="medium"
+ title="Delete"
+ variant="danger"
+ />
+ </div>
</div>
<div
@@ -17,5 +47,21 @@ exports[`Saved replies list item component renders list item 1`] = `
>
/assign_reviewer
</div>
+
+ <gl-modal-stub
+ actionprimary="[object Object]"
+ actionsecondary="[object Object]"
+ arialabel=""
+ dismisslabel="Close"
+ modalclass=""
+ modalid="delete-saved-reply-2"
+ size="sm"
+ title="Delete saved reply"
+ titletag="h4"
+ >
+ <gl-sprintf-stub
+ message="Are you sure you want to delete %{name}? This action cannot be undone."
+ />
+ </gl-modal-stub>
</li>
`;
diff --git a/spec/frontend/saved_replies/components/form_spec.js b/spec/frontend/saved_replies/components/form_spec.js
new file mode 100644
index 00000000000..adeda498e6f
--- /dev/null
+++ b/spec/frontend/saved_replies/components/form_spec.js
@@ -0,0 +1,144 @@
+import Vue, { nextTick } from 'vue';
+import { mount } from '@vue/test-utils';
+import { GlAlert } from '@gitlab/ui';
+import VueApollo from 'vue-apollo';
+import createdSavedReplyResponse from 'test_fixtures/graphql/saved_replies/create_saved_reply.mutation.graphql.json';
+import createdSavedReplyErrorResponse from 'test_fixtures/graphql/saved_replies/create_saved_reply_with_errors.mutation.graphql.json';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import Form from '~/saved_replies/components/form.vue';
+import createSavedReplyMutation from '~/saved_replies/queries/create_saved_reply.mutation.graphql';
+import updateSavedReplyMutation from '~/saved_replies/queries/update_saved_reply.mutation.graphql';
+
+let wrapper;
+let createSavedReplyResponseSpy;
+let updateSavedReplyResponseSpy;
+
+function createMockApolloProvider(response) {
+ Vue.use(VueApollo);
+
+ createSavedReplyResponseSpy = jest.fn().mockResolvedValue(response);
+ updateSavedReplyResponseSpy = jest.fn().mockResolvedValue(response);
+
+ const requestHandlers = [
+ [createSavedReplyMutation, createSavedReplyResponseSpy],
+ [updateSavedReplyMutation, updateSavedReplyResponseSpy],
+ ];
+
+ return createMockApollo(requestHandlers);
+}
+
+function createComponent(id = null, response = createdSavedReplyResponse) {
+ const mockApollo = createMockApolloProvider(response);
+
+ return mount(Form, {
+ propsData: {
+ id,
+ },
+ apolloProvider: mockApollo,
+ });
+}
+
+const findSavedReplyNameInput = () => wrapper.find('[data-testid="saved-reply-name-input"]');
+const findSavedReplyNameFormGroup = () =>
+ wrapper.find('[data-testid="saved-reply-name-form-group"]');
+const findSavedReplyContentInput = () => wrapper.find('[data-testid="saved-reply-content-input"]');
+const findSavedReplyContentFormGroup = () =>
+ wrapper.find('[data-testid="saved-reply-content-form-group"]');
+const findSavedReplyFrom = () => wrapper.find('[data-testid="saved-reply-form"]');
+const findAlerts = () => wrapper.findAllComponents(GlAlert);
+const findSubmitBtn = () => wrapper.find('[data-testid="saved-reply-form-submit-btn"]');
+
+describe('Saved replies form component', () => {
+ describe('create saved reply', () => {
+ it('calls apollo mutation', async () => {
+ wrapper = createComponent();
+
+ findSavedReplyNameInput().setValue('Test');
+ findSavedReplyContentInput().setValue('Test content');
+ findSavedReplyFrom().trigger('submit');
+
+ await waitForPromises();
+
+ expect(createSavedReplyResponseSpy).toHaveBeenCalledWith({
+ id: null,
+ content: 'Test content',
+ name: 'Test',
+ });
+ });
+
+ it('does not submit when form validation fails', async () => {
+ wrapper = createComponent();
+
+ findSavedReplyFrom().trigger('submit');
+
+ await waitForPromises();
+
+ expect(createSavedReplyResponseSpy).not.toHaveBeenCalled();
+ });
+
+ it.each`
+ findFormGroup | findInput | fieldName
+ ${findSavedReplyNameFormGroup} | ${findSavedReplyContentInput} | ${'name'}
+ ${findSavedReplyContentFormGroup} | ${findSavedReplyNameInput} | ${'content'}
+ `('shows errors for empty $fieldName input', async ({ findFormGroup, findInput }) => {
+ wrapper = createComponent(null, createdSavedReplyErrorResponse);
+
+ findInput().setValue('Test');
+ findSavedReplyFrom().trigger('submit');
+
+ await waitForPromises();
+
+ expect(findFormGroup().classes('is-invalid')).toBe(true);
+ });
+
+ it('displays errors when mutation fails', async () => {
+ wrapper = createComponent(null, createdSavedReplyErrorResponse);
+
+ findSavedReplyNameInput().setValue('Test');
+ findSavedReplyContentInput().setValue('Test content');
+ findSavedReplyFrom().trigger('submit');
+
+ await waitForPromises();
+
+ const { errors } = createdSavedReplyErrorResponse;
+ const alertMessages = findAlerts().wrappers.map((x) => x.text());
+
+ expect(alertMessages).toEqual(errors.map((x) => x.message));
+ });
+
+ it('shows loading state when saving', async () => {
+ wrapper = createComponent();
+
+ findSavedReplyNameInput().setValue('Test');
+ findSavedReplyContentInput().setValue('Test content');
+ findSavedReplyFrom().trigger('submit');
+
+ await nextTick();
+
+ expect(findSubmitBtn().props('loading')).toBe(true);
+
+ await waitForPromises();
+
+ expect(findSubmitBtn().props('loading')).toBe(false);
+ });
+ });
+
+ describe('updates saved reply', () => {
+ it('calls apollo mutation', async () => {
+ wrapper = createComponent('1');
+
+ findSavedReplyNameInput().setValue('Test');
+ findSavedReplyContentInput().setValue('Test content');
+ findSavedReplyFrom().trigger('submit');
+
+ await waitForPromises();
+
+ expect(updateSavedReplyResponseSpy).toHaveBeenCalledWith({
+ id: '1',
+ content: 'Test content',
+ name: 'Test',
+ });
+ });
+ });
+});
diff --git a/spec/frontend/saved_replies/components/list_item_spec.js b/spec/frontend/saved_replies/components/list_item_spec.js
index cad1000473b..f1ecdfecb15 100644
--- a/spec/frontend/saved_replies/components/list_item_spec.js
+++ b/spec/frontend/saved_replies/components/list_item_spec.js
@@ -1,22 +1,50 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
import { shallowMount } from '@vue/test-utils';
+import { GlModal } from '@gitlab/ui';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { createMockDirective } from 'helpers/vue_mock_directive';
+import waitForPromises from 'helpers/wait_for_promises';
import ListItem from '~/saved_replies/components/list_item.vue';
+import deleteSavedReplyMutation from '~/saved_replies/queries/delete_saved_reply.mutation.graphql';
let wrapper;
+let deleteSavedReplyMutationResponse;
function createComponent(propsData = {}) {
+ Vue.use(VueApollo);
+
+ deleteSavedReplyMutationResponse = jest
+ .fn()
+ .mockResolvedValue({ data: { savedReplyDestroy: { errors: [] } } });
+
return shallowMount(ListItem, {
propsData,
+ directives: {
+ GlModal: createMockDirective('gl-modal'),
+ },
+ apolloProvider: createMockApollo([
+ [deleteSavedReplyMutation, deleteSavedReplyMutationResponse],
+ ]),
});
}
describe('Saved replies list item component', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders list item', async () => {
wrapper = createComponent({ reply: { name: 'test', content: '/assign_reviewer' } });
expect(wrapper.element).toMatchSnapshot();
});
+
+ describe('delete button', () => {
+ it('calls Apollo mutate', async () => {
+ wrapper = createComponent({ reply: { name: 'test', content: '/assign_reviewer', id: 1 } });
+
+ wrapper.findComponent(GlModal).vm.$emit('primary');
+
+ await waitForPromises();
+
+ expect(deleteSavedReplyMutationResponse).toHaveBeenCalledWith({ id: 1 });
+ });
+ });
});
diff --git a/spec/frontend/saved_replies/components/list_spec.js b/spec/frontend/saved_replies/components/list_spec.js
index 66e9ddfe148..bc69cb852e0 100644
--- a/spec/frontend/saved_replies/components/list_spec.js
+++ b/spec/frontend/saved_replies/components/list_spec.js
@@ -1,61 +1,39 @@
-import Vue from 'vue';
import { mount } from '@vue/test-utils';
-import VueApollo from 'vue-apollo';
import noSavedRepliesResponse from 'test_fixtures/graphql/saved_replies/saved_replies_empty.query.graphql.json';
import savedRepliesResponse from 'test_fixtures/graphql/saved_replies/saved_replies.query.graphql.json';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import waitForPromises from 'helpers/wait_for_promises';
import List from '~/saved_replies/components/list.vue';
import ListItem from '~/saved_replies/components/list_item.vue';
-import savedRepliesQuery from '~/saved_replies/queries/saved_replies.query.graphql';
let wrapper;
-function createMockApolloProvider(response) {
- Vue.use(VueApollo);
-
- const requestHandlers = [[savedRepliesQuery, jest.fn().mockResolvedValue(response)]];
-
- return createMockApollo(requestHandlers);
-}
-
-function createComponent(options = {}) {
- const { mockApollo } = options;
+function createComponent(res = {}) {
+ const { savedReplies } = res.data.currentUser;
return mount(List, {
- apolloProvider: mockApollo,
+ propsData: {
+ savedReplies: savedReplies.nodes,
+ pageInfo: savedReplies.pageInfo,
+ count: savedReplies.count,
+ },
});
}
describe('Saved replies list component', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('does not render any list items when response is empty', async () => {
- const mockApollo = createMockApolloProvider(noSavedRepliesResponse);
- wrapper = createComponent({ mockApollo });
-
- await waitForPromises();
+ it('does not render any list items when response is empty', () => {
+ wrapper = createComponent(noSavedRepliesResponse);
expect(wrapper.findAllComponents(ListItem).length).toBe(0);
});
- it('render saved replies count', async () => {
- const mockApollo = createMockApolloProvider(savedRepliesResponse);
- wrapper = createComponent({ mockApollo });
-
- await waitForPromises();
+ it('render saved replies count', () => {
+ wrapper = createComponent(savedRepliesResponse);
expect(wrapper.find('[data-testid="title"]').text()).toEqual('My saved replies (2)');
});
- it('renders list of saved replies', async () => {
- const mockApollo = createMockApolloProvider(savedRepliesResponse);
+ it('renders list of saved replies', () => {
const savedReplies = savedRepliesResponse.data.currentUser.savedReplies.nodes;
- wrapper = createComponent({ mockApollo });
-
- await waitForPromises();
+ wrapper = createComponent(savedRepliesResponse);
expect(wrapper.findAllComponents(ListItem).length).toBe(2);
expect(wrapper.findAllComponents(ListItem).at(0).props('reply')).toEqual(
diff --git a/spec/frontend/saved_replies/pages/index_spec.js b/spec/frontend/saved_replies/pages/index_spec.js
new file mode 100644
index 00000000000..771025d64ec
--- /dev/null
+++ b/spec/frontend/saved_replies/pages/index_spec.js
@@ -0,0 +1,45 @@
+import Vue from 'vue';
+import { mount } from '@vue/test-utils';
+import VueApollo from 'vue-apollo';
+import savedRepliesResponse from 'test_fixtures/graphql/saved_replies/saved_replies.query.graphql.json';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import IndexPage from '~/saved_replies/pages/index.vue';
+import ListItem from '~/saved_replies/components/list_item.vue';
+import savedRepliesQuery from '~/saved_replies/queries/saved_replies.query.graphql';
+
+let wrapper;
+
+function createMockApolloProvider(response) {
+ Vue.use(VueApollo);
+
+ const requestHandlers = [[savedRepliesQuery, jest.fn().mockResolvedValue(response)]];
+
+ return createMockApollo(requestHandlers);
+}
+
+function createComponent(options = {}) {
+ const { mockApollo } = options;
+
+ return mount(IndexPage, {
+ apolloProvider: mockApollo,
+ });
+}
+
+describe('Saved replies index page component', () => {
+ it('renders list of saved replies', async () => {
+ const mockApollo = createMockApolloProvider(savedRepliesResponse);
+ const savedReplies = savedRepliesResponse.data.currentUser.savedReplies.nodes;
+ wrapper = createComponent({ mockApollo });
+
+ await waitForPromises();
+
+ expect(wrapper.findAllComponents(ListItem).length).toBe(2);
+ expect(wrapper.findAllComponents(ListItem).at(0).props('reply')).toEqual(
+ expect.objectContaining(savedReplies[0]),
+ );
+ expect(wrapper.findAllComponents(ListItem).at(1).props('reply')).toEqual(
+ expect.objectContaining(savedReplies[1]),
+ );
+ });
+});
diff --git a/spec/frontend/search/mock_data.js b/spec/frontend/search/mock_data.js
index fb9c0a93907..0aa4f0e1c84 100644
--- a/spec/frontend/search/mock_data.js
+++ b/spec/frontend/search/mock_data.js
@@ -653,3 +653,10 @@ export const TEST_FILTER_DATA = {
JSON: { label: 'JSON', value: 'JSON', count: 15 },
},
};
+
+export const SMALL_MOCK_AGGREGATIONS = [
+ {
+ name: 'language',
+ buckets: TEST_RAW_BUCKETS,
+ },
+];
diff --git a/spec/frontend/search/sidebar/components/app_spec.js b/spec/frontend/search/sidebar/components/app_spec.js
index 83302b90233..8a35ae96d5e 100644
--- a/spec/frontend/search/sidebar/components/app_spec.js
+++ b/spec/frontend/search/sidebar/components/app_spec.js
@@ -17,6 +17,10 @@ describe('GlobalSearchSidebar', () => {
resetQuery: jest.fn(),
};
+ const getterSpies = {
+ currentScope: jest.fn(() => 'issues'),
+ };
+
const createComponent = (initialState, featureFlags) => {
const store = new Vuex.Store({
state: {
@@ -24,6 +28,7 @@ describe('GlobalSearchSidebar', () => {
...initialState,
},
actions: actionSpies,
+ getters: getterSpies,
});
wrapper = shallowMount(GlobalSearchSidebar, {
@@ -52,22 +57,23 @@ describe('GlobalSearchSidebar', () => {
});
describe.each`
- scope | showFilters | ShowsLanguage
+ scope | showFilters | showsLanguage
${'issues'} | ${true} | ${false}
${'merge_requests'} | ${true} | ${false}
${'projects'} | ${false} | ${false}
${'blobs'} | ${false} | ${true}
- `('sidebar scope: $scope', ({ scope, showFilters, ShowsLanguage }) => {
+ `('sidebar scope: $scope', ({ scope, showFilters, showsLanguage }) => {
beforeEach(() => {
- createComponent({ urlQuery: { scope } }, { searchBlobsLanguageAggregation: true });
+ getterSpies.currentScope = jest.fn(() => scope);
+ createComponent({ urlQuery: { scope } });
});
it(`${!showFilters ? "doesn't" : ''} shows filters`, () => {
expect(findFilters().exists()).toBe(showFilters);
});
- it(`${!ShowsLanguage ? "doesn't" : ''} shows language filters`, () => {
- expect(findLanguageAggregation().exists()).toBe(ShowsLanguage);
+ it(`${!showsLanguage ? "doesn't" : ''} shows language filters`, () => {
+ expect(findLanguageAggregation().exists()).toBe(showsLanguage);
});
});
@@ -80,22 +86,4 @@ describe('GlobalSearchSidebar', () => {
});
});
});
-
- describe('when search_blobs_language_aggregation is enabled', () => {
- beforeEach(() => {
- createComponent({ urlQuery: { scope: 'blobs' } }, { searchBlobsLanguageAggregation: true });
- });
- it('shows the language filter', () => {
- expect(findLanguageAggregation().exists()).toBe(true);
- });
- });
-
- describe('when search_blobs_language_aggregation is disabled', () => {
- beforeEach(() => {
- createComponent({ urlQuery: { scope: 'blobs' } }, { searchBlobsLanguageAggregation: false });
- });
- it('hides the language filter', () => {
- expect(findLanguageAggregation().exists()).toBe(false);
- });
- });
});
diff --git a/spec/frontend/search/sidebar/components/checkbox_filter_spec.js b/spec/frontend/search/sidebar/components/checkbox_filter_spec.js
index 82017754b23..f7b35c7bb14 100644
--- a/spec/frontend/search/sidebar/components/checkbox_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/checkbox_filter_spec.js
@@ -17,8 +17,12 @@ describe('CheckboxFilter', () => {
setQuery: jest.fn(),
};
+ const getterSpies = {
+ queryLanguageFilters: jest.fn(() => []),
+ };
+
const defaultProps = {
- filterData: convertFiltersData(MOCK_LANGUAGE_AGGREGATIONS_BUCKETS),
+ filtersData: convertFiltersData(MOCK_LANGUAGE_AGGREGATIONS_BUCKETS),
};
const createComponent = () => {
@@ -27,6 +31,7 @@ describe('CheckboxFilter', () => {
query: MOCK_QUERY,
},
actions: actionSpies,
+ getters: getterSpies,
});
wrapper = shallowMountExtended(CheckboxFilter, {
@@ -73,7 +78,7 @@ describe('CheckboxFilter', () => {
describe('actions', () => {
it('triggers setQuery', () => {
const filter =
- defaultProps.filterData.filters[Object.keys(defaultProps.filterData.filters)[0]].value;
+ defaultProps.filtersData.filters[Object.keys(defaultProps.filtersData.filters)[0]].value;
findFormCheckboxGroup().vm.$emit('input', filter);
expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), {
diff --git a/spec/frontend/search/sidebar/components/filters_spec.js b/spec/frontend/search/sidebar/components/filters_spec.js
index 7e564bfa005..51c7bdd9609 100644
--- a/spec/frontend/search/sidebar/components/filters_spec.js
+++ b/spec/frontend/search/sidebar/components/filters_spec.js
@@ -17,6 +17,10 @@ describe('GlobalSearchSidebarFilters', () => {
resetQuery: jest.fn(),
};
+ const defaultGetters = {
+ currentScope: () => 'issues',
+ };
+
const createComponent = (initialState) => {
const store = new Vuex.Store({
state: {
@@ -24,6 +28,7 @@ describe('GlobalSearchSidebarFilters', () => {
...initialState,
},
actions: actionSpies,
+ getters: defaultGetters,
});
wrapper = shallowMount(ResultsFilters, {
@@ -31,10 +36,6 @@ describe('GlobalSearchSidebarFilters', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findSidebarForm = () => wrapper.find('form');
const findStatusFilter = () => wrapper.findComponent(StatusFilter);
const findConfidentialityFilter = () => wrapper.findComponent(ConfidentialityFilter);
@@ -142,7 +143,11 @@ describe('GlobalSearchSidebarFilters', () => {
${'blobs'} | ${false}
`(`ConfidentialityFilter`, ({ scope, showFilter }) => {
beforeEach(() => {
- createComponent({ urlQuery: { scope } });
+ defaultGetters.currentScope = () => scope;
+ createComponent();
+ });
+ afterEach(() => {
+ defaultGetters.currentScope = () => 'issues';
});
it(`does${showFilter ? '' : ' not'} render when scope is ${scope}`, () => {
@@ -162,7 +167,11 @@ describe('GlobalSearchSidebarFilters', () => {
${'blobs'} | ${false}
`(`StatusFilter`, ({ scope, showFilter }) => {
beforeEach(() => {
- createComponent({ urlQuery: { scope } });
+ defaultGetters.currentScope = () => scope;
+ createComponent();
+ });
+ afterEach(() => {
+ defaultGetters.currentScope = () => 'issues';
});
it(`does${showFilter ? '' : ' not'} render when scope is ${scope}`, () => {
diff --git a/spec/frontend/search/sidebar/components/language_filters_spec.js b/spec/frontend/search/sidebar/components/language_filter_spec.js
index e297d1c33b0..17656ba749b 100644
--- a/spec/frontend/search/sidebar/components/language_filters_spec.js
+++ b/spec/frontend/search/sidebar/components/language_filter_spec.js
@@ -22,7 +22,8 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
};
const getterSpies = {
- langugageAggregationBuckets: jest.fn(() => MOCK_LANGUAGE_AGGREGATIONS_BUCKETS),
+ languageAggregationBuckets: jest.fn(() => MOCK_LANGUAGE_AGGREGATIONS_BUCKETS),
+ queryLanguageFilters: jest.fn(() => []),
};
const createComponent = (initialState) => {
@@ -48,6 +49,7 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
const findForm = () => wrapper.findComponent(GlForm);
const findCheckboxFilter = () => wrapper.findComponent(CheckboxFilter);
const findApplyButton = () => wrapper.findByTestId('apply-button');
+ const findResetButton = () => wrapper.findByTestId('reset-button');
const findShowMoreButton = () => wrapper.findByTestId('show-more-button');
const findAlert = () => wrapper.findComponent(GlAlert);
const findAllCheckboxes = () => wrapper.findAllComponents(GlFormCheckbox);
@@ -84,6 +86,25 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
});
});
+ describe('resetButton', () => {
+ describe.each`
+ description | sidebarDirty | queryFilters | isDisabled
+ ${'sidebar dirty only'} | ${true} | ${[]} | ${undefined}
+ ${'query filters only'} | ${false} | ${['JSON', 'C']} | ${undefined}
+ ${'sidebar dirty and query filters'} | ${true} | ${['JSON', 'C']} | ${undefined}
+ ${'no sidebar and no query filters'} | ${false} | ${[]} | ${'true'}
+ `('$description', ({ sidebarDirty, queryFilters, isDisabled }) => {
+ beforeEach(() => {
+ getterSpies.queryLanguageFilters = jest.fn(() => queryFilters);
+ createComponent({ sidebarDirty, query: { ...MOCK_QUERY, language: queryFilters } });
+ });
+
+ it(`button is ${isDisabled ? 'enabled' : 'disabled'}`, () => {
+ expect(findResetButton().attributes('disabled')).toBe(isDisabled);
+ });
+ });
+ });
+
describe('ApplyButton', () => {
describe('when sidebarDirty is false', () => {
beforeEach(() => {
@@ -135,8 +156,8 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
createComponent({});
});
- it('uses getter langugageAggregationBuckets', () => {
- expect(getterSpies.langugageAggregationBuckets).toHaveBeenCalled();
+ it('uses getter languageAggregationBuckets', () => {
+ expect(getterSpies.languageAggregationBuckets).toHaveBeenCalled();
});
it('uses action fetchLanguageAggregation', () => {
diff --git a/spec/frontend/search/sidebar/components/radio_filter_spec.js b/spec/frontend/search/sidebar/components/radio_filter_spec.js
index 94d529348a9..47235b828c3 100644
--- a/spec/frontend/search/sidebar/components/radio_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/radio_filter_spec.js
@@ -16,6 +16,10 @@ describe('RadioFilter', () => {
setQuery: jest.fn(),
};
+ const defaultGetters = {
+ currentScope: jest.fn(() => 'issues'),
+ };
+
const defaultProps = {
filterData: stateFilterData,
};
@@ -27,6 +31,7 @@ describe('RadioFilter', () => {
...initialState,
},
actions: actionSpies,
+ getters: defaultGetters,
});
wrapper = shallowMount(RadioFilter, {
@@ -38,11 +43,6 @@ describe('RadioFilter', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findGlRadioButtonGroup = () => wrapper.findComponent(GlFormRadioGroup);
const findGlRadioButtons = () => findGlRadioButtonGroup().findAllComponents(GlFormRadio);
const findGlRadioButtonsText = () => findGlRadioButtons().wrappers.map((w) => w.text());
diff --git a/spec/frontend/search/sidebar/components/scope_navigation_spec.js b/spec/frontend/search/sidebar/components/scope_navigation_spec.js
index 23c158239dc..3b2d528e1d7 100644
--- a/spec/frontend/search/sidebar/components/scope_navigation_spec.js
+++ b/spec/frontend/search/sidebar/components/scope_navigation_spec.js
@@ -14,6 +14,10 @@ describe('ScopeNavigation', () => {
fetchSidebarCount: jest.fn(),
};
+ const getterSpies = {
+ currentScope: jest.fn(() => 'issues'),
+ };
+
const createComponent = (initialState) => {
const store = new Vuex.Store({
state: {
@@ -22,6 +26,7 @@ describe('ScopeNavigation', () => {
...initialState,
},
actions: actionSpies,
+ getters: getterSpies,
});
wrapper = shallowMount(ScopeNavigation, {
@@ -29,16 +34,12 @@ describe('ScopeNavigation', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findNavElement = () => wrapper.find('nav');
const findGlNav = () => wrapper.findComponent(GlNav);
const findGlNavItems = () => wrapper.findAllComponents(GlNavItem);
- const findGlNavItemActive = () => findGlNavItems().wrappers.filter((w) => w.attributes('active'));
- const findGlNavItemActiveLabel = () => findGlNavItemActive().at(0).findAll('span').at(0).text();
- const findGlNavItemActiveCount = () => findGlNavItemActive().at(0).findAll('span').at(1);
+ const findGlNavItemActive = () => wrapper.find('[active=true]');
+ const findGlNavItemActiveLabel = () => findGlNavItemActive().find('[data-testid="label"]');
+ const findGlNavItemActiveCount = () => findGlNavItemActive().find('[data-testid="count"]');
describe('scope navigation', () => {
beforeEach(() => {
@@ -71,8 +72,8 @@ describe('ScopeNavigation', () => {
});
it('has correct active item', () => {
- expect(findGlNavItemActive()).toHaveLength(1);
- expect(findGlNavItemActiveLabel()).toBe('Issues');
+ expect(findGlNavItemActive().exists()).toBe(true);
+ expect(findGlNavItemActiveLabel().text()).toBe('Issues');
});
it('has correct active item count', () => {
@@ -80,7 +81,7 @@ describe('ScopeNavigation', () => {
});
it('does not have plus sign after count text', () => {
- expect(findGlNavItemActive().at(0).findComponent(GlIcon).exists()).toBe(false);
+ expect(findGlNavItemActive().findComponent(GlIcon).exists()).toBe(false);
});
it('has count is highlighted correctly', () => {
@@ -90,14 +91,26 @@ describe('ScopeNavigation', () => {
describe('scope navigation sets proper state with NO url scope set', () => {
beforeEach(() => {
+ getterSpies.currentScope = jest.fn(() => 'projects');
createComponent({
urlQuery: {},
+ navigation: {
+ ...MOCK_NAVIGATION,
+ projects: {
+ ...MOCK_NAVIGATION.projects,
+ active: true,
+ },
+ issues: {
+ ...MOCK_NAVIGATION.issues,
+ active: false,
+ },
+ },
});
});
it('has correct active item', () => {
- expect(findGlNavItems().at(0).attributes('active')).toBe('true');
- expect(findGlNavItemActiveLabel()).toBe('Projects');
+ expect(findGlNavItemActive().exists()).toBe(true);
+ expect(findGlNavItemActiveLabel().text()).toBe('Projects');
});
it('has correct active item count', () => {
@@ -105,7 +118,7 @@ describe('ScopeNavigation', () => {
});
it('has correct active item count and over limit sign', () => {
- expect(findGlNavItemActive().at(0).findComponent(GlIcon).exists()).toBe(true);
+ expect(findGlNavItemActive().findComponent(GlIcon).exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/search/sort/components/app_spec.js b/spec/frontend/search/sort/components/app_spec.js
index a566b9b99d3..322ce1b16ef 100644
--- a/spec/frontend/search/sort/components/app_spec.js
+++ b/spec/frontend/search/sort/components/app_spec.js
@@ -38,11 +38,6 @@ describe('GlobalSearchSort', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findSortButtonGroup = () => wrapper.findComponent(GlButtonGroup);
const findSortDropdown = () => wrapper.findComponent(GlDropdown);
const findSortDirectionButton = () => wrapper.findComponent(GlButton);
diff --git a/spec/frontend/search/store/actions_spec.js b/spec/frontend/search/store/actions_spec.js
index 2f87802dfe6..0884411df0c 100644
--- a/spec/frontend/search/store/actions_spec.js
+++ b/spec/frontend/search/store/actions_spec.js
@@ -1,7 +1,7 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import * as logger from '~/lib/logger';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
@@ -33,7 +33,7 @@ import {
MOCK_AGGREGATIONS,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility', () => ({
setUrlParams: jest.fn(),
joinPaths: jest.fn().mockReturnValue(''),
@@ -47,7 +47,7 @@ describe('Global Search Store Actions', () => {
let mock;
let state;
- const flashCallback = (callCount) => {
+ const alertCallback = (callCount) => {
expect(createAlert).toHaveBeenCalledTimes(callCount);
createAlert.mockClear();
};
@@ -63,12 +63,12 @@ describe('Global Search Store Actions', () => {
});
describe.each`
- action | axiosMock | type | expectedMutations | flashCallCount
+ action | axiosMock | type | expectedMutations | alertCallCount
${actions.fetchGroups} | ${{ method: 'onGet', code: HTTP_STATUS_OK, res: MOCK_GROUPS }} | ${'success'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_SUCCESS, payload: MOCK_GROUPS }]} | ${0}
${actions.fetchGroups} | ${{ method: 'onGet', code: HTTP_STATUS_INTERNAL_SERVER_ERROR, res: null }} | ${'error'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_ERROR }]} | ${1}
${actions.fetchProjects} | ${{ method: 'onGet', code: HTTP_STATUS_OK, res: MOCK_PROJECTS }} | ${'success'} | ${[{ type: types.REQUEST_PROJECTS }, { type: types.RECEIVE_PROJECTS_SUCCESS, payload: MOCK_PROJECTS }]} | ${0}
${actions.fetchProjects} | ${{ method: 'onGet', code: HTTP_STATUS_INTERNAL_SERVER_ERROR, res: null }} | ${'error'} | ${[{ type: types.REQUEST_PROJECTS }, { type: types.RECEIVE_PROJECTS_ERROR }]} | ${1}
- `(`axios calls`, ({ action, axiosMock, type, expectedMutations, flashCallCount }) => {
+ `(`axios calls`, ({ action, axiosMock, type, expectedMutations, alertCallCount }) => {
describe(action.name, () => {
describe(`on ${type}`, () => {
beforeEach(() => {
@@ -76,7 +76,7 @@ describe('Global Search Store Actions', () => {
});
it(`should dispatch the correct mutations`, () => {
return testAction({ action, state, expectedMutations }).then(() =>
- flashCallback(flashCallCount),
+ alertCallback(alertCallCount),
);
});
});
@@ -84,12 +84,12 @@ describe('Global Search Store Actions', () => {
});
describe.each`
- action | axiosMock | type | expectedMutations | flashCallCount
+ action | axiosMock | type | expectedMutations | alertCallCount
${actions.loadFrequentGroups} | ${{ method: 'onGet', code: HTTP_STATUS_OK }} | ${'success'} | ${[PROMISE_ALL_EXPECTED_MUTATIONS.resGroups]} | ${0}
${actions.loadFrequentGroups} | ${{ method: 'onGet', code: HTTP_STATUS_INTERNAL_SERVER_ERROR }} | ${'error'} | ${[]} | ${1}
${actions.loadFrequentProjects} | ${{ method: 'onGet', code: HTTP_STATUS_OK }} | ${'success'} | ${[PROMISE_ALL_EXPECTED_MUTATIONS.resProjects]} | ${0}
${actions.loadFrequentProjects} | ${{ method: 'onGet', code: HTTP_STATUS_INTERNAL_SERVER_ERROR }} | ${'error'} | ${[]} | ${1}
- `('Promise.all calls', ({ action, axiosMock, type, expectedMutations, flashCallCount }) => {
+ `('Promise.all calls', ({ action, axiosMock, type, expectedMutations, alertCallCount }) => {
describe(action.name, () => {
describe(`on ${type}`, () => {
beforeEach(() => {
@@ -103,7 +103,7 @@ describe('Global Search Store Actions', () => {
it(`should dispatch the correct mutations`, () => {
return testAction({ action, state, expectedMutations }).then(() => {
- flashCallback(flashCallCount);
+ alertCallback(alertCallCount);
});
});
});
@@ -275,7 +275,7 @@ describe('Global Search Store Actions', () => {
describe.each`
action | axiosMock | type | scope | expectedMutations | errorLogs
${actions.fetchSidebarCount} | ${{ method: 'onGet', code: HTTP_STATUS_OK }} | ${'success'} | ${'issues'} | ${[MOCK_NAVIGATION_ACTION_MUTATION]} | ${0}
- ${actions.fetchSidebarCount} | ${{ method: null, code: 0 }} | ${'success'} | ${'projects'} | ${[]} | ${0}
+ ${actions.fetchSidebarCount} | ${{ method: null, code: 0 }} | ${'error'} | ${'projects'} | ${[]} | ${1}
${actions.fetchSidebarCount} | ${{ method: 'onGet', code: HTTP_STATUS_INTERNAL_SERVER_ERROR }} | ${'error'} | ${'issues'} | ${[]} | ${1}
`('fetchSidebarCount', ({ action, axiosMock, type, expectedMutations, scope, errorLogs }) => {
describe(`on ${type}`, () => {
@@ -290,9 +290,9 @@ describe('Global Search Store Actions', () => {
}
});
- it(`should ${expectedMutations.length === 0 ? 'NOT ' : ''}dispatch ${
- expectedMutations.length === 0 ? '' : 'the correct '
- }mutations for ${scope}`, () => {
+ it(`should ${expectedMutations.length === 0 ? 'NOT' : ''} dispatch ${
+ expectedMutations.length === 0 ? '' : 'the correct'
+ } mutations for ${scope}`, () => {
return testAction({ action, state, expectedMutations }).then(() => {
expect(logger.logError).toHaveBeenCalledTimes(errorLogs);
});
@@ -325,4 +325,26 @@ describe('Global Search Store Actions', () => {
});
});
});
+
+ describe('resetLanguageQueryWithRedirect', () => {
+ it('calls visitUrl and setParams with the state.query', () => {
+ return testAction(actions.resetLanguageQueryWithRedirect, null, state, [], [], () => {
+ expect(urlUtils.setUrlParams).toHaveBeenCalledWith({ ...state.query, page: null });
+ expect(urlUtils.visitUrl).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('resetLanguageQuery', () => {
+ it('calls commit SET_QUERY with value []', () => {
+ state = { ...state, query: { ...state.query, language: ['YAML', 'Text', 'Markdown'] } };
+ return testAction(
+ actions.resetLanguageQuery,
+ null,
+ state,
+ [{ type: types.SET_QUERY, payload: { key: 'language', value: [] } }],
+ [],
+ );
+ });
+ });
});
diff --git a/spec/frontend/search/store/getters_spec.js b/spec/frontend/search/store/getters_spec.js
index 818902ee720..0ef0922c4b0 100644
--- a/spec/frontend/search/store/getters_spec.js
+++ b/spec/frontend/search/store/getters_spec.js
@@ -1,12 +1,15 @@
import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from '~/search/store/constants';
import * as getters from '~/search/store/getters';
import createState from '~/search/store/state';
+import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import {
MOCK_QUERY,
MOCK_GROUPS,
MOCK_PROJECTS,
MOCK_AGGREGATIONS,
MOCK_LANGUAGE_AGGREGATIONS_BUCKETS,
+ TEST_FILTER_DATA,
+ MOCK_NAVIGATION,
} from '../mock_data';
describe('Global Search Store Getters', () => {
@@ -14,37 +17,55 @@ describe('Global Search Store Getters', () => {
beforeEach(() => {
state = createState({ query: MOCK_QUERY });
+ useMockLocationHelper();
});
describe('frequentGroups', () => {
- beforeEach(() => {
- state.frequentItems[GROUPS_LOCAL_STORAGE_KEY] = MOCK_GROUPS;
- });
-
it('returns the correct data', () => {
+ state.frequentItems[GROUPS_LOCAL_STORAGE_KEY] = MOCK_GROUPS;
expect(getters.frequentGroups(state)).toStrictEqual(MOCK_GROUPS);
});
});
describe('frequentProjects', () => {
- beforeEach(() => {
- state.frequentItems[PROJECTS_LOCAL_STORAGE_KEY] = MOCK_PROJECTS;
- });
-
it('returns the correct data', () => {
+ state.frequentItems[PROJECTS_LOCAL_STORAGE_KEY] = MOCK_PROJECTS;
expect(getters.frequentProjects(state)).toStrictEqual(MOCK_PROJECTS);
});
});
- describe('langugageAggregationBuckets', () => {
- beforeEach(() => {
+ describe('languageAggregationBuckets', () => {
+ it('returns the correct data', () => {
state.aggregations.data = MOCK_AGGREGATIONS;
+ expect(getters.languageAggregationBuckets(state)).toStrictEqual(
+ MOCK_LANGUAGE_AGGREGATIONS_BUCKETS,
+ );
});
+ });
+ describe('queryLanguageFilters', () => {
it('returns the correct data', () => {
- expect(getters.langugageAggregationBuckets(state)).toStrictEqual(
- MOCK_LANGUAGE_AGGREGATIONS_BUCKETS,
- );
+ state.query.language = Object.keys(TEST_FILTER_DATA.filters);
+ expect(getters.queryLanguageFilters(state)).toStrictEqual(state.query.language);
+ });
+ });
+
+ describe('currentScope', () => {
+ it('returns the correct scope name', () => {
+ state.navigation = MOCK_NAVIGATION;
+ expect(getters.currentScope(state)).toBe('issues');
+ });
+ });
+
+ describe('currentUrlQueryHasLanguageFilters', () => {
+ it.each`
+ description | lang | result
+ ${'has valid language'} | ${{ language: ['a', 'b'] }} | ${true}
+ ${'has empty lang'} | ${{ language: [] }} | ${false}
+ ${'has no lang'} | ${{}} | ${false}
+ `('$description', ({ lang, result }) => {
+ state.urlQuery = lang;
+ expect(getters.currentUrlQueryHasLanguageFilters(state)).toBe(result);
});
});
});
diff --git a/spec/frontend/search/store/utils_spec.js b/spec/frontend/search/store/utils_spec.js
index 487ed7bfe03..dfe4e801f11 100644
--- a/spec/frontend/search/store/utils_spec.js
+++ b/spec/frontend/search/store/utils_spec.js
@@ -7,6 +7,7 @@ import {
isSidebarDirty,
formatSearchResultCount,
getAggregationsUrl,
+ prepareSearchAggregations,
} from '~/search/store/utils';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import {
@@ -15,6 +16,9 @@ import {
MOCK_INFLATED_DATA,
FRESH_STORED_DATA,
STALE_STORED_DATA,
+ MOCK_AGGREGATIONS,
+ SMALL_MOCK_AGGREGATIONS,
+ TEST_RAW_BUCKETS,
} from '../mock_data';
const PREV_TIME = new Date().getTime() - 1;
@@ -226,11 +230,14 @@ describe('Global Search Store Utils', () => {
});
describe.each`
- description | currentQuery | urlQuery | isDirty
- ${'identical'} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'default' }} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'default' }} | ${false}
- ${'different'} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'new' }} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'default' }} | ${true}
- ${'null/undefined'} | ${{ [SIDEBAR_PARAMS[0]]: null, [SIDEBAR_PARAMS[1]]: null }} | ${{ [SIDEBAR_PARAMS[0]]: undefined, [SIDEBAR_PARAMS[1]]: undefined }} | ${false}
- ${'updated/undefined'} | ${{ [SIDEBAR_PARAMS[0]]: 'new', [SIDEBAR_PARAMS[1]]: 'new' }} | ${{ [SIDEBAR_PARAMS[0]]: undefined, [SIDEBAR_PARAMS[1]]: undefined }} | ${true}
+ description | currentQuery | urlQuery | isDirty
+ ${'identical'} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'default', [SIDEBAR_PARAMS[2]]: ['a', 'b'] }} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'default', [SIDEBAR_PARAMS[2]]: ['a', 'b'] }} | ${false}
+ ${'different'} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'new', [SIDEBAR_PARAMS[2]]: ['a', 'b'] }} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'default', [SIDEBAR_PARAMS[2]]: ['a', 'c'] }} | ${true}
+ ${'null/undefined'} | ${{ [SIDEBAR_PARAMS[0]]: null, [SIDEBAR_PARAMS[1]]: null, [SIDEBAR_PARAMS[2]]: null }} | ${{ [SIDEBAR_PARAMS[0]]: undefined, [SIDEBAR_PARAMS[1]]: undefined, [SIDEBAR_PARAMS[2]]: undefined }} | ${false}
+ ${'updated/undefined'} | ${{ [SIDEBAR_PARAMS[0]]: 'new', [SIDEBAR_PARAMS[1]]: 'new', [SIDEBAR_PARAMS[2]]: ['a', 'b'] }} | ${{ [SIDEBAR_PARAMS[0]]: undefined, [SIDEBAR_PARAMS[1]]: undefined, [SIDEBAR_PARAMS[2]]: [] }} | ${true}
+ ${'language only no url params'} | ${{ [SIDEBAR_PARAMS[2]]: ['a', 'b'] }} | ${{ [SIDEBAR_PARAMS[2]]: undefined }} | ${true}
+ ${'language only url params symetric'} | ${{ [SIDEBAR_PARAMS[2]]: ['a', 'b'] }} | ${{ [SIDEBAR_PARAMS[2]]: ['a', 'b'] }} | ${false}
+ ${'language only url params asymetric'} | ${{ [SIDEBAR_PARAMS[2]]: ['a'] }} | ${{ [SIDEBAR_PARAMS[2]]: ['a', 'b'] }} | ${true}
`('isSidebarDirty', ({ description, currentQuery, urlQuery, isDirty }) => {
describe(`with ${description} sidebar query data`, () => {
let res;
@@ -263,4 +270,22 @@ describe('Global Search Store Utils', () => {
expect(getAggregationsUrl()).toStrictEqual(`${testURL}search/aggregations`);
});
});
+
+ const TEST_LANGUAGE_QUERY = ['Markdown', 'JSON'];
+ const TEST_EXPECTED_ORDERED_BUCKETS = [
+ TEST_RAW_BUCKETS.find((x) => x.key === 'Markdown'),
+ TEST_RAW_BUCKETS.find((x) => x.key === 'JSON'),
+ ...TEST_RAW_BUCKETS.filter((x) => !TEST_LANGUAGE_QUERY.includes(x.key)),
+ ];
+
+ describe('prepareSearchAggregations', () => {
+ it.each`
+ description | query | data | result
+ ${'has no query'} | ${undefined} | ${MOCK_AGGREGATIONS} | ${MOCK_AGGREGATIONS}
+ ${'has query'} | ${{ language: TEST_LANGUAGE_QUERY }} | ${SMALL_MOCK_AGGREGATIONS} | ${[{ ...SMALL_MOCK_AGGREGATIONS[0], buckets: TEST_EXPECTED_ORDERED_BUCKETS }]}
+ ${'has bad query'} | ${{ language: ['sdf', 'wrt'] }} | ${SMALL_MOCK_AGGREGATIONS} | ${SMALL_MOCK_AGGREGATIONS}
+ `('$description', ({ query, data, result }) => {
+ expect(prepareSearchAggregations({ query }, data)).toStrictEqual(result);
+ });
+ });
});
diff --git a/spec/frontend/search/topbar/components/app_spec.js b/spec/frontend/search/topbar/components/app_spec.js
index 3975887cfff..423ec6ff63b 100644
--- a/spec/frontend/search/topbar/components/app_spec.js
+++ b/spec/frontend/search/topbar/components/app_spec.js
@@ -36,10 +36,6 @@ describe('GlobalSearchTopbar', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGlSearchBox = () => wrapper.findComponent(GlSearchBoxByClick);
const findGroupFilter = () => wrapper.findComponent(GroupFilter);
const findProjectFilter = () => wrapper.findComponent(ProjectFilter);
diff --git a/spec/frontend/search/topbar/components/group_filter_spec.js b/spec/frontend/search/topbar/components/group_filter_spec.js
index b2d0297fdc2..78d9efbd686 100644
--- a/spec/frontend/search/topbar/components/group_filter_spec.js
+++ b/spec/frontend/search/topbar/components/group_filter_spec.js
@@ -49,10 +49,6 @@ describe('GroupFilter', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findSearchableDropdown = () => wrapper.findComponent(SearchableDropdown);
describe('template', () => {
diff --git a/spec/frontend/search/topbar/components/project_filter_spec.js b/spec/frontend/search/topbar/components/project_filter_spec.js
index 297a536e075..9eda34b1633 100644
--- a/spec/frontend/search/topbar/components/project_filter_spec.js
+++ b/spec/frontend/search/topbar/components/project_filter_spec.js
@@ -49,10 +49,6 @@ describe('ProjectFilter', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findSearchableDropdown = () => wrapper.findComponent(SearchableDropdown);
describe('template', () => {
diff --git a/spec/frontend/search/topbar/components/searchable_dropdown_item_spec.js b/spec/frontend/search/topbar/components/searchable_dropdown_item_spec.js
index e51fe9a4cf9..c911fe53d40 100644
--- a/spec/frontend/search/topbar/components/searchable_dropdown_item_spec.js
+++ b/spec/frontend/search/topbar/components/searchable_dropdown_item_spec.js
@@ -24,10 +24,6 @@ describe('Global Search Searchable Dropdown Item', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGlDropdownItem = () => wrapper.findComponent(GlDropdownItem);
const findGlAvatar = () => wrapper.findComponent(GlAvatar);
const findDropdownTitle = () => wrapper.findByTestId('item-title');
diff --git a/spec/frontend/search/topbar/components/searchable_dropdown_spec.js b/spec/frontend/search/topbar/components/searchable_dropdown_spec.js
index de1cefa9e9d..5e5f46ff34e 100644
--- a/spec/frontend/search/topbar/components/searchable_dropdown_spec.js
+++ b/spec/frontend/search/topbar/components/searchable_dropdown_spec.js
@@ -1,6 +1,6 @@
import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlSkeletonLoader } from '@gitlab/ui';
import { shallowMount, mount } 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 { MOCK_GROUPS, MOCK_GROUP, MOCK_QUERY } from 'jest/search/mock_data';
@@ -40,10 +40,6 @@ describe('Global Search Searchable Dropdown', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGlDropdown = () => wrapper.findComponent(GlDropdown);
const findGlDropdownSearch = () => findGlDropdown().findComponent(GlSearchBoxByType);
const findDropdownText = () => findGlDropdown().find('.dropdown-toggle-text');
@@ -133,9 +129,7 @@ describe('Global Search Searchable Dropdown', () => {
describe(`when search is ${searchText} and frequentItems length is ${frequentItems.length}`, () => {
beforeEach(() => {
createComponent({}, { frequentItems });
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ searchText });
+ findGlDropdownSearch().vm.$emit('input', searchText);
});
it(`should${length ? '' : ' not'} render frequent dropdown items`, () => {
@@ -191,28 +185,33 @@ describe('Global Search Searchable Dropdown', () => {
});
describe('opening the dropdown', () => {
- describe('for the first time', () => {
- beforeEach(() => {
- findGlDropdown().vm.$emit('show');
- });
+ beforeEach(() => {
+ findGlDropdown().vm.$emit('show');
+ });
- it('$emits @search and @first-open', () => {
- expect(wrapper.emitted('search')[0]).toStrictEqual([wrapper.vm.searchText]);
- expect(wrapper.emitted('first-open')[0]).toStrictEqual([]);
- });
+ it('$emits @search and @first-open on the first open', async () => {
+ expect(wrapper.emitted('search')[0]).toStrictEqual(['']);
+ expect(wrapper.emitted('first-open')[0]).toStrictEqual([]);
});
- describe('not for the first time', () => {
- beforeEach(() => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ hasBeenOpened: true });
- findGlDropdown().vm.$emit('show');
+ describe('when the dropdown has been opened', () => {
+ it('$emits @search with the searchText', async () => {
+ const searchText = 'foo';
+
+ findGlDropdownSearch().vm.$emit('input', searchText);
+ await nextTick();
+
+ expect(wrapper.emitted('search')[1]).toStrictEqual([searchText]);
+ expect(wrapper.emitted('first-open')).toHaveLength(1);
});
- it('$emits @search and not @first-open', () => {
- expect(wrapper.emitted('search')[0]).toStrictEqual([wrapper.vm.searchText]);
- expect(wrapper.emitted('first-open')).toBeUndefined();
+ it('does not emit @first-open again', async () => {
+ expect(wrapper.emitted('first-open')).toHaveLength(1);
+
+ findGlDropdownSearch().vm.$emit('input');
+ await nextTick();
+
+ expect(wrapper.emitted('first-open')).toHaveLength(1);
});
});
});
diff --git a/spec/frontend/search_autocomplete_spec.js b/spec/frontend/search_autocomplete_spec.js
index a3098fb81ea..dfb31eeda78 100644
--- a/spec/frontend/search_autocomplete_spec.js
+++ b/spec/frontend/search_autocomplete_spec.js
@@ -119,7 +119,6 @@ describe('Search autocomplete dropdown', () => {
afterEach(() => {
// Undo what we did to the shared <body>
removeBodyAttributes();
- window.gon = {};
resetHTMLFixture();
});
diff --git a/spec/frontend/search_settings/components/search_settings_spec.js b/spec/frontend/search_settings/components/search_settings_spec.js
index 3f856968db6..fe761049a70 100644
--- a/spec/frontend/search_settings/components/search_settings_spec.js
+++ b/spec/frontend/search_settings/components/search_settings_spec.js
@@ -79,10 +79,6 @@ describe('search_settings/components/search_settings.vue', () => {
buildWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('hides sections that do not match the search term', () => {
const hiddenSection = document.querySelector(`#${GENERAL_SETTINGS_ID}`);
search(SEARCH_TERM);
diff --git a/spec/frontend/security_configuration/components/app_spec.js b/spec/frontend/security_configuration/components/app_spec.js
index ddefda2ffc3..0ca350f9ed7 100644
--- a/spec/frontend/security_configuration/components/app_spec.js
+++ b/spec/frontend/security_configuration/components/app_spec.js
@@ -26,6 +26,8 @@ import {
REPORT_TYPE_LICENSE_COMPLIANCE,
REPORT_TYPE_SAST,
} from '~/vue_shared/security_reports/constants';
+import { USER_FACING_ERROR_MESSAGE_PREFIX } from '~/lib/utils/error_message';
+import { manageViaMRErrorMessage } from '../constants';
const upgradePath = '/upgrade';
const autoDevopsHelpPagePath = '/autoDevopsHelpPagePath';
@@ -129,10 +131,6 @@ describe('App component', () => {
const findAutoDevopsEnabledAlert = () => wrapper.findComponent(AutoDevopsEnabledAlert);
const findVulnerabilityManagementTab = () => wrapper.findByTestId('vulnerability-management-tab');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('basic structure', () => {
beforeEach(() => {
createComponent();
@@ -141,7 +139,7 @@ describe('App component', () => {
it('renders main-heading with correct text', () => {
const mainHeading = findMainHeading();
expect(mainHeading.exists()).toBe(true);
- expect(mainHeading.text()).toContain('Security Configuration');
+ expect(mainHeading.text()).toContain('Security configuration');
});
describe('tabs', () => {
@@ -204,18 +202,21 @@ describe('App component', () => {
});
});
- describe('when error occurs', () => {
+ describe('when user facing error occurs', () => {
it('should show Alert with error Message', async () => {
expect(findManageViaMRErrorAlert().exists()).toBe(false);
- findFeatureCards().at(1).vm.$emit('error', 'There was a manage via MR error');
+ // Prefixed with USER_FACING_ERROR_MESSAGE_PREFIX as used in lib/gitlab/utils/error_message.rb to indicate a user facing error
+ findFeatureCards()
+ .at(1)
+ .vm.$emit('error', `${USER_FACING_ERROR_MESSAGE_PREFIX} ${manageViaMRErrorMessage}`);
await nextTick();
expect(findManageViaMRErrorAlert().exists()).toBe(true);
- expect(findManageViaMRErrorAlert().text()).toEqual('There was a manage via MR error');
+ expect(findManageViaMRErrorAlert().text()).toEqual(manageViaMRErrorMessage);
});
it('should hide Alert when it is dismissed', async () => {
- findFeatureCards().at(1).vm.$emit('error', 'There was a manage via MR error');
+ findFeatureCards().at(1).vm.$emit('error', manageViaMRErrorMessage);
await nextTick();
expect(findManageViaMRErrorAlert().exists()).toBe(true);
@@ -225,6 +226,17 @@ describe('App component', () => {
expect(findManageViaMRErrorAlert().exists()).toBe(false);
});
});
+
+ describe('when non-user facing error occurs', () => {
+ it('should show Alert with generic error Message', async () => {
+ expect(findManageViaMRErrorAlert().exists()).toBe(false);
+ findFeatureCards().at(1).vm.$emit('error', manageViaMRErrorMessage);
+
+ await nextTick();
+ expect(findManageViaMRErrorAlert().exists()).toBe(true);
+ expect(findManageViaMRErrorAlert().text()).toEqual(i18n.genericErrorText);
+ });
+ });
});
describe('Auto DevOps hint alert', () => {
diff --git a/spec/frontend/security_configuration/components/auto_dev_ops_alert_spec.js b/spec/frontend/security_configuration/components/auto_dev_ops_alert_spec.js
index 467ae35408c..df1fa1a8084 100644
--- a/spec/frontend/security_configuration/components/auto_dev_ops_alert_spec.js
+++ b/spec/frontend/security_configuration/components/auto_dev_ops_alert_spec.js
@@ -23,10 +23,6 @@ describe('AutoDevopsAlert component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('contains correct body text', () => {
expect(wrapper.text()).toContain('Quickly enable all');
});
diff --git a/spec/frontend/security_configuration/components/auto_dev_ops_enabled_alert_spec.js b/spec/frontend/security_configuration/components/auto_dev_ops_enabled_alert_spec.js
index 778fea2896a..22f45a92f70 100644
--- a/spec/frontend/security_configuration/components/auto_dev_ops_enabled_alert_spec.js
+++ b/spec/frontend/security_configuration/components/auto_dev_ops_enabled_alert_spec.js
@@ -21,10 +21,6 @@ describe('AutoDevopsEnabledAlert component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('contains correct body text', () => {
expect(wrapper.text()).toMatchInterpolatedText(AutoDevopsEnabledAlert.i18n.body);
});
diff --git a/spec/frontend/security_configuration/components/feature_card_spec.js b/spec/frontend/security_configuration/components/feature_card_spec.js
index d10722be8ea..23edd8a69de 100644
--- a/spec/frontend/security_configuration/components/feature_card_spec.js
+++ b/spec/frontend/security_configuration/components/feature_card_spec.js
@@ -5,6 +5,7 @@ import FeatureCard from '~/security_configuration/components/feature_card.vue';
import FeatureCardBadge from '~/security_configuration/components/feature_card_badge.vue';
import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
import { REPORT_TYPE_SAST } from '~/vue_shared/security_reports/constants';
+import { manageViaMRErrorMessage } from '../constants';
import { makeFeature } from './utils';
describe('FeatureCard component', () => {
@@ -78,7 +79,6 @@ describe('FeatureCard component', () => {
};
afterEach(() => {
- wrapper.destroy();
feature = undefined;
});
@@ -107,8 +107,8 @@ describe('FeatureCard component', () => {
});
it('should catch and emit manage-via-mr-error', () => {
- findManageViaMr().vm.$emit('error', 'There was a manage via MR error');
- expect(wrapper.emitted('error')).toEqual([['There was a manage via MR error']]);
+ findManageViaMr().vm.$emit('error', manageViaMRErrorMessage);
+ expect(wrapper.emitted('error')).toEqual([[manageViaMRErrorMessage]]);
});
});
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 8f2b5383191..1f8f306c931 100644
--- a/spec/frontend/security_configuration/components/training_provider_list_spec.js
+++ b/spec/frontend/security_configuration/components/training_provider_list_spec.js
@@ -106,7 +106,7 @@ describe('TrainingProviderList component', () => {
projectFullPath: testProjectPath,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
propsData: {
securityTrainingEnabled: true,
@@ -132,7 +132,6 @@ describe('TrainingProviderList component', () => {
const toggleFirstProvider = () => findFirstToggle().vm.$emit('change', testProviderIds[0]);
afterEach(() => {
- wrapper.destroy();
apolloProvider = null;
});
diff --git a/spec/frontend/security_configuration/components/upgrade_banner_spec.js b/spec/frontend/security_configuration/components/upgrade_banner_spec.js
index c34d8e47a6c..97087877224 100644
--- a/spec/frontend/security_configuration/components/upgrade_banner_spec.js
+++ b/spec/frontend/security_configuration/components/upgrade_banner_spec.js
@@ -44,7 +44,6 @@ describe('UpgradeBanner component', () => {
});
afterEach(() => {
- wrapper.destroy();
unmockTracking();
});
diff --git a/spec/frontend/security_configuration/constants.js b/spec/frontend/security_configuration/constants.js
new file mode 100644
index 00000000000..d31036a2534
--- /dev/null
+++ b/spec/frontend/security_configuration/constants.js
@@ -0,0 +1 @@
+export const manageViaMRErrorMessage = 'There was a manage via MR error';
diff --git a/spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap b/spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap
index efe3f7e8dbf..c278bb4579f 100644
--- a/spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap
+++ b/spec/frontend/self_monitor/components/__snapshots__/self_monitor_form_spec.js.snap
@@ -40,6 +40,41 @@ exports[`self-monitor component When the self-monitor project has not been creat
</p>
</div>
+ <gl-alert-stub
+ class="gl-mb-3"
+ dismissible="true"
+ dismisslabel="Dismiss"
+ primarybuttonlink=""
+ primarybuttontext=""
+ secondarybuttonlink=""
+ secondarybuttontext=""
+ showicon="true"
+ title="Deprecation notice"
+ variant="danger"
+ >
+ <div>
+ Self-monitoring was
+ <a
+ href="/help/update/deprecations.md#gitlab-self-monitoring-project"
+ >
+ deprecated
+ </a>
+ in GitLab 14.9, and is
+ <a
+ href="https://gitlab.com/gitlab-org/gitlab/-/issues/348909"
+ >
+ scheduled for removal
+ </a>
+ in GitLab 16.0. For information on a possible replacement,
+ <a
+ href="https://gitlab.com/groups/gitlab-org/-/epics/6976"
+ >
+ learn more about Opstrace
+ </a>
+ .
+ </div>
+ </gl-alert-stub>
+
<div
class="settings-content"
>
diff --git a/spec/frontend/sentry/index_spec.js b/spec/frontend/sentry/index_spec.js
index 2dd528a8a1c..83195e9d306 100644
--- a/spec/frontend/sentry/index_spec.js
+++ b/spec/frontend/sentry/index_spec.js
@@ -4,8 +4,6 @@ import LegacySentryConfig from '~/sentry/legacy_sentry_config';
import SentryConfig from '~/sentry/sentry_config';
describe('Sentry init', () => {
- let originalGon;
-
const dsn = 'https://123@sentry.gitlab.test/123';
const environment = 'test';
const currentUserId = '1';
@@ -14,7 +12,6 @@ describe('Sentry init', () => {
const featureCategory = 'my_feature_category';
beforeEach(() => {
- originalGon = window.gon;
window.gon = {
sentry_dsn: dsn,
sentry_environment: environment,
@@ -28,10 +25,6 @@ describe('Sentry init', () => {
jest.spyOn(SentryConfig, 'init').mockImplementation();
});
- afterEach(() => {
- window.gon = originalGon;
- });
-
it('exports new version of Sentry in the global object', () => {
// eslint-disable-next-line no-underscore-dangle
expect(window._Sentry.SDK_VERSION).not.toMatch(/^5\./);
diff --git a/spec/frontend/sentry/legacy_index_spec.js b/spec/frontend/sentry/legacy_index_spec.js
index 5c336f8392e..493b4dfde67 100644
--- a/spec/frontend/sentry/legacy_index_spec.js
+++ b/spec/frontend/sentry/legacy_index_spec.js
@@ -4,8 +4,6 @@ import LegacySentryConfig from '~/sentry/legacy_sentry_config';
import SentryConfig from '~/sentry/sentry_config';
describe('Sentry init', () => {
- let originalGon;
-
const dsn = 'https://123@sentry.gitlab.test/123';
const environment = 'test';
const currentUserId = '1';
@@ -14,7 +12,6 @@ describe('Sentry init', () => {
const featureCategory = 'my_feature_category';
beforeEach(() => {
- originalGon = window.gon;
window.gon = {
sentry_dsn: dsn,
sentry_environment: environment,
@@ -28,10 +25,6 @@ describe('Sentry init', () => {
jest.spyOn(SentryConfig, 'init').mockImplementation();
});
- afterEach(() => {
- window.gon = originalGon;
- });
-
it('exports legacy version of Sentry in the global object', () => {
// eslint-disable-next-line no-underscore-dangle
expect(window._Sentry.SDK_VERSION).toMatch(/^5\./);
diff --git a/spec/frontend/sentry/sentry_config_spec.js b/spec/frontend/sentry/sentry_config_spec.js
index 44acbee9b38..34c5221ef0d 100644
--- a/spec/frontend/sentry/sentry_config_spec.js
+++ b/spec/frontend/sentry/sentry_config_spec.js
@@ -1,5 +1,4 @@
import * as Sentry from 'sentrybrowser7';
-import { IGNORE_ERRORS, DENY_URLS, SAMPLE_RATE } from '~/sentry/constants';
import SentryConfig from '~/sentry/sentry_config';
@@ -62,11 +61,8 @@ describe('SentryConfig', () => {
expect(Sentry.init).toHaveBeenCalledWith({
dsn: options.dsn,
release: options.release,
- sampleRate: SAMPLE_RATE,
allowUrls: options.allowUrls,
environment: options.environment,
- ignoreErrors: IGNORE_ERRORS,
- denyUrls: DENY_URLS,
});
});
@@ -82,11 +78,8 @@ describe('SentryConfig', () => {
expect(Sentry.init).toHaveBeenCalledWith({
dsn: options.dsn,
release: options.release,
- sampleRate: SAMPLE_RATE,
allowUrls: options.allowUrls,
environment: 'development',
- ignoreErrors: IGNORE_ERRORS,
- denyUrls: DENY_URLS,
});
});
});
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 85cd8d51272..b5bf739b35a 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
@@ -5,13 +5,13 @@ import { useFakeDate } from 'helpers/fake_date';
import { initEmojiMock, clearEmojiMock } from 'helpers/emoji';
import * as UserApi from '~/api/user_api';
import EmojiPicker from '~/emoji/components/picker.vue';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import stubChildren from 'helpers/stub_children';
import SetStatusModalWrapper from '~/set_status_modal/set_status_modal_wrapper.vue';
import { AVAILABILITY_STATUS } from '~/set_status_modal/constants';
import SetStatusForm from '~/set_status_modal/set_status_form.vue';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('SetStatusModalWrapper', () => {
let wrapper;
@@ -72,7 +72,6 @@ describe('SetStatusModalWrapper', () => {
};
afterEach(() => {
- wrapper.destroy();
clearEmojiMock();
});
@@ -244,7 +243,7 @@ describe('SetStatusModalWrapper', () => {
return initModal({ mockOnUpdateFailure: false });
});
- it('flashes an error message', async () => {
+ it('alerts an error message', async () => {
findModal().vm.$emit('primary');
await nextTick();
diff --git a/spec/frontend/set_status_modal/user_profile_set_status_wrapper_spec.js b/spec/frontend/set_status_modal/user_profile_set_status_wrapper_spec.js
index a4a2a86dc73..a6ad90123b7 100644
--- a/spec/frontend/set_status_modal/user_profile_set_status_wrapper_spec.js
+++ b/spec/frontend/set_status_modal/user_profile_set_status_wrapper_spec.js
@@ -37,10 +37,6 @@ describe('UserProfileSetStatusWrapper', () => {
const findInput = (name) => wrapper.find(`[name="${name}"]`);
const findSetStatusForm = () => wrapper.findComponent(SetStatusForm);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders `SetStatusForm` component and passes expected props', () => {
createComponent();
diff --git a/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js b/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js
index 60edab8766a..81b65f4f050 100644
--- a/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js
+++ b/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js
@@ -30,10 +30,6 @@ describe('AssigneeAvatarLink component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
const findTooltipText = () => wrapper.attributes('title');
const findUserLink = () => wrapper.findComponent(GlLink);
diff --git a/spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js b/spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js
index 7df37d11987..b6b3dbd5b6b 100644
--- a/spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js
+++ b/spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js
@@ -7,7 +7,6 @@ const TEST_AVATAR = `${TEST_HOST}/avatar.png`;
const TEST_DEFAULT_AVATAR_URL = `${TEST_HOST}/default/avatar/url.png`;
describe('AssigneeAvatar', () => {
- let origGon;
let wrapper;
function createComponent(props = {}) {
@@ -24,15 +23,9 @@ describe('AssigneeAvatar', () => {
}
beforeEach(() => {
- origGon = window.gon;
window.gon = { default_avatar_url: TEST_DEFAULT_AVATAR_URL };
});
- afterEach(() => {
- window.gon = origGon;
- wrapper.destroy();
- });
-
const findImg = () => wrapper.find('img');
it('does not show warning icon if assignee can merge', () => {
diff --git a/spec/frontend/sidebar/components/assignees/assignee_title_spec.js b/spec/frontend/sidebar/components/assignees/assignee_title_spec.js
index 14a6bdbf907..d561c761c99 100644
--- a/spec/frontend/sidebar/components/assignees/assignee_title_spec.js
+++ b/spec/frontend/sidebar/components/assignees/assignee_title_spec.js
@@ -17,11 +17,6 @@ describe('AssigneeTitle component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('assignee title', () => {
it('renders assignee', () => {
wrapper = createComponent({
diff --git a/spec/frontend/sidebar/components/assignees/assignees_realtime_spec.js b/spec/frontend/sidebar/components/assignees/assignees_realtime_spec.js
index 080171fb2ea..0501c1bae23 100644
--- a/spec/frontend/sidebar/components/assignees/assignees_realtime_spec.js
+++ b/spec/frontend/sidebar/components/assignees/assignees_realtime_spec.js
@@ -49,7 +49,6 @@ describe('Assignees Realtime', () => {
});
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
SidebarMediator.singleton = null;
});
diff --git a/spec/frontend/sidebar/components/assignees/assignees_spec.js b/spec/frontend/sidebar/components/assignees/assignees_spec.js
index d422292ed9e..1661e28abd2 100644
--- a/spec/frontend/sidebar/components/assignees/assignees_spec.js
+++ b/spec/frontend/sidebar/components/assignees/assignees_spec.js
@@ -25,10 +25,6 @@ describe('Assignee component', () => {
const findComponentTextNoUsers = () => wrapper.find('[data-testid="no-value"]');
const findCollapsedChildren = () => wrapper.findAll('.sidebar-collapsed-icon > *');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('No assignees/users', () => {
it('displays no assignee icon when collapsed', () => {
createWrapper();
diff --git a/spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js b/spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js
index 7e7d4921cfa..40d3d090bb4 100644
--- a/spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js
+++ b/spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js
@@ -26,10 +26,6 @@ describe('CollapsedAssigneeList component', () => {
const findAssignees = () => wrapper.findAllComponents(CollapsedAssignee);
const getTooltipTitle = () => wrapper.attributes('title');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('No assignees/users', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js b/spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js
index 4db95114b96..851eaedf0bd 100644
--- a/spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js
+++ b/spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js
@@ -21,10 +21,6 @@ describe('CollapsedAssignee assignee component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('has author name', () => {
createComponent();
diff --git a/spec/frontend/sidebar/components/assignees/issuable_assignees_spec.js b/spec/frontend/sidebar/components/assignees/issuable_assignees_spec.js
index 1161fefcc64..82145b82e21 100644
--- a/spec/frontend/sidebar/components/assignees/issuable_assignees_spec.js
+++ b/spec/frontend/sidebar/components/assignees/issuable_assignees_spec.js
@@ -20,11 +20,6 @@ describe('IssuableAssignees', () => {
const findUncollapsedAssigneeList = () => wrapper.findComponent(UncollapsedAssigneeList);
const findEmptyAssignee = () => wrapper.find('[data-testid="none"]');
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when no assignees are present', () => {
it.each`
signedIn | editable | message
diff --git a/spec/frontend/sidebar/components/assignees/sidebar_assignees_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_assignees_spec.js
index 58b174059fa..a189d3656a2 100644
--- a/spec/frontend/sidebar/components/assignees/sidebar_assignees_spec.js
+++ b/spec/frontend/sidebar/components/assignees/sidebar_assignees_spec.js
@@ -39,9 +39,6 @@ describe('sidebar assignees', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
-
SidebarService.singleton = null;
SidebarStore.singleton = null;
SidebarMediator.singleton = null;
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 3aca346ff5f..9f7c587ca9d 100644
--- a/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
+++ b/spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
@@ -5,8 +5,8 @@ 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 { createAlert } from '~/flash';
-import { IssuableType } from '~/issues/constants';
+import { createAlert } from '~/alert';
+import { TYPE_MERGE_REQUEST } from '~/issues/constants';
import SidebarAssigneesRealtime from '~/sidebar/components/assignees/assignees_realtime.vue';
import IssuableAssignees from '~/sidebar/components/assignees/issuable_assignees.vue';
import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue';
@@ -17,7 +17,7 @@ import updateIssueAssigneesMutation from '~/sidebar/queries/update_issue_assigne
import UserSelect from '~/vue_shared/components/user_select/user_select.vue';
import { issuableQueryResponse, updateIssueAssigneesMutationResponse } from '../../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
const updateIssueAssigneesMutationSuccess = jest
.fn()
@@ -98,10 +98,7 @@ describe('Sidebar assignees widget', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
fakeApollo = null;
- delete gon.current_username;
});
describe('with passed initial assignees', () => {
@@ -397,7 +394,7 @@ describe('Sidebar assignees widget', () => {
});
it('does not render invite members link on non-issue sidebar', async () => {
- createComponent({ props: { issuableType: IssuableType.MergeRequest } });
+ createComponent({ props: { issuableType: TYPE_MERGE_REQUEST } });
await waitForPromises();
expect(findInviteMembersLink().exists()).toBe(false);
});
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 6c22d2f687d..27c31ac56c9 100644
--- a/spec/frontend/sidebar/components/assignees/sidebar_editable_item_spec.js
+++ b/spec/frontend/sidebar/components/assignees/sidebar_editable_item_spec.js
@@ -21,11 +21,6 @@ describe('boards sidebar remove issue', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('template', () => {
it('renders title', () => {
const title = 'Sidebar item title';
diff --git a/spec/frontend/sidebar/components/assignees/sidebar_invite_members_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_invite_members_spec.js
index b738d931040..501048bf056 100644
--- a/spec/frontend/sidebar/components/assignees/sidebar_invite_members_spec.js
+++ b/spec/frontend/sidebar/components/assignees/sidebar_invite_members_spec.js
@@ -16,10 +16,6 @@ describe('Sidebar invite members component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when directly inviting members', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js b/spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js
index be0b14fa997..7895274ab6d 100644
--- a/spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js
+++ b/spec/frontend/sidebar/components/assignees/sidebar_participant_spec.js
@@ -1,6 +1,6 @@
import { GlAvatarLabeled, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import SidebarParticipant from '~/sidebar/components/assignees/sidebar_participant.vue';
const user = {
@@ -32,10 +32,6 @@ describe('Sidebar participant component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('does not show `Busy` status when user is not busy', () => {
createComponent();
@@ -56,13 +52,13 @@ describe('Sidebar participant component', () => {
describe('when on merge request sidebar', () => {
it('when project member cannot merge', () => {
- createComponent({ issuableType: IssuableType.MergeRequest });
+ createComponent({ issuableType: TYPE_MERGE_REQUEST });
expect(findIcon().exists()).toBe(true);
});
it('when project member can merge', () => {
- createComponent({ issuableType: IssuableType.MergeRequest, canMerge: true });
+ createComponent({ issuableType: TYPE_MERGE_REQUEST, canMerge: true });
expect(findIcon().exists()).toBe(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 03c2e1a37a9..c74a714cca4 100644
--- a/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js
+++ b/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js
@@ -24,10 +24,6 @@ describe('UncollapsedAssigneeList component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
const findMoreButton = () => wrapper.find('.user-list-more button');
describe('One assignee/user', () => {
diff --git a/spec/frontend/sidebar/components/assignees/user_name_with_status_spec.js b/spec/frontend/sidebar/components/assignees/user_name_with_status_spec.js
index 37c16bc9235..877d7cd61ee 100644
--- a/spec/frontend/sidebar/components/assignees/user_name_with_status_spec.js
+++ b/spec/frontend/sidebar/components/assignees/user_name_with_status_spec.js
@@ -18,10 +18,6 @@ describe('UserNameWithStatus', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('will render the users name', () => {
expect(wrapper.html()).toContain(name);
});
diff --git a/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_content_spec.js b/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_content_spec.js
index 81354d64a90..4a2b3b30e6d 100644
--- a/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_content_spec.js
+++ b/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_content_spec.js
@@ -18,10 +18,6 @@ describe('Sidebar Confidentiality Content', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('emits `expandSidebar` event on collapsed icon click', () => {
createComponent();
findCollapsedIcon().trigger('click');
diff --git a/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_form_spec.js b/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_form_spec.js
index b27f7c6b4e1..1ca20dad1c6 100644
--- a/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_form_spec.js
+++ b/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_form_spec.js
@@ -2,11 +2,11 @@ import { GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import SidebarConfidentialityForm from '~/sidebar/components/confidential/sidebar_confidentiality_form.vue';
import { confidentialityQueries } from '~/sidebar/constants';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Sidebar Confidentiality Form', () => {
let wrapper;
@@ -38,10 +38,6 @@ describe('Sidebar Confidentiality Form', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('emits a `closeForm` event when Cancel button is clicked', () => {
createComponent();
findCancelButton().vm.$emit('click');
@@ -58,7 +54,7 @@ describe('Sidebar Confidentiality Form', () => {
expect(findConfidentialToggle().props('loading')).toBe(true);
});
- it('creates a flash if mutation is rejected', async () => {
+ it('creates an alert if mutation is rejected', async () => {
createComponent({ mutate: jest.fn().mockRejectedValue('Error!') });
findConfidentialToggle().vm.$emit('click', new MouseEvent('click'));
await waitForPromises();
@@ -68,7 +64,7 @@ describe('Sidebar Confidentiality Form', () => {
});
});
- it('creates a flash if mutation contains errors', async () => {
+ it('creates an alert if mutation contains errors', async () => {
createComponent({
mutate: jest.fn().mockResolvedValue({
data: { issuableSetConfidential: { errors: ['Houston, we have a problem!'] } },
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 e486a8e9ec7..39b30307dd7 100644
--- a/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js
+++ b/spec/frontend/sidebar/components/confidential/sidebar_confidentiality_widget_spec.js
@@ -4,7 +4,7 @@ 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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import SidebarConfidentialityContent from '~/sidebar/components/confidential/sidebar_confidentiality_content.vue';
import SidebarConfidentialityForm from '~/sidebar/components/confidential/sidebar_confidentiality_form.vue';
import SidebarConfidentialityWidget, {
@@ -14,7 +14,7 @@ import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'
import issueConfidentialQuery from '~/sidebar/queries/issue_confidential.query.graphql';
import { issueConfidentialityResponse } from '../../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(VueApollo);
@@ -48,7 +48,6 @@ describe('Sidebar Confidentiality Widget', () => {
};
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
@@ -120,7 +119,7 @@ describe('Sidebar Confidentiality Widget', () => {
});
});
- it('displays a flash message when query is rejected', async () => {
+ it('displays an alert message when query is rejected', async () => {
createComponent({
confidentialQueryHandler: jest.fn().mockRejectedValue('Houston, we have a problem'),
});
diff --git a/spec/frontend/sidebar/components/copy/copyable_field_spec.js b/spec/frontend/sidebar/components/copy/copyable_field_spec.js
index 7790d77bc65..03e131aab35 100644
--- a/spec/frontend/sidebar/components/copy/copyable_field_spec.js
+++ b/spec/frontend/sidebar/components/copy/copyable_field_spec.js
@@ -20,10 +20,6 @@ describe('SidebarCopyableField', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findClipboardButton = () => wrapper.findComponent(ClipboardButton);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
diff --git a/spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js b/spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js
index c3de076d6aa..2ae80b2c97b 100644
--- a/spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js
+++ b/spec/frontend/sidebar/components/copy/sidebar_reference_widget_spec.js
@@ -3,7 +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 { IssuableType, TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import SidebarReferenceWidget from '~/sidebar/components/copy/sidebar_reference_widget.vue';
import issueReferenceQuery from '~/sidebar/queries/issue_reference.query.graphql';
import mergeRequestReferenceQuery from '~/sidebar/queries/merge_request_reference.query.graphql';
@@ -39,10 +39,6 @@ describe('Sidebar Reference Widget', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when reference is loading', () => {
it('sets CopyableField `is-loading` prop to `true`', () => {
createComponent({ referenceQueryHandler: jest.fn().mockReturnValue(new Promise(() => {})) });
@@ -52,7 +48,7 @@ describe('Sidebar Reference Widget', () => {
describe.each([
[TYPE_ISSUE, issueReferenceQuery],
- [IssuableType.MergeRequest, mergeRequestReferenceQuery],
+ [TYPE_MERGE_REQUEST, mergeRequestReferenceQuery],
])('when issuableType is %s', (issuableType, referenceQuery) => {
it('sets CopyableField `value` prop to reference value', async () => {
createComponent({
diff --git a/spec/frontend/sidebar/components/crm_contacts/crm_contacts_spec.js b/spec/frontend/sidebar/components/crm_contacts/crm_contacts_spec.js
index ca43c219d92..546cabd07d3 100644
--- a/spec/frontend/sidebar/components/crm_contacts/crm_contacts_spec.js
+++ b/spec/frontend/sidebar/components/crm_contacts/crm_contacts_spec.js
@@ -3,7 +3,7 @@ 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 { createAlert } from '~/alert';
import CrmContacts from '~/sidebar/components/crm_contacts/crm_contacts.vue';
import getIssueCrmContactsQuery from '~/sidebar/queries/get_issue_crm_contacts.query.graphql';
import issueCrmContactsSubscription from '~/sidebar/queries/issue_crm_contacts.subscription.graphql';
@@ -13,7 +13,7 @@ import {
issueCrmContactsUpdateNullResponse,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Issue crm contacts component', () => {
Vue.use(VueApollo);
@@ -39,7 +39,6 @@ describe('Issue crm contacts component', () => {
};
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
diff --git a/spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js b/spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js
index 67413cffdda..b9c8655d5d8 100644
--- a/spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js
+++ b/spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js
@@ -4,7 +4,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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue';
import SidebarFormattedDate from '~/sidebar/components/date/sidebar_formatted_date.vue';
import SidebarInheritDate from '~/sidebar/components/date/sidebar_inherit_date.vue';
@@ -13,7 +13,7 @@ import epicStartDateQuery from '~/sidebar/queries/epic_start_date.query.graphql'
import issueDueDateQuery from '~/sidebar/queries/issue_due_date.query.graphql';
import { issuableDueDateResponse, issuableStartDateResponse } from '../../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(VueApollo);
@@ -22,10 +22,6 @@ describe('Sidebar date Widget', () => {
let fakeApollo;
const date = '2021-04-15';
- window.gon = {
- first_day_of_week: 1,
- };
-
const findEditableItem = () => wrapper.findComponent(SidebarEditableItem);
const findPopoverIcon = () => wrapper.find('[data-testid="inherit-date-popover"]');
const findDatePicker = () => wrapper.findComponent(GlDatepicker);
@@ -61,8 +57,11 @@ describe('Sidebar date Widget', () => {
});
};
+ beforeEach(() => {
+ window.gon.first_day_of_week = 1;
+ });
+
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
@@ -159,7 +158,7 @@ describe('Sidebar date Widget', () => {
expect(wrapper.findComponent(SidebarInheritDate).exists()).toBe(false);
});
- it('displays a flash message when query is rejected', async () => {
+ it('displays an alert message when query is rejected', async () => {
createComponent({
dueDateQueryHandler: jest.fn().mockRejectedValue('Houston, we have a problem'),
});
diff --git a/spec/frontend/sidebar/components/date/sidebar_formatted_date_spec.js b/spec/frontend/sidebar/components/date/sidebar_formatted_date_spec.js
index cbe01263dcd..1bb910c53ea 100644
--- a/spec/frontend/sidebar/components/date/sidebar_formatted_date_spec.js
+++ b/spec/frontend/sidebar/components/date/sidebar_formatted_date_spec.js
@@ -27,10 +27,6 @@ describe('SidebarFormattedDate', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays formatted date', () => {
expect(findFormattedDate().text()).toBe('Apr 15, 2021');
});
diff --git a/spec/frontend/sidebar/components/date/sidebar_inherit_date_spec.js b/spec/frontend/sidebar/components/date/sidebar_inherit_date_spec.js
index a7556b9110c..97debe3088d 100644
--- a/spec/frontend/sidebar/components/date/sidebar_inherit_date_spec.js
+++ b/spec/frontend/sidebar/components/date/sidebar_inherit_date_spec.js
@@ -31,10 +31,6 @@ describe('SidebarInheritDate', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays formatted fixed and inherited dates with radio buttons', () => {
expect(wrapper.findAllComponents(SidebarFormattedDate)).toHaveLength(2);
expect(wrapper.findAllComponents(GlFormRadio)).toHaveLength(2);
diff --git a/spec/frontend/sidebar/components/incidents/escalation_status_spec.js b/spec/frontend/sidebar/components/incidents/escalation_status_spec.js
index 1a78ce4ddee..e356f02a36b 100644
--- a/spec/frontend/sidebar/components/incidents/escalation_status_spec.js
+++ b/spec/frontend/sidebar/components/incidents/escalation_status_spec.js
@@ -17,10 +17,6 @@ describe('EscalationStatus', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
const findDropdownComponent = () => wrapper.findComponent(GlDropdown);
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findDropdownMenu = () => findDropdownComponent().find('.dropdown-menu');
diff --git a/spec/frontend/sidebar/components/incidents/sidebar_escalation_status_spec.js b/spec/frontend/sidebar/components/incidents/sidebar_escalation_status_spec.js
index 2dded61c073..00b57b4916e 100644
--- a/spec/frontend/sidebar/components/incidents/sidebar_escalation_status_spec.js
+++ b/spec/frontend/sidebar/components/incidents/sidebar_escalation_status_spec.js
@@ -18,11 +18,11 @@ import {
} from '~/sidebar/constants';
import waitForPromises from 'helpers/wait_for_promises';
import EscalationStatus from 'ee_else_ce/sidebar/components/incidents/escalation_status.vue';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { logError } from '~/lib/logger';
jest.mock('~/lib/logger');
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(VueApollo);
@@ -57,7 +57,7 @@ describe('SidebarEscalationStatus', () => {
canUpdate: true,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
apolloProvider,
});
diff --git a/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_button_spec.js b/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_button_spec.js
index 4f2a89e20db..084ca5ed3fc 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_button_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_button_spec.js
@@ -29,10 +29,6 @@ describe('DropdownButton', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findDropdownButton = () => wrapper.findComponent(GlButton);
const findDropdownText = () => wrapper.find('.dropdown-toggle-text');
const findDropdownIcon = () => wrapper.findComponent(GlIcon);
diff --git a/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_contents_create_view_spec.js b/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_contents_create_view_spec.js
index 59e95edfa20..bb7554ff21d 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_contents_create_view_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_contents_create_view_spec.js
@@ -32,10 +32,6 @@ describe('DropdownContentsCreateView', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('computed', () => {
describe('disableCreate', () => {
it('returns `true` when label title and color is not defined', () => {
@@ -174,11 +170,17 @@ describe('DropdownContentsCreateView', () => {
});
await nextTick();
- const colorPreviewEl = wrapper.find('.color-input-container > .dropdown-label-color-preview');
- const colorInputEl = wrapper.find('.color-input-container').findComponent(GlFormInput);
+ const colorPreviewEl = wrapper
+ .find('.color-input-container')
+ .findAllComponents(GlFormInput)
+ .at(0);
+ const colorInputEl = wrapper
+ .find('.color-input-container')
+ .findAllComponents(GlFormInput)
+ .at(1);
expect(colorPreviewEl.exists()).toBe(true);
- expect(colorPreviewEl.attributes('style')).toContain('background-color');
+ expect(colorPreviewEl.attributes('value')).toBe('#ff0000');
expect(colorInputEl.exists()).toBe(true);
expect(colorInputEl.attributes('placeholder')).toBe('Use custom color #FF0000');
expect(colorInputEl.attributes('value')).toBe('#ff0000');
diff --git a/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_contents_labels_view_spec.js b/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_contents_labels_view_spec.js
index 865dc8fe8fb..7940518f1e8 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_contents_labels_view_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_contents_labels_view_spec.js
@@ -51,10 +51,6 @@ describe('DropdownContentsLabelsView', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findDropdownContent = () => wrapper.find('[data-testid="dropdown-content"]');
const findDropdownTitle = () => wrapper.find('[data-testid="dropdown-title"]');
const findDropdownFooter = () => wrapper.find('[data-testid="dropdown-footer"]');
diff --git a/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_contents_spec.js b/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_contents_spec.js
index e9ffda7c251..0a17c5f8721 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_contents_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_contents_spec.js
@@ -28,10 +28,6 @@ describe('DropdownContent', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('computed', () => {
describe('dropdownContentsView', () => {
it('returns string "dropdown-contents-create-view" when `showDropdownContentsCreateView` prop is `true`', () => {
diff --git a/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_title_spec.js b/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_title_spec.js
index 6c3fda421ff..367f6007194 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_title_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_title_spec.js
@@ -31,10 +31,6 @@ describe('DropdownTitle', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
it('renders component container element with string "Labels"', () => {
expect(wrapper.text()).toContain('Labels');
diff --git a/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_value_collapsed_spec.js b/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_value_collapsed_spec.js
index 56f25a1c6a4..6684cf0c5f4 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_value_collapsed_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_value_collapsed_spec.js
@@ -19,15 +19,11 @@ describe('DropdownValueCollapsedComponent', () => {
wrapper = shallowMount(DropdownValueCollapsedComponent, {
propsData: { ...defaultProps, ...props },
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGlIcon = () => wrapper.findComponent(GlIcon);
const getTooltip = () => getBinding(wrapper.element, 'gl-tooltip');
diff --git a/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_value_spec.js b/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_value_spec.js
index a1ccc9d2ab1..70aafceb00c 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_value_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_vue/dropdown_value_spec.js
@@ -28,10 +28,6 @@ describe('DropdownValue', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('methods', () => {
describe('labelFilterUrl', () => {
it('returns a label filter URL based on provided label param', () => {
diff --git a/spec/frontend/sidebar/components/labels/labels_select_vue/label_item_spec.js b/spec/frontend/sidebar/components/labels/labels_select_vue/label_item_spec.js
index e14c0e308ce..468dd14c9ee 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_vue/label_item_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_vue/label_item_spec.js
@@ -26,10 +26,6 @@ describe('LabelItem', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
it('renders gl-link component', () => {
expect(wrapper.findComponent(GlLink).exists()).toBe(true);
diff --git a/spec/frontend/sidebar/components/labels/labels_select_vue/labels_select_root_spec.js b/spec/frontend/sidebar/components/labels/labels_select_vue/labels_select_root_spec.js
index a3b10c18374..806064b2202 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_vue/labels_select_root_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_vue/labels_select_root_spec.js
@@ -40,10 +40,6 @@ describe('LabelsSelectRoot', () => {
store = new Vuex.Store(labelsSelectModule());
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('methods', () => {
describe('handleVuexActionDispatch', () => {
const touchedLabels = [
diff --git a/spec/frontend/sidebar/components/labels/labels_select_vue/store/actions_spec.js b/spec/frontend/sidebar/components/labels/labels_select_vue/store/actions_spec.js
index 55651bccaa8..c27afb75375 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_vue/store/actions_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_vue/store/actions_spec.js
@@ -1,14 +1,14 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import * as actions from '~/sidebar/components/labels/labels_select_vue/store/actions';
import * as types from '~/sidebar/components/labels/labels_select_vue/store/mutation_types';
import defaultState from '~/sidebar/components/labels/labels_select_vue/store/state';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('LabelsSelect Actions', () => {
let state;
@@ -100,7 +100,7 @@ describe('LabelsSelect Actions', () => {
);
});
- it('shows flash error', () => {
+ it('shows alert error', () => {
actions.receiveLabelsFailure({ commit: () => {} });
expect(createAlert).toHaveBeenCalledWith({ message: 'Error fetching labels.' });
@@ -184,7 +184,7 @@ describe('LabelsSelect Actions', () => {
);
});
- it('shows flash error', () => {
+ it('shows alert error', () => {
actions.receiveCreateLabelFailure({ commit: () => {} });
expect(createAlert).toHaveBeenCalledWith({ message: 'Error creating label.' });
diff --git a/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_create_view_spec.js b/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_create_view_spec.js
index 79b164b0ea7..4ca0a813da2 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_create_view_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_create_view_spec.js
@@ -4,7 +4,7 @@ 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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { workspaceLabelsQueries } from '~/sidebar/constants';
import DropdownContentsCreateView from '~/sidebar/components/labels/labels_select_widget/dropdown_contents_create_view.vue';
import createLabelMutation from '~/sidebar/components/labels/labels_select_widget/graphql/create_label.mutation.graphql';
@@ -15,7 +15,7 @@ import {
workspaceLabelsQueryResponse,
} from './mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
const colors = Object.keys(mockSuggestedColors);
@@ -87,10 +87,6 @@ describe('DropdownContentsCreateView', () => {
gon.suggested_label_colors = mockSuggestedColors;
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a palette of 21 colors', () => {
createComponent();
expect(findAllColors()).toHaveLength(21);
@@ -103,7 +99,7 @@ describe('DropdownContentsCreateView', () => {
findAllColors().at(0).vm.$emit('click', new Event('mouseclick'));
await nextTick();
- expect(findSelectedColor().attributes('style')).toBe('background-color: rgb(0, 153, 102);');
+ expect(findSelectedColor().attributes('value')).toBe('#009966');
});
it('shows correct color hex code after selecting a color', async () => {
diff --git a/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view_spec.js b/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view_spec.js
index 913badccbe4..c351a60735b 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view_spec.js
@@ -9,7 +9,7 @@ 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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { DropdownVariant } from '~/sidebar/components/labels/labels_select_widget/constants';
import DropdownContentsLabelsView from '~/sidebar/components/labels/labels_select_widget/dropdown_contents_labels_view.vue';
@@ -17,7 +17,7 @@ import projectLabelsQuery from '~/sidebar/components/labels/labels_select_widget
import LabelItem from '~/sidebar/components/labels/labels_select_widget/label_item.vue';
import { mockConfig, workspaceLabelsQueryResponse } from './mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(VueApollo);
@@ -64,10 +64,6 @@ describe('DropdownContentsLabelsView', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findLabels = () => wrapper.findAllComponents(LabelItem);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findObserver = () => wrapper.findComponent(GlIntersectionObserver);
diff --git a/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_spec.js b/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_spec.js
index 9bbb1413ee9..e9023cb9ff6 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_contents_spec.js
@@ -66,10 +66,6 @@ describe('DropdownContent', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findCreateView = () => wrapper.findComponent(DropdownContentsCreateView);
const findLabelsView = () => wrapper.findComponent(DropdownContentsLabelsView);
const findDropdownHeader = () => wrapper.findComponent(DropdownHeaderStub);
diff --git a/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_footer_spec.js b/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_footer_spec.js
index 9a6e0ca3ccd..ad1edaa6671 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_footer_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_footer_spec.js
@@ -20,10 +20,6 @@ describe('DropdownFooter', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findCreateLabelButton = () => wrapper.find('[data-testid="create-label-button"]');
describe('Labels view', () => {
diff --git a/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_header_spec.js b/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_header_spec.js
index d9001dface4..824f91812fb 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_header_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_header_spec.js
@@ -28,10 +28,6 @@ describe('DropdownHeader', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findSearchInput = () => wrapper.findComponent(GlSearchBoxByType);
const findGoBackButton = () => wrapper.findByTestId('go-back-button');
const findDropdownTitle = () => wrapper.findByTestId('dropdown-header-title');
diff --git a/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_value_spec.js b/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_value_spec.js
index 585048983c9..d70b989b493 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_value_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_widget/dropdown_value_spec.js
@@ -30,10 +30,6 @@ describe('DropdownValue', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when there are no labels', () => {
beforeEach(() => {
createComponent(
diff --git a/spec/frontend/sidebar/components/labels/labels_select_widget/embedded_labels_list_spec.js b/spec/frontend/sidebar/components/labels/labels_select_widget/embedded_labels_list_spec.js
index 4fa65c752f9..715dd4e034e 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_widget/embedded_labels_list_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_widget/embedded_labels_list_spec.js
@@ -30,10 +30,6 @@ describe('EmbeddedLabelsList', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when there are no labels', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/sidebar/components/labels/labels_select_widget/label_item_spec.js b/spec/frontend/sidebar/components/labels/labels_select_widget/label_item_spec.js
index 74188a77994..377d1894411 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_widget/label_item_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_widget/label_item_spec.js
@@ -19,10 +19,6 @@ describe('LabelItem', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
it('renders label color element', () => {
const colorEl = wrapper.find('[data-testid="label-color-box"]');
diff --git a/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js b/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js
index fd8e72bac49..3101fd90f2e 100644
--- a/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js
+++ b/spec/frontend/sidebar/components/labels/labels_select_widget/labels_select_root_spec.js
@@ -3,8 +3,8 @@ 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 { createAlert } from '~/flash';
-import { IssuableType, TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
+import { createAlert } from '~/alert';
+import { TYPE_EPIC, TYPE_ISSUE, TYPE_MERGE_REQUEST, TYPE_TEST_CASE } from '~/issues/constants';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
import DropdownContents from '~/sidebar/components/labels/labels_select_widget/dropdown_contents.vue';
import DropdownValue from '~/sidebar/components/labels/labels_select_widget/dropdown_value.vue';
@@ -25,7 +25,7 @@ import {
mockRegularLabel,
} from './mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(VueApollo);
@@ -36,9 +36,9 @@ const errorQueryHandler = jest.fn().mockRejectedValue('Houston, we have a proble
const updateLabelsMutation = {
[TYPE_ISSUE]: updateIssueLabelsMutation,
- [IssuableType.MergeRequest]: updateMergeRequestLabelsMutation,
+ [TYPE_MERGE_REQUEST]: updateMergeRequestLabelsMutation,
[TYPE_EPIC]: updateEpicLabelsMutation,
- [IssuableType.TestCase]: updateTestCaseLabelsMutation,
+ [TYPE_TEST_CASE]: updateTestCaseLabelsMutation,
};
describe('LabelsSelectRoot', () => {
@@ -83,10 +83,6 @@ describe('LabelsSelectRoot', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders component with classes `labels-select-wrapper gl-relative`', () => {
createComponent();
expect(wrapper.classes()).toEqual(['labels-select-wrapper', 'gl-relative']);
@@ -150,7 +146,7 @@ describe('LabelsSelectRoot', () => {
});
});
- it('creates flash with error message when query is rejected', async () => {
+ it('creates alert with error message when query is rejected', async () => {
createComponent({ queryHandler: errorQueryHandler });
await waitForPromises();
expect(createAlert).toHaveBeenCalledWith({ message: 'Error fetching labels.' });
@@ -214,9 +210,9 @@ describe('LabelsSelectRoot', () => {
describe.each`
issuableType
${TYPE_ISSUE}
- ${IssuableType.MergeRequest}
+ ${TYPE_MERGE_REQUEST}
${TYPE_EPIC}
- ${IssuableType.TestCase}
+ ${TYPE_TEST_CASE}
`('when updating labels for $issuableType', ({ issuableType }) => {
const label = { id: 'gid://gitlab/ProjectLabel/2' };
diff --git a/spec/frontend/sidebar/components/lock/edit_form_buttons_spec.js b/spec/frontend/sidebar/components/lock/edit_form_buttons_spec.js
index 2abb0c24d7d..ad9efc371f0 100644
--- a/spec/frontend/sidebar/components/lock/edit_form_buttons_spec.js
+++ b/spec/frontend/sidebar/components/lock/edit_form_buttons_spec.js
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { createStore as createMrStore } from '~/mr_notes/stores';
import createStore from '~/notes/stores';
import EditFormButtons from '~/sidebar/components/lock/edit_form_buttons.vue';
@@ -8,7 +8,7 @@ import eventHub from '~/sidebar/event_hub';
import { ISSUABLE_TYPE_ISSUE, ISSUABLE_TYPE_MR } from './constants';
jest.mock('~/sidebar/event_hub', () => ({ $emit: jest.fn() }));
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('EditFormButtons', () => {
let wrapper;
@@ -51,11 +51,6 @@ describe('EditFormButtons', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe.each`
pageType
${ISSUABLE_TYPE_ISSUE} | ${ISSUABLE_TYPE_MR}
@@ -128,7 +123,7 @@ describe('EditFormButtons', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('closeLockForm');
});
- it('does not flash an error message', () => {
+ it('does not alert an error message', () => {
expect(createAlert).not.toHaveBeenCalled();
});
});
@@ -161,7 +156,7 @@ describe('EditFormButtons', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('closeLockForm');
});
- it('calls flash with the correct message', () => {
+ it('calls alert with the correct message', () => {
expect(createAlert).toHaveBeenCalledWith({
message: `Something went wrong trying to change the locked state of this ${issuableDisplayName}`,
});
diff --git a/spec/frontend/sidebar/components/lock/edit_form_spec.js b/spec/frontend/sidebar/components/lock/edit_form_spec.js
index 4ae9025ee39..06cce7bd7ca 100644
--- a/spec/frontend/sidebar/components/lock/edit_form_spec.js
+++ b/spec/frontend/sidebar/components/lock/edit_form_spec.js
@@ -24,11 +24,6 @@ describe('Edit Form Dropdown', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe.each`
pageType
${ISSUABLE_TYPE_ISSUE} | ${ISSUABLE_TYPE_MR}
diff --git a/spec/frontend/sidebar/components/lock/issuable_lock_form_spec.js b/spec/frontend/sidebar/components/lock/issuable_lock_form_spec.js
index 8f825847cfc..d26ef7298ce 100644
--- a/spec/frontend/sidebar/components/lock/issuable_lock_form_spec.js
+++ b/spec/frontend/sidebar/components/lock/issuable_lock_form_spec.js
@@ -62,16 +62,11 @@ describe('IssuableLockForm', () => {
...props,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe.each`
pageType
${ISSUABLE_TYPE_ISSUE} | ${ISSUABLE_TYPE_MR}
diff --git a/spec/frontend/sidebar/components/milestone/milestone_dropdown_spec.js b/spec/frontend/sidebar/components/milestone/milestone_dropdown_spec.js
index b492753867b..8a0db1715f3 100644
--- a/spec/frontend/sidebar/components/milestone/milestone_dropdown_spec.js
+++ b/spec/frontend/sidebar/components/milestone/milestone_dropdown_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { TYPE_ISSUE, WorkspaceType } from '~/issues/constants';
+import { TYPE_ISSUE, WORKSPACE_PROJECT } from '~/issues/constants';
import { __ } from '~/locale';
import MilestoneDropdown from '~/sidebar/components/milestone/milestone_dropdown.vue';
import SidebarDropdown from '~/sidebar/components/sidebar_dropdown.vue';
@@ -12,7 +12,7 @@ describe('MilestoneDropdown component', () => {
const propsData = {
attrWorkspacePath: 'full/path',
issuableType: TYPE_ISSUE,
- workspaceType: WorkspaceType.project,
+ workspaceType: WORKSPACE_PROJECT,
};
const findHiddenInput = () => wrapper.find('input');
diff --git a/spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js b/spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js
index 72279f44e80..e247f5d27fa 100644
--- a/spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js
+++ b/spec/frontend/sidebar/components/move/issuable_move_dropdown_spec.js
@@ -63,7 +63,6 @@ describe('IssuableMoveDropdown', () => {
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
});
diff --git a/spec/frontend/sidebar/components/move/move_issue_button_spec.js b/spec/frontend/sidebar/components/move/move_issue_button_spec.js
index acd6b23c1f5..eb5e23c6047 100644
--- a/spec/frontend/sidebar/components/move/move_issue_button_spec.js
+++ b/spec/frontend/sidebar/components/move/move_issue_button_spec.js
@@ -4,14 +4,14 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { visitUrl } from '~/lib/utils/url_utility';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import ProjectSelect from '~/sidebar/components/move/issuable_move_dropdown.vue';
import MoveIssueButton from '~/sidebar/components/move/move_issue_button.vue';
import moveIssueMutation from '~/sidebar/queries/move_issue.mutation.graphql';
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn(),
}));
@@ -118,7 +118,7 @@ describe('MoveIssueButton', () => {
expect(findProjectSelect().props('moveInProgress')).toBe(false);
});
- it('creates a flash and logs errors when a mutation returns errors', async () => {
+ it('creates an alert and logs errors when a mutation returns errors', async () => {
createComponent(resolvedMutationWithErrorsMock);
emitProjectSelectEvent();
diff --git a/spec/frontend/sidebar/components/move/move_issues_button_spec.js b/spec/frontend/sidebar/components/move/move_issues_button_spec.js
index c65bad642a0..662a39c829d 100644
--- a/spec/frontend/sidebar/components/move/move_issues_button_spec.js
+++ b/spec/frontend/sidebar/components/move/move_issues_button_spec.js
@@ -6,7 +6,7 @@ import { GlAlert } from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { logError } from '~/lib/logger';
import IssuableMoveDropdown from '~/sidebar/components/move/issuable_move_dropdown.vue';
import issuableEventHub from '~/issues/list/eventhub';
@@ -22,7 +22,7 @@ import {
WORK_ITEM_TYPE_ENUM_TEST_CASE,
} from '~/work_items/constants';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/logger');
useMockLocationHelper();
@@ -159,7 +159,6 @@ describe('MoveIssuesButton', () => {
});
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
@@ -389,7 +388,7 @@ describe('MoveIssuesButton', () => {
});
describe('shows errors', () => {
- it('does not create flashes or logs errors when no issue is selected', async () => {
+ it('does not create alerts or logs errors when no issue is selected', async () => {
createComponent();
emitMoveIssuablesEvent();
@@ -399,7 +398,7 @@ describe('MoveIssuesButton', () => {
expect(createAlert).not.toHaveBeenCalled();
});
- it('does not create flashes or logs errors when only tasks are selected', async () => {
+ it('does not create alerts or logs errors when only tasks are selected', async () => {
createComponent({ selectedIssuables: selectedIssuesMocks.tasksOnly });
emitMoveIssuablesEvent();
@@ -409,7 +408,7 @@ describe('MoveIssuesButton', () => {
expect(createAlert).not.toHaveBeenCalled();
});
- it('does not create flashes or logs errors when only test cases are selected', async () => {
+ it('does not create alerts or logs errors when only test cases are selected', async () => {
createComponent({ selectedIssuables: selectedIssuesMocks.testCasesOnly });
emitMoveIssuablesEvent();
@@ -419,7 +418,7 @@ describe('MoveIssuesButton', () => {
expect(createAlert).not.toHaveBeenCalled();
});
- it('does not create flashes or logs errors when only tasks and test cases are selected', async () => {
+ it('does not create alerts or logs errors when only tasks and test cases are selected', async () => {
createComponent({ selectedIssuables: selectedIssuesMocks.tasksAndTestCases });
emitMoveIssuablesEvent();
@@ -429,7 +428,7 @@ describe('MoveIssuesButton', () => {
expect(createAlert).not.toHaveBeenCalled();
});
- it('does not create flashes or logs errors when issues are moved without errors', async () => {
+ it('does not create alerts or logs errors when issues are moved without errors', async () => {
createComponent(
{ selectedIssuables: selectedIssuesMocks.issuesTasksAndTestCases },
resolvedMutationWithoutErrorsMock,
@@ -442,7 +441,7 @@ describe('MoveIssuesButton', () => {
expect(createAlert).not.toHaveBeenCalled();
});
- it('creates a flash and logs errors when a mutation returns errors', async () => {
+ it('creates an alert and logs errors when a mutation returns errors', async () => {
createComponent(
{ selectedIssuables: selectedIssuesMocks.issuesTasksAndTestCases },
resolvedMutationWithErrorsMock,
@@ -462,14 +461,14 @@ describe('MoveIssuesButton', () => {
`Error moving issue. Error message: ${mockMutationErrorMessage}`,
);
- // Only one flash is created even if multiple errors are reported
+ // Only one alert is created even if multiple errors are reported
expect(createAlert).toHaveBeenCalledTimes(1);
expect(createAlert).toHaveBeenCalledWith({
message: 'There was an error while moving the issues.',
});
});
- it('creates a flash but not logs errors when a mutation is rejected', async () => {
+ it('creates an alert but not logs errors when a mutation is rejected', async () => {
createComponent({ selectedIssuables: selectedIssuesMocks.issuesTasksAndTestCases });
emitMoveIssuablesEvent();
diff --git a/spec/frontend/sidebar/components/participants/participants_spec.js b/spec/frontend/sidebar/components/participants/participants_spec.js
index f7a626a189c..72d83ebeca4 100644
--- a/spec/frontend/sidebar/components/participants/participants_spec.js
+++ b/spec/frontend/sidebar/components/participants/participants_spec.js
@@ -1,203 +1,114 @@
-import { GlLoadingIcon } from '@gitlab/ui';
+import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
import Participants from '~/sidebar/components/participants/participants.vue';
-const PARTICIPANT = {
- id: 1,
- state: 'active',
- username: 'marcene',
- name: 'Allie Will',
- web_url: 'foo.com',
- avatar_url: 'gravatar.com/avatar/xxx',
-};
-
-const PARTICIPANT_LIST = [PARTICIPANT, { ...PARTICIPANT, id: 2 }, { ...PARTICIPANT, id: 3 }];
-
-describe('Participants', () => {
+describe('Participants component', () => {
let wrapper;
- const getMoreParticipantsButton = () => wrapper.find('[data-testid="more-participants"]');
- const getCollapsedParticipantsCount = () => wrapper.find('[data-testid="collapsed-count"]');
+ const participant = {
+ id: 1,
+ state: 'active',
+ username: 'marcene',
+ name: 'Allie Will',
+ web_url: 'foo.com',
+ avatar_url: 'gravatar.com/avatar/xxx',
+ };
- const mountComponent = (propsData) =>
- shallowMount(Participants, {
- propsData,
- });
+ const participants = [participant, { ...participant, id: 2 }, { ...participant, id: 3 }];
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findMoreParticipantsButton = () => wrapper.findComponent(GlButton);
+ const findCollapsedIcon = () => wrapper.find('.sidebar-collapsed-icon');
+ const findParticipantsAuthor = () => wrapper.findAll('.participants-author');
+
+ const mountComponent = (propsData) => shallowMount(Participants, { propsData });
describe('collapsed sidebar state', () => {
it('shows loading spinner when loading', () => {
- wrapper = mountComponent({
- loading: true,
- });
+ wrapper = mountComponent({ loading: true });
- expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ expect(findLoadingIcon().exists()).toBe(true);
});
- it('does not show loading spinner not loading', () => {
- wrapper = mountComponent({
- loading: false,
- });
+ it('does not show loading spinner when not loading', () => {
+ wrapper = mountComponent({ loading: false });
- expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
+ expect(findLoadingIcon().exists()).toBe(false);
});
it('shows participant count when given', () => {
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- });
+ wrapper = mountComponent({ participants });
- expect(getCollapsedParticipantsCount().text()).toBe(`${PARTICIPANT_LIST.length}`);
+ expect(findCollapsedIcon().text()).toBe(participants.length.toString());
});
it('shows full participant count when there are hidden participants', () => {
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- numberOfLessParticipants: 1,
- });
+ wrapper = mountComponent({ participants, numberOfLessParticipants: 1 });
- expect(getCollapsedParticipantsCount().text()).toBe(`${PARTICIPANT_LIST.length}`);
+ expect(findCollapsedIcon().text()).toBe(participants.length.toString());
});
});
describe('expanded sidebar state', () => {
it('shows loading spinner when loading', () => {
- wrapper = mountComponent({
- loading: true,
- });
+ wrapper = mountComponent({ loading: true });
- expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
+ expect(findLoadingIcon().exists()).toBe(true);
});
- it('when only showing visible participants, shows an avatar only for each participant under the limit', async () => {
+ it('when only showing visible participants, shows an avatar only for each participant under the limit', () => {
const numberOfLessParticipants = 2;
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- numberOfLessParticipants,
- });
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- isShowingMoreParticipants: false,
- });
-
- await nextTick();
- expect(wrapper.findAll('.participants-author')).toHaveLength(numberOfLessParticipants);
+ wrapper = mountComponent({ participants, numberOfLessParticipants });
+
+ expect(findParticipantsAuthor()).toHaveLength(numberOfLessParticipants);
});
it('when only showing all participants, each has an avatar', async () => {
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- numberOfLessParticipants: 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({
- isShowingMoreParticipants: true,
- });
-
- await nextTick();
- expect(wrapper.findAll('.participants-author')).toHaveLength(PARTICIPANT_LIST.length);
+ wrapper = mountComponent({ participants, numberOfLessParticipants: 2 });
+
+ await findMoreParticipantsButton().vm.$emit('click');
+
+ expect(findParticipantsAuthor()).toHaveLength(participants.length);
});
it('does not have more participants link when they can all be shown', () => {
const numberOfLessParticipants = 100;
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- numberOfLessParticipants,
- });
-
- expect(PARTICIPANT_LIST.length).toBeLessThan(numberOfLessParticipants);
- expect(getMoreParticipantsButton().exists()).toBe(false);
- });
+ wrapper = mountComponent({ participants, numberOfLessParticipants });
- it('when too many participants, has more participants link to show more', async () => {
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- numberOfLessParticipants: 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({
- isShowingMoreParticipants: false,
- });
-
- await nextTick();
- expect(getMoreParticipantsButton().text()).toBe('+ 1 more');
+ expect(participants.length).toBeLessThan(numberOfLessParticipants);
+ expect(findMoreParticipantsButton().exists()).toBe(false);
});
- it('when too many participants and already showing them, has more participants link to show less', async () => {
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- numberOfLessParticipants: 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({
- isShowingMoreParticipants: true,
- });
-
- await nextTick();
- expect(getMoreParticipantsButton().text()).toBe('- show less');
- });
+ it('when too many participants, has more participants link to show more', () => {
+ wrapper = mountComponent({ participants, numberOfLessParticipants: 2 });
- it('clicking more participants link emits event', () => {
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- numberOfLessParticipants: 2,
- });
+ expect(findMoreParticipantsButton().text()).toBe('+ 1 more');
+ });
- expect(wrapper.vm.isShowingMoreParticipants).toBe(false);
+ it('when too many participants and already showing them, has more participants link to show less', async () => {
+ wrapper = mountComponent({ participants, numberOfLessParticipants: 2 });
- getMoreParticipantsButton().vm.$emit('click');
+ await findMoreParticipantsButton().vm.$emit('click');
- expect(wrapper.vm.isShowingMoreParticipants).toBe(true);
+ expect(findMoreParticipantsButton().text()).toBe('- show less');
});
- it('clicking on participants icon emits `toggleSidebar` event', async () => {
- wrapper = mountComponent({
- loading: false,
- participants: PARTICIPANT_LIST,
- numberOfLessParticipants: 2,
- });
-
- const spy = jest.spyOn(wrapper.vm, '$emit');
+ it('clicking on participants icon emits `toggleSidebar` event', () => {
+ wrapper = mountComponent({ participants, numberOfLessParticipants: 2 });
- wrapper.find('.sidebar-collapsed-icon').trigger('click');
+ findCollapsedIcon().trigger('click');
- await nextTick();
- expect(spy).toHaveBeenCalledWith('toggleSidebar');
- spy.mockRestore();
+ expect(wrapper.emitted('toggleSidebar')).toEqual([[]]);
});
});
describe('when not showing participants label', () => {
beforeEach(() => {
- wrapper = mountComponent({
- participants: PARTICIPANT_LIST,
- showParticipantLabel: false,
- });
+ wrapper = mountComponent({ participants, showParticipantLabel: false });
});
it('does not show sidebar collapsed icon', () => {
- expect(wrapper.find('.sidebar-collapsed-icon').exists()).toBe(false);
+ expect(findCollapsedIcon().exists()).toBe(false);
});
it('does not show participants label title', () => {
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..914e848eced 100644
--- a/spec/frontend/sidebar/components/participants/sidebar_participants_widget_spec.js
+++ b/spec/frontend/sidebar/components/participants/sidebar_participants_widget_spec.js
@@ -35,7 +35,6 @@ describe('Sidebar Participants Widget', () => {
};
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
diff --git a/spec/frontend/sidebar/components/reviewers/reviewer_title_spec.js b/spec/frontend/sidebar/components/reviewers/reviewer_title_spec.js
index 68ecd62e4c6..0f595ab21a5 100644
--- a/spec/frontend/sidebar/components/reviewers/reviewer_title_spec.js
+++ b/spec/frontend/sidebar/components/reviewers/reviewer_title_spec.js
@@ -16,11 +16,6 @@ describe('ReviewerTitle component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('reviewer title', () => {
it('renders reviewer', () => {
wrapper = createComponent({
diff --git a/spec/frontend/sidebar/components/reviewers/reviewers_spec.js b/spec/frontend/sidebar/components/reviewers/reviewers_spec.js
index 229f7ffbe04..016ec9225da 100644
--- a/spec/frontend/sidebar/components/reviewers/reviewers_spec.js
+++ b/spec/frontend/sidebar/components/reviewers/reviewers_spec.js
@@ -35,10 +35,6 @@ describe('Reviewer component', () => {
const findCollapsedChildren = () => wrapper.findAll('.sidebar-collapsed-icon > *');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('No reviewers/users', () => {
it('displays no reviewer icon when collapsed', () => {
createWrapper();
diff --git a/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js b/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js
index 57ae146a27a..a221d28704b 100644
--- a/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js
+++ b/spec/frontend/sidebar/components/reviewers/sidebar_reviewers_spec.js
@@ -44,9 +44,6 @@ describe('sidebar reviewers', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
-
SidebarService.singleton = null;
SidebarStore.singleton = null;
SidebarMediator.singleton = null;
diff --git a/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js b/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js
index d00c8dcb653..483449f924b 100644
--- a/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js
+++ b/spec/frontend/sidebar/components/reviewers/uncollapsed_reviewer_list_spec.js
@@ -38,10 +38,6 @@ describe('UncollapsedReviewerList component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('single reviewer', () => {
const user = userDataMock();
diff --git a/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js b/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js
index 71c6c259c32..8a27e82093d 100644
--- a/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js
+++ b/spec/frontend/sidebar/components/severity/sidebar_severity_spec.js
@@ -2,13 +2,14 @@ import { GlDropdown, GlDropdownItem, GlLoadingIcon, GlTooltip, GlSprintf } from
import { nextTick } from 'vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
-import { INCIDENT_SEVERITY, ISSUABLE_TYPES } from '~/sidebar/constants';
+import { createAlert } from '~/alert';
+import { TYPE_INCIDENT } from '~/issues/constants';
+import { INCIDENT_SEVERITY } from '~/sidebar/constants';
import updateIssuableSeverity from '~/sidebar/queries/update_issuable_severity.mutation.graphql';
import SeverityToken from '~/sidebar/components/severity/severity.vue';
import SidebarSeverityWidget from '~/sidebar/components/severity/sidebar_severity_widget.vue';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('SidebarSeverity', () => {
let wrapper;
@@ -22,7 +23,7 @@ describe('SidebarSeverity', () => {
const propsData = {
projectPath,
iid,
- issuableType: ISSUABLE_TYPES.INCIDENT,
+ issuableType: TYPE_INCIDENT,
initialSeverity: severity,
...props,
};
@@ -47,10 +48,6 @@ describe('SidebarSeverity', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findSeverityToken = () => wrapper.findAllComponents(SeverityToken);
const findEditBtn = () => wrapper.findByTestId('edit-button');
const findDropdown = () => wrapper.findComponent(GlDropdown);
diff --git a/spec/frontend/sidebar/components/sidebar_dropdown_spec.js b/spec/frontend/sidebar/components/sidebar_dropdown_spec.js
index 9f3d689edee..7a0044c00ac 100644
--- a/spec/frontend/sidebar/components/sidebar_dropdown_spec.js
+++ b/spec/frontend/sidebar/components/sidebar_dropdown_spec.js
@@ -10,7 +10,7 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { TYPE_ISSUE } from '~/issues/constants';
import SidebarDropdown from '~/sidebar/components/sidebar_dropdown.vue';
import { IssuableAttributeType } from '~/sidebar/constants';
@@ -23,7 +23,7 @@ import {
noCurrentMilestoneResponse,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('SidebarDropdown component', () => {
let wrapper;
diff --git a/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js b/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js
index 060a2873e04..53d81e3fcaf 100644
--- a/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js
+++ b/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js
@@ -7,7 +7,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { TYPE_ISSUE } from '~/issues/constants';
import { timeFor } from '~/lib/utils/datetime_utility';
@@ -27,7 +27,7 @@ import {
mockMilestone2,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('SidebarDropdownWidget', () => {
let wrapper;
@@ -140,7 +140,7 @@ describe('SidebarDropdownWidget', () => {
},
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
stubs: {
SidebarEditableItem,
@@ -157,11 +157,6 @@ describe('SidebarDropdownWidget', () => {
jest.spyOn(wrapper.vm, 'showDropdown').mockImplementation();
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when not editing', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/sidebar/components/status/status_dropdown_spec.js b/spec/frontend/sidebar/components/status/status_dropdown_spec.js
index 5a75299c3a4..229b51ea568 100644
--- a/spec/frontend/sidebar/components/status/status_dropdown_spec.js
+++ b/spec/frontend/sidebar/components/status/status_dropdown_spec.js
@@ -14,10 +14,6 @@ describe('SubscriptionsDropdown component', () => {
wrapper = shallowMount(StatusDropdown);
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with no value selected', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js b/spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js
index c94f9918243..7275557e7f2 100644
--- a/spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js
+++ b/spec/frontend/sidebar/components/subscriptions/sidebar_subscriptions_widget_spec.js
@@ -4,7 +4,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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
import SidebarSubscriptionWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue';
import issueSubscribedQuery from '~/sidebar/queries/issue_subscribed.query.graphql';
@@ -15,7 +15,7 @@ import {
mergeRequestSubscriptionMutationResponse,
} from '../../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/vue_shared/plugins/global_toast');
Vue.use(VueApollo);
@@ -62,7 +62,6 @@ describe('Sidebar Subscriptions Widget', () => {
};
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
@@ -138,7 +137,7 @@ describe('Sidebar Subscriptions Widget', () => {
});
});
- it('displays a flash message when query is rejected', async () => {
+ it('displays an alert message when query is rejected', async () => {
createComponent({
subscriptionsQueryHandler: jest.fn().mockRejectedValue('Houston, we have a problem'),
});
diff --git a/spec/frontend/sidebar/components/subscriptions/subscriptions_dropdown_spec.js b/spec/frontend/sidebar/components/subscriptions/subscriptions_dropdown_spec.js
index 3fb8214606c..eaf7bc13d20 100644
--- a/spec/frontend/sidebar/components/subscriptions/subscriptions_dropdown_spec.js
+++ b/spec/frontend/sidebar/components/subscriptions/subscriptions_dropdown_spec.js
@@ -15,10 +15,6 @@ describe('SubscriptionsDropdown component', () => {
wrapper = shallowMount(SubscriptionsDropdown);
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with no value selected', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/sidebar/components/subscriptions/subscriptions_spec.js b/spec/frontend/sidebar/components/subscriptions/subscriptions_spec.js
index 1a1aa370eef..cae21189ee0 100644
--- a/spec/frontend/sidebar/components/subscriptions/subscriptions_spec.js
+++ b/spec/frontend/sidebar/components/subscriptions/subscriptions_spec.js
@@ -16,11 +16,6 @@ describe('Subscriptions', () => {
}),
);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('shows loading spinner when loading', () => {
wrapper = mountComponent({
loading: true,
diff --git a/spec/frontend/sidebar/components/time_tracking/create_timelog_form_spec.js b/spec/frontend/sidebar/components/time_tracking/create_timelog_form_spec.js
index 715f66d305a..a7c3867c359 100644
--- a/spec/frontend/sidebar/components/time_tracking/create_timelog_form_spec.js
+++ b/spec/frontend/sidebar/components/time_tracking/create_timelog_form_spec.js
@@ -47,8 +47,8 @@ describe('Create Timelog Form', () => {
const findAlert = () => wrapper.findComponent(GlAlert);
const findDocsLink = () => wrapper.findByTestId('timetracking-docs-link');
const findSaveButton = () => findModal().props('actionPrimary');
- const findSaveButtonLoadingState = () => findSaveButton().attributes[0].loading;
- const findSaveButtonDisabledState = () => findSaveButton().attributes[0].disabled;
+ const findSaveButtonLoadingState = () => findSaveButton().attributes.loading;
+ const findSaveButtonDisabledState = () => findSaveButton().attributes.disabled;
const submitForm = () => findForm().trigger('submit');
diff --git a/spec/frontend/sidebar/components/time_tracking/report_spec.js b/spec/frontend/sidebar/components/time_tracking/report_spec.js
index 0259aee48f0..713ae83cbf1 100644
--- a/spec/frontend/sidebar/components/time_tracking/report_spec.js
+++ b/spec/frontend/sidebar/components/time_tracking/report_spec.js
@@ -6,7 +6,7 @@ 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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import Report from '~/sidebar/components/time_tracking/report.vue';
import getIssueTimelogsQuery from '~/sidebar/queries/get_issue_timelogs.query.graphql';
import getMrTimelogsQuery from '~/sidebar/queries/get_mr_timelogs.query.graphql';
@@ -17,7 +17,7 @@ import {
timelogToRemoveId,
} from './mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Issuable Time Tracking Report', () => {
Vue.use(VueApollo);
@@ -51,7 +51,6 @@ describe('Issuable Time Tracking Report', () => {
};
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
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 45d8b5e4647..91c013596d7 100644
--- a/spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js
+++ b/spec/frontend/sidebar/components/time_tracking/time_tracker_spec.js
@@ -32,7 +32,7 @@ describe('Issuable Time Tracker', () => {
const mountComponent = ({ props = {}, issuableType = 'issue', loading = false } = {}) => {
return mount(TimeTracker, {
propsData: { ...defaultProps, ...props },
- directives: { GlTooltip: createMockDirective() },
+ directives: { GlTooltip: createMockDirective('gl-tooltip') },
stubs: {
transition: stubTransition(),
},
@@ -53,10 +53,6 @@ describe('Issuable Time Tracker', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Initialization', () => {
beforeEach(() => {
wrapper = mountComponent();
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 5bfe3b59eb3..39b480b295c 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
@@ -4,13 +4,13 @@ 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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import SidebarTodoWidget from '~/sidebar/components/todo_toggle/sidebar_todo_widget.vue';
import epicTodoQuery from '~/sidebar/queries/epic_todo.query.graphql';
import TodoButton from '~/sidebar/components/todo_toggle/todo_button.vue';
import { todosResponse, noTodosResponse } from '../../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(VueApollo);
@@ -41,7 +41,6 @@ describe('Sidebar Todo Widget', () => {
};
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
@@ -77,7 +76,7 @@ describe('Sidebar Todo Widget', () => {
});
});
- it('displays a flash message when query is rejected', async () => {
+ it('displays an alert message when query is rejected', async () => {
createComponent({
todosQueryHandler: jest.fn().mockRejectedValue('Houston, we have a problem'),
});
diff --git a/spec/frontend/sidebar/components/todo_toggle/todo_button_spec.js b/spec/frontend/sidebar/components/todo_toggle/todo_button_spec.js
index fb07029a249..472a89e9b21 100644
--- a/spec/frontend/sidebar/components/todo_toggle/todo_button_spec.js
+++ b/spec/frontend/sidebar/components/todo_toggle/todo_button_spec.js
@@ -22,7 +22,6 @@ describe('Todo Button', () => {
});
afterEach(() => {
- wrapper.destroy();
dispatchEventSpy = null;
jest.clearAllMocks();
});
diff --git a/spec/frontend/sidebar/components/todo_toggle/todo_spec.js b/spec/frontend/sidebar/components/todo_toggle/todo_spec.js
index 8e6597bf80f..4da915f0dd3 100644
--- a/spec/frontend/sidebar/components/todo_toggle/todo_spec.js
+++ b/spec/frontend/sidebar/components/todo_toggle/todo_spec.js
@@ -21,10 +21,6 @@ describe('SidebarTodo', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
state | classes
${false} | ${['gl-button', 'btn', 'btn-default', 'btn-todo', 'issuable-header-btn', 'float-right']}
diff --git a/spec/frontend/sidebar/components/toggle/toggle_sidebar_spec.js b/spec/frontend/sidebar/components/toggle/toggle_sidebar_spec.js
index cf9b2828dde..0370d5e337d 100644
--- a/spec/frontend/sidebar/components/toggle/toggle_sidebar_spec.js
+++ b/spec/frontend/sidebar/components/toggle/toggle_sidebar_spec.js
@@ -17,10 +17,6 @@ describe('ToggleSidebar', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findGlButton = () => wrapper.findComponent(GlButton);
it('should render the "chevron-double-lg-left" icon when collapsed', () => {
diff --git a/spec/frontend/sidebar/mock_data.js b/spec/frontend/sidebar/mock_data.js
index 391cbb1e0d5..844320efc1c 100644
--- a/spec/frontend/sidebar/mock_data.js
+++ b/spec/frontend/sidebar/mock_data.js
@@ -243,6 +243,7 @@ export const issuableDueDateResponse = (dueDate = null) => ({
__typename: 'Issue',
id: 'gid://gitlab/Issue/4',
dueDate,
+ dueDateFixed: dueDate,
},
},
},
diff --git a/spec/frontend/sidebar/sidebar_mediator_spec.js b/spec/frontend/sidebar/sidebar_mediator_spec.js
index 77b1ccb4f9a..f2003aee96e 100644
--- a/spec/frontend/sidebar/sidebar_mediator_spec.js
+++ b/spec/frontend/sidebar/sidebar_mediator_spec.js
@@ -7,7 +7,7 @@ import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarStore from '~/sidebar/stores/sidebar_store';
import Mock from './mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/vue_shared/plugins/global_toast');
jest.mock('~/commons/nav/user_merge_requests');
diff --git a/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap b/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap
index fec300ddd7e..7eb0468c5be 100644
--- a/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap
+++ b/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap
@@ -28,10 +28,13 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] =
data-uploads-path=""
>
<markdown-header-stub
+ data-testid="markdownHeader"
enablepreview="true"
linecontent=""
+ markdownpreviewpath="foo/"
restrictedtoolbaritems=""
suggestionstartindex="0"
+ uploadspath=""
/>
<div
diff --git a/spec/frontend/snippets/components/edit_spec.js b/spec/frontend/snippets/components/edit_spec.js
index e7dab0ad79d..0d0e78e9179 100644
--- a/spec/frontend/snippets/components/edit_spec.js
+++ b/spec/frontend/snippets/components/edit_spec.js
@@ -9,7 +9,7 @@ import { stubPerformanceWebAPI } from 'helpers/performance';
import waitForPromises from 'helpers/wait_for_promises';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import GetSnippetQuery from 'shared_queries/snippet/snippet.query.graphql';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import * as urlUtils from '~/lib/utils/url_utility';
import SnippetEditApp from '~/snippets/components/edit.vue';
import SnippetBlobActionsEdit from '~/snippets/components/snippet_blob_actions_edit.vue';
@@ -25,7 +25,7 @@ import UpdateSnippetMutation from '~/snippets/mutations/update_snippet.mutation.
import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue';
import { testEntries, createGQLSnippetsQueryResponse, createGQLSnippet } from '../test_utils';
-jest.mock('~/flash');
+jest.mock('~/alert');
const TEST_UPLOADED_FILES = ['foo/bar.txt', 'alpha/beta.js'];
const TEST_API_ERROR = new Error('TEST_API_ERROR');
@@ -94,7 +94,6 @@ describe('Snippet Edit app', () => {
let mutateSpy;
const relativeUrlRoot = '/foo/';
- const originalRelativeUrlRoot = gon.relative_url_root;
beforeEach(() => {
stubPerformanceWebAPI();
@@ -108,12 +107,6 @@ describe('Snippet Edit app', () => {
jest.spyOn(urlUtils, 'redirectTo').mockImplementation();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- gon.relative_url_root = originalRelativeUrlRoot;
- });
-
const findBlobActions = () => wrapper.findComponent(SnippetBlobActionsEdit);
const findCancelButton = () => wrapper.findByTestId('snippet-cancel-btn');
const clickSubmitBtn = () => wrapper.findByTestId('snippet-edit-form').trigger('submit');
@@ -132,10 +125,6 @@ describe('Snippet Edit app', () => {
props = {},
selectedLevel = VISIBILITY_LEVEL_PRIVATE_STRING,
} = {}) => {
- if (wrapper) {
- throw new Error('wrapper already created');
- }
-
const requestHandlers = [
[GetSnippetQuery, getSpy],
// See `mutateSpy` declaration comment for why we send a key
@@ -347,7 +336,7 @@ describe('Snippet Edit app', () => {
projectPath
${'project/path'}
${''}
- `('should flash error (projectPath=$projectPath)', async ({ projectPath }) => {
+ `('should alert error (projectPath=$projectPath)', async ({ projectPath }) => {
mutateSpy.mockResolvedValue(createMutationResponseWithErrors('createSnippet'));
await createComponentAndLoad({
@@ -373,7 +362,7 @@ describe('Snippet Edit app', () => {
${'project/path'}
${''}
`(
- 'should flash error with (snippet=$snippetGid, projectPath=$projectPath)',
+ 'should alert error with (snippet=$snippetGid, projectPath=$projectPath)',
async ({ projectPath }) => {
mutateSpy.mockResolvedValue(createMutationResponseWithErrors('updateSnippet'));
@@ -405,7 +394,7 @@ describe('Snippet Edit app', () => {
expect(urlUtils.redirectTo).not.toHaveBeenCalled();
});
- it('should flash', () => {
+ it('should alert', () => {
// Apollo automatically wraps the resolver's error in a NetworkError
expect(createAlert).toHaveBeenCalledWith({
message: `Can't update snippet: ${TEST_API_ERROR.message}`,
diff --git a/spec/frontend/snippets/components/embed_dropdown_spec.js b/spec/frontend/snippets/components/embed_dropdown_spec.js
index ed5ea6cab8a..d8c6ad3278a 100644
--- a/spec/frontend/snippets/components/embed_dropdown_spec.js
+++ b/spec/frontend/snippets/components/embed_dropdown_spec.js
@@ -17,11 +17,6 @@ describe('snippets/components/embed_dropdown', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findSectionsData = () => {
const sections = [];
let current = {};
diff --git a/spec/frontend/snippets/components/show_spec.js b/spec/frontend/snippets/components/show_spec.js
index 032dcf8e5f5..45a7c7b0b4a 100644
--- a/spec/frontend/snippets/components/show_spec.js
+++ b/spec/frontend/snippets/components/show_spec.js
@@ -50,10 +50,6 @@ describe('Snippet view app', () => {
stubPerformanceWebAPI();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders loader while the query is in flight', () => {
createComponent({ loading: true });
expect(findLoadingIcon().exists()).toBe(true);
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 a650353093d..58f47e8b0dc 100644
--- a/spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js
+++ b/spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js
@@ -56,11 +56,6 @@ describe('snippets/components/snippet_blob_actions_edit', () => {
const triggerBlobDelete = (idx) => findBlobEdits().at(idx).vm.$emit('delete');
const triggerBlobUpdate = (idx, props) => findBlobEdits().at(idx).vm.$emit('blob-updated', props);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('multi-file snippets rendering', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/snippets/components/snippet_blob_edit_spec.js b/spec/frontend/snippets/components/snippet_blob_edit_spec.js
index 82c4a37ccc9..b699e056576 100644
--- a/spec/frontend/snippets/components/snippet_blob_edit_spec.js
+++ b/spec/frontend/snippets/components/snippet_blob_edit_spec.js
@@ -4,14 +4,14 @@ import AxiosMockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
import BlobHeaderEdit from '~/blob/components/blob_edit_header.vue';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { joinPaths } from '~/lib/utils/url_utility';
import SnippetBlobEdit from '~/snippets/components/snippet_blob_edit.vue';
import SourceEditor from '~/vue_shared/components/source_editor.vue';
-jest.mock('~/flash');
+jest.mock('~/alert');
const TEST_ID = 'blob_local_7';
const TEST_PATH = 'foo/bar/test.md';
@@ -62,8 +62,6 @@ describe('Snippet Blob Edit component', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
axiosMock.restore();
});
@@ -123,7 +121,7 @@ describe('Snippet Blob Edit component', () => {
createComponent();
});
- it('should call flash', async () => {
+ it('should call alert', async () => {
await waitForPromises();
expect(createAlert).toHaveBeenCalledWith({
diff --git a/spec/frontend/snippets/components/snippet_blob_view_spec.js b/spec/frontend/snippets/components/snippet_blob_view_spec.js
index c7ff8c21d80..840bca8c9c8 100644
--- a/spec/frontend/snippets/components/snippet_blob_view_spec.js
+++ b/spec/frontend/snippets/components/snippet_blob_view_spec.js
@@ -62,10 +62,6 @@ describe('Blob Embeddable', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('rendering', () => {
it('renders correct components', () => {
createComponent();
diff --git a/spec/frontend/snippets/components/snippet_description_edit_spec.js b/spec/frontend/snippets/components/snippet_description_edit_spec.js
index ff75515e71a..2b42eba19c2 100644
--- a/spec/frontend/snippets/components/snippet_description_edit_spec.js
+++ b/spec/frontend/snippets/components/snippet_description_edit_spec.js
@@ -30,10 +30,6 @@ describe('Snippet Description Edit component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('rendering', () => {
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
diff --git a/spec/frontend/snippets/components/snippet_description_view_spec.js b/spec/frontend/snippets/components/snippet_description_view_spec.js
index 14f116f2aaf..3c5d50ccaa6 100644
--- a/spec/frontend/snippets/components/snippet_description_view_spec.js
+++ b/spec/frontend/snippets/components/snippet_description_view_spec.js
@@ -17,10 +17,6 @@ describe('Snippet Description component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
diff --git a/spec/frontend/snippets/components/snippet_header_spec.js b/spec/frontend/snippets/components/snippet_header_spec.js
index c930c9f635b..994cf65c1f5 100644
--- a/spec/frontend/snippets/components/snippet_header_spec.js
+++ b/spec/frontend/snippets/components/snippet_header_spec.js
@@ -1,8 +1,9 @@
-import { GlButton, GlModal, GlDropdown } from '@gitlab/ui';
+import { GlModal, GlButton, GlDropdown } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import { ApolloMutation } from 'vue-apollo';
+import VueApollo from 'vue-apollo';
import MockAdapter from 'axios-mock-adapter';
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
+import createMockApollo from 'helpers/mock_apollo_helper';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
@@ -10,31 +11,41 @@ import { differenceInMilliseconds } from '~/lib/utils/datetime_utility';
import SnippetHeader, { i18n } from '~/snippets/components/snippet_header.vue';
import DeleteSnippetMutation from '~/snippets/mutations/delete_snippet.mutation.graphql';
import axios from '~/lib/utils/axios_utils';
-import { createAlert, VARIANT_DANGER, VARIANT_SUCCESS } from '~/flash';
+import { createAlert, VARIANT_DANGER, VARIANT_SUCCESS } from '~/alert';
+import CanCreateProjectSnippet from 'shared_queries/snippet/project_permissions.query.graphql';
+import CanCreatePersonalSnippet from 'shared_queries/snippet/user_permissions.query.graphql';
+import { getCanCreateProjectSnippetMock, getCanCreatePersonalSnippetMock } from '../mock_data';
-jest.mock('~/flash');
+const ERROR_MSG = 'Foo bar';
+const ERR = { message: ERROR_MSG };
+
+const MUTATION_TYPES = {
+ RESOLVE: jest.fn().mockResolvedValue({ data: { destroySnippet: { errors: [] } } }),
+ REJECT: jest.fn().mockRejectedValue(ERR),
+};
+
+jest.mock('~/alert');
+
+Vue.use(VueApollo);
describe('Snippet header component', () => {
let wrapper;
let snippet;
- let mutationTypes;
- let mutationVariables;
let mock;
+ let mockApollo;
- let errorMsg;
- let err;
- const originalRelativeUrlRoot = gon.relative_url_root;
const reportAbusePath = '/-/snippets/42/mark_as_spam';
const canReportSpam = true;
const GlEmoji = { template: '<img/>' };
function createComponent({
- loading = false,
permissions = {},
- mutationRes = mutationTypes.RESOLVE,
snippetProps = {},
provide = {},
+ canCreateProjectSnippetMock = jest.fn().mockResolvedValue(getCanCreateProjectSnippetMock()),
+ canCreatePersonalSnippetMock = jest.fn().mockResolvedValue(getCanCreatePersonalSnippetMock()),
+ deleteSnippetMock = MUTATION_TYPES.RESOLVE,
} = {}) {
const defaultProps = Object.assign(snippet, snippetProps);
if (permissions) {
@@ -42,17 +53,14 @@ describe('Snippet header component', () => {
...permissions,
});
}
- const $apollo = {
- queries: {
- canCreateSnippet: {
- loading,
- },
- },
- mutate: mutationRes,
- };
+
+ mockApollo = createMockApollo([
+ [CanCreateProjectSnippet, canCreateProjectSnippetMock],
+ [CanCreatePersonalSnippet, canCreatePersonalSnippetMock],
+ [DeleteSnippetMutation, deleteSnippetMock],
+ ]);
wrapper = mount(SnippetHeader, {
- mocks: { $apollo },
provide: {
reportAbusePath,
canReportSpam,
@@ -64,9 +72,9 @@ describe('Snippet header component', () => {
},
},
stubs: {
- ApolloMutation,
GlEmoji,
},
+ apolloProvider: mockApollo,
});
}
@@ -91,6 +99,7 @@ describe('Snippet header component', () => {
title: x.attributes('title'),
text: x.text(),
}));
+ const findDeleteModal = () => wrapper.findComponent(GlModal);
beforeEach(() => {
gon.relative_url_root = '/foo/';
@@ -113,28 +122,12 @@ describe('Snippet header component', () => {
createdAt: new Date(differenceInMilliseconds(32 * 24 * 3600 * 1000)).toISOString(),
};
- mutationVariables = {
- mutation: DeleteSnippetMutation,
- variables: {
- id: snippet.id,
- },
- };
-
- errorMsg = 'Foo bar';
- err = { message: errorMsg };
-
- mutationTypes = {
- RESOLVE: jest.fn(() => Promise.resolve({ data: { destroySnippet: { errors: [] } } })),
- REJECT: jest.fn(() => Promise.reject(err)),
- };
-
mock = new MockAdapter(axios);
});
afterEach(() => {
- wrapper.destroy();
+ mockApollo = null;
mock.restore();
- gon.relative_url_root = originalRelativeUrlRoot;
});
it('renders itself', () => {
@@ -238,15 +231,16 @@ describe('Snippet header component', () => {
});
it('with canCreateSnippet permission, renders create button', async () => {
- createComponent();
+ createComponent({
+ canCreateProjectSnippetMock: jest
+ .fn()
+ .mockResolvedValue(getCanCreateProjectSnippetMock(true)),
+ canCreatePersonalSnippetMock: jest
+ .fn()
+ .mockResolvedValue(getCanCreatePersonalSnippetMock(true)),
+ });
- // TODO: we should avoid `wrapper.setData` since they
- // are component internals. Let's use the apollo mock helpers
- // in a follow-up.
- // 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 nextTick();
+ await waitForPromises();
expect(findButtonsAsModel()).toEqual(
expect.arrayContaining([
@@ -271,7 +265,7 @@ describe('Snippet header component', () => {
${200} | ${VARIANT_SUCCESS} | ${i18n.snippetSpamSuccess}
${500} | ${VARIANT_DANGER} | ${i18n.snippetSpamFailure}
`(
- 'renders a "$variant" flash message with "$text" message for a request with a "$request" response',
+ 'renders a "$variant" alert message with "$text" message for a request with a "$request" response',
async ({ request, variant, text }) => {
const submitAsSpamBtn = findButtons().at(2);
mock.onPost(reportAbusePath).reply(request);
@@ -329,21 +323,37 @@ describe('Snippet header component', () => {
});
describe('Delete mutation', () => {
- it('dispatches a mutation to delete the snippet with correct variables', () => {
+ const deleteSnippet = async () => {
+ // Click delete action
+ findButtons().at(1).trigger('click');
+ await nextTick();
+
+ expect(findDeleteModal().props().visible).toBe(true);
+
+ // Click delete button in delete modal
+ document.querySelector('[data-testid="delete-snippet"').click();
+ await waitForPromises();
+ };
+
+ it('dispatches a mutation to delete the snippet with correct variables', async () => {
createComponent();
- wrapper.vm.deleteSnippet();
- expect(mutationTypes.RESOLVE).toHaveBeenCalledWith(mutationVariables);
+
+ await deleteSnippet();
+
+ expect(MUTATION_TYPES.RESOLVE).toHaveBeenCalledWith({
+ id: snippet.id,
+ });
});
it('sets error message if mutation fails', async () => {
- createComponent({ mutationRes: mutationTypes.REJECT });
+ createComponent({ deleteSnippetMock: MUTATION_TYPES.REJECT });
expect(Boolean(wrapper.vm.errorMessage)).toBe(false);
- wrapper.vm.deleteSnippet();
-
- await waitForPromises();
+ await deleteSnippet();
- expect(wrapper.vm.errorMessage).toEqual(errorMsg);
+ expect(document.querySelector('[data-testid="delete-alert"').textContent.trim()).toBe(
+ ERROR_MSG,
+ );
});
describe('in case of successful mutation, closes modal and redirects to correct listing', () => {
@@ -353,15 +363,16 @@ describe('Snippet header component', () => {
createComponent({
snippetProps,
});
- wrapper.vm.closeDeleteModal = jest.fn();
- wrapper.vm.deleteSnippet();
- await nextTick();
+ await deleteSnippet();
};
it('redirects to dashboard/snippets for personal snippet', async () => {
await createDeleteSnippet();
- expect(wrapper.vm.closeDeleteModal).toHaveBeenCalled();
+
+ // Check that the modal is hidden after deleting the snippet
+ expect(findDeleteModal().props().visible).toBe(false);
+
expect(window.location.pathname).toBe(`${gon.relative_url_root}dashboard/snippets`);
});
@@ -372,7 +383,10 @@ describe('Snippet header component', () => {
fullPath,
},
});
- expect(wrapper.vm.closeDeleteModal).toHaveBeenCalled();
+
+ // Check that the modal is hidden after deleting the snippet
+ expect(findDeleteModal().props().visible).toBe(false);
+
expect(window.location.pathname).toBe(`${fullPath}/-/snippets`);
});
});
diff --git a/spec/frontend/snippets/components/snippet_title_spec.js b/spec/frontend/snippets/components/snippet_title_spec.js
index 7c40735d64e..0a3b57c9244 100644
--- a/spec/frontend/snippets/components/snippet_title_spec.js
+++ b/spec/frontend/snippets/components/snippet_title_spec.js
@@ -26,10 +26,6 @@ describe('Snippet header component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders itself', () => {
createComponent();
expect(wrapper.find('.snippet-header').exists()).toBe(true);
diff --git a/spec/frontend/snippets/components/snippet_visibility_edit_spec.js b/spec/frontend/snippets/components/snippet_visibility_edit_spec.js
index 29eb002ef4a..70eb719f706 100644
--- a/spec/frontend/snippets/components/snippet_visibility_edit_spec.js
+++ b/spec/frontend/snippets/components/snippet_visibility_edit_spec.js
@@ -51,10 +51,6 @@ describe('Snippet Visibility Edit component', () => {
};
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('rendering', () => {
it('matches the snapshot', () => {
createComponent();
diff --git a/spec/frontend/snippets/mock_data.js b/spec/frontend/snippets/mock_data.js
new file mode 100644
index 00000000000..7546fa575c6
--- /dev/null
+++ b/spec/frontend/snippets/mock_data.js
@@ -0,0 +1,19 @@
+export const getCanCreateProjectSnippetMock = (createSnippet = false) => ({
+ data: {
+ project: {
+ userPermissions: {
+ createSnippet,
+ },
+ },
+ },
+});
+
+export const getCanCreatePersonalSnippetMock = (createSnippet = false) => ({
+ data: {
+ currentUser: {
+ userPermissions: {
+ createSnippet,
+ },
+ },
+ },
+});
diff --git a/spec/frontend/streaming/chunk_writer_spec.js b/spec/frontend/streaming/chunk_writer_spec.js
new file mode 100644
index 00000000000..2aadb332838
--- /dev/null
+++ b/spec/frontend/streaming/chunk_writer_spec.js
@@ -0,0 +1,214 @@
+import { ChunkWriter } from '~/streaming/chunk_writer';
+import { RenderBalancer } from '~/streaming/render_balancer';
+
+jest.mock('~/streaming/render_balancer');
+
+describe('ChunkWriter', () => {
+ let accumulator = '';
+ let write;
+ let close;
+ let abort;
+ let config;
+ let render;
+
+ const createChunk = (text) => {
+ const encoder = new TextEncoder();
+ return encoder.encode(text);
+ };
+
+ const createHtmlStream = () => {
+ write = jest.fn((part) => {
+ accumulator += part;
+ });
+ close = jest.fn();
+ abort = jest.fn();
+ return {
+ write,
+ close,
+ abort,
+ };
+ };
+
+ const createWriter = () => {
+ return new ChunkWriter(createHtmlStream(), config);
+ };
+
+ const pushChunks = (...chunks) => {
+ const writer = createWriter();
+ chunks.forEach((chunk) => {
+ writer.write(createChunk(chunk));
+ });
+ writer.close();
+ };
+
+ afterAll(() => {
+ global.JEST_DEBOUNCE_THROTTLE_TIMEOUT = undefined;
+ });
+
+ beforeEach(() => {
+ global.JEST_DEBOUNCE_THROTTLE_TIMEOUT = 100;
+ accumulator = '';
+ config = undefined;
+ render = jest.fn((cb) => {
+ while (cb()) {
+ // render until 'false'
+ }
+ });
+ RenderBalancer.mockImplementation(() => ({ render }));
+ });
+
+ describe('when chunk length must be "1"', () => {
+ beforeEach(() => {
+ config = { minChunkSize: 1, maxChunkSize: 1 };
+ });
+
+ it('splits big chunks into smaller ones', () => {
+ const text = 'foobar';
+ pushChunks(text);
+ expect(accumulator).toBe(text);
+ expect(write).toHaveBeenCalledTimes(text.length);
+ });
+
+ it('handles small emoji chunks', () => {
+ const text = 'foo👀bar👨‍👩‍👧baz👧👧🏻👧🏼👧🏽👧🏾👧🏿';
+ pushChunks(text);
+ expect(accumulator).toBe(text);
+ expect(write).toHaveBeenCalledTimes(createChunk(text).length);
+ });
+ });
+
+ describe('when chunk length must not be lower than "5" and exceed "10"', () => {
+ beforeEach(() => {
+ config = { minChunkSize: 5, maxChunkSize: 10 };
+ });
+
+ it('joins small chunks', () => {
+ const text = '12345';
+ pushChunks(...text.split(''));
+ expect(accumulator).toBe(text);
+ expect(write).toHaveBeenCalledTimes(1);
+ expect(close).toHaveBeenCalledTimes(1);
+ });
+
+ it('handles overflow with small chunks', () => {
+ const text = '123456789';
+ pushChunks(...text.split(''));
+ expect(accumulator).toBe(text);
+ expect(write).toHaveBeenCalledTimes(2);
+ expect(close).toHaveBeenCalledTimes(1);
+ });
+
+ it('calls flush on small chunks', () => {
+ global.JEST_DEBOUNCE_THROTTLE_TIMEOUT = undefined;
+ const flushAccumulator = jest.spyOn(ChunkWriter.prototype, 'flushAccumulator');
+ const text = '1';
+ pushChunks(text);
+ expect(accumulator).toBe(text);
+ expect(flushAccumulator).toHaveBeenCalledTimes(1);
+ });
+
+ it('calls flush on large chunks', () => {
+ const flushAccumulator = jest.spyOn(ChunkWriter.prototype, 'flushAccumulator');
+ const text = '1234567890123';
+ const writer = createWriter();
+ writer.write(createChunk(text));
+ jest.runAllTimers();
+ expect(accumulator).toBe(text);
+ expect(flushAccumulator).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('chunk balancing', () => {
+ let increase;
+ let decrease;
+ let renderOnce;
+
+ beforeEach(() => {
+ render = jest.fn((cb) => {
+ let next = true;
+ renderOnce = () => {
+ if (!next) return;
+ next = cb();
+ };
+ });
+ RenderBalancer.mockImplementation(({ increase: inc, decrease: dec }) => {
+ increase = jest.fn(inc);
+ decrease = jest.fn(dec);
+ return {
+ render,
+ };
+ });
+ });
+
+ describe('when frame time exceeds low limit', () => {
+ beforeEach(() => {
+ config = {
+ minChunkSize: 1,
+ maxChunkSize: 5,
+ balanceRate: 10,
+ };
+ });
+
+ it('increases chunk size', () => {
+ const text = '111222223';
+ const writer = createWriter();
+ const chunk = createChunk(text);
+
+ writer.write(chunk);
+
+ renderOnce();
+ increase();
+ renderOnce();
+ renderOnce();
+
+ writer.close();
+
+ expect(accumulator).toBe(text);
+ expect(write.mock.calls).toMatchObject([['111'], ['22222'], ['3']]);
+ expect(close).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('when frame time exceeds high limit', () => {
+ beforeEach(() => {
+ config = {
+ minChunkSize: 1,
+ maxChunkSize: 10,
+ balanceRate: 2,
+ };
+ });
+
+ it('decreases chunk size', () => {
+ const text = '1111112223345';
+ const writer = createWriter();
+ const chunk = createChunk(text);
+
+ writer.write(chunk);
+
+ renderOnce();
+ decrease();
+
+ renderOnce();
+ decrease();
+
+ renderOnce();
+ decrease();
+
+ renderOnce();
+ renderOnce();
+
+ writer.close();
+
+ expect(accumulator).toBe(text);
+ expect(write.mock.calls).toMatchObject([['111111'], ['222'], ['33'], ['4'], ['5']]);
+ expect(close).toHaveBeenCalledTimes(1);
+ });
+ });
+ });
+
+ it('calls abort on htmlStream', () => {
+ const writer = createWriter();
+ writer.abort();
+ expect(abort).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/spec/frontend/streaming/handle_streamed_anchor_link_spec.js b/spec/frontend/streaming/handle_streamed_anchor_link_spec.js
new file mode 100644
index 00000000000..ef17957b2fc
--- /dev/null
+++ b/spec/frontend/streaming/handle_streamed_anchor_link_spec.js
@@ -0,0 +1,132 @@
+import { resetHTMLFixture, setHTMLFixture } from 'helpers/fixtures';
+import waitForPromises from 'helpers/wait_for_promises';
+import { handleStreamedAnchorLink } from '~/streaming/handle_streamed_anchor_link';
+import { scrollToElement } from '~/lib/utils/common_utils';
+import LineHighlighter from '~/blob/line_highlighter';
+import { TEST_HOST } from 'spec/test_constants';
+
+jest.mock('~/lib/utils/common_utils');
+jest.mock('~/blob/line_highlighter');
+
+describe('handleStreamedAnchorLink', () => {
+ const ANCHOR_START = 'L100';
+ const ANCHOR_END = '300';
+ const findRoot = () => document.querySelector('#root');
+
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
+ describe('when single line anchor is given', () => {
+ beforeEach(() => {
+ delete window.location;
+ window.location = new URL(`${TEST_HOST}#${ANCHOR_START}`);
+ });
+
+ describe('when element is present', () => {
+ beforeEach(() => {
+ setHTMLFixture(`<div id="root"><div id="${ANCHOR_START}"></div></div>`);
+ handleStreamedAnchorLink(findRoot());
+ });
+
+ it('does nothing', async () => {
+ await waitForPromises();
+ expect(scrollToElement).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when element is streamed', () => {
+ let stop;
+ const insertElement = () => {
+ findRoot().insertAdjacentHTML('afterbegin', `<div id="${ANCHOR_START}"></div>`);
+ };
+
+ beforeEach(() => {
+ setHTMLFixture('<div id="root"></div>');
+ stop = handleStreamedAnchorLink(findRoot());
+ });
+
+ afterEach(() => {
+ stop = undefined;
+ });
+
+ it('scrolls to the anchor when inserted', async () => {
+ insertElement();
+ await waitForPromises();
+ expect(scrollToElement).toHaveBeenCalledTimes(1);
+ expect(LineHighlighter).toHaveBeenCalledTimes(1);
+ });
+
+ it("doesn't scroll to the anchor when destroyed", async () => {
+ stop();
+ insertElement();
+ await waitForPromises();
+ expect(scrollToElement).not.toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('when line range anchor is given', () => {
+ beforeEach(() => {
+ delete window.location;
+ window.location = new URL(`${TEST_HOST}#${ANCHOR_START}-${ANCHOR_END}`);
+ });
+
+ describe('when last element is present', () => {
+ beforeEach(() => {
+ setHTMLFixture(`<div id="root"><div id="L${ANCHOR_END}"></div></div>`);
+ handleStreamedAnchorLink(findRoot());
+ });
+
+ it('does nothing', async () => {
+ await waitForPromises();
+ expect(scrollToElement).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when last element is streamed', () => {
+ let stop;
+ const insertElement = () => {
+ findRoot().insertAdjacentHTML(
+ 'afterbegin',
+ `<div id="${ANCHOR_START}"></div><div id="L${ANCHOR_END}"></div>`,
+ );
+ };
+
+ beforeEach(() => {
+ setHTMLFixture('<div id="root"></div>');
+ stop = handleStreamedAnchorLink(findRoot());
+ });
+
+ afterEach(() => {
+ stop = undefined;
+ });
+
+ it('scrolls to the anchor when inserted', async () => {
+ insertElement();
+ await waitForPromises();
+ expect(scrollToElement).toHaveBeenCalledTimes(1);
+ expect(LineHighlighter).toHaveBeenCalledTimes(1);
+ });
+
+ it("doesn't scroll to the anchor when destroyed", async () => {
+ stop();
+ insertElement();
+ await waitForPromises();
+ expect(scrollToElement).not.toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('when anchor is not given', () => {
+ beforeEach(() => {
+ setHTMLFixture(`<div id="root"></div>`);
+ handleStreamedAnchorLink(findRoot());
+ });
+
+ it('does nothing', async () => {
+ await waitForPromises();
+ expect(scrollToElement).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/streaming/html_stream_spec.js b/spec/frontend/streaming/html_stream_spec.js
new file mode 100644
index 00000000000..115a9ddc803
--- /dev/null
+++ b/spec/frontend/streaming/html_stream_spec.js
@@ -0,0 +1,46 @@
+import { HtmlStream } from '~/streaming/html_stream';
+import { ChunkWriter } from '~/streaming/chunk_writer';
+
+jest.mock('~/streaming/chunk_writer');
+
+describe('HtmlStream', () => {
+ let write;
+ let close;
+ let streamingElement;
+
+ beforeEach(() => {
+ write = jest.fn();
+ close = jest.fn();
+ jest.spyOn(Document.prototype, 'write').mockImplementation(write);
+ jest.spyOn(Document.prototype, 'close').mockImplementation(close);
+ jest.spyOn(Document.prototype, 'querySelector').mockImplementation(() => {
+ streamingElement = document.createElement('div');
+ return streamingElement;
+ });
+ });
+
+ it('attaches to original document', () => {
+ // eslint-disable-next-line no-new
+ new HtmlStream(document.body);
+ expect(document.body.contains(streamingElement)).toBe(true);
+ });
+
+ it('can write to a document', () => {
+ const htmlStream = new HtmlStream(document.body);
+ htmlStream.write('foo');
+ htmlStream.close();
+ expect(write.mock.calls).toEqual([['<streaming-element>'], ['foo'], ['</streaming-element>']]);
+ expect(close).toHaveBeenCalledTimes(1);
+ });
+
+ it('returns chunked writer', () => {
+ const htmlStream = new HtmlStream(document.body).withChunkWriter();
+ expect(htmlStream).toBeInstanceOf(ChunkWriter);
+ });
+
+ it('closes on abort', () => {
+ const htmlStream = new HtmlStream(document.body);
+ htmlStream.abort();
+ expect(close).toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/streaming/rate_limit_stream_requests_spec.js b/spec/frontend/streaming/rate_limit_stream_requests_spec.js
new file mode 100644
index 00000000000..02e3cf93014
--- /dev/null
+++ b/spec/frontend/streaming/rate_limit_stream_requests_spec.js
@@ -0,0 +1,155 @@
+import waitForPromises from 'helpers/wait_for_promises';
+import { rateLimitStreamRequests } from '~/streaming/rate_limit_stream_requests';
+
+describe('rateLimitStreamRequests', () => {
+ const encoder = new TextEncoder('utf-8');
+ const createStreamResponse = (content = 'foo') =>
+ new ReadableStream({
+ pull(controller) {
+ controller.enqueue(encoder.encode(content));
+ controller.close();
+ },
+ });
+
+ const createFactory = (content) => {
+ return jest.fn(() => {
+ return Promise.resolve(createStreamResponse(content));
+ });
+ };
+
+ it('does nothing for zero total requests', () => {
+ const factory = jest.fn();
+ const requests = rateLimitStreamRequests({
+ factory,
+ total: 0,
+ });
+ expect(factory).toHaveBeenCalledTimes(0);
+ expect(requests.length).toBe(0);
+ });
+
+ it('does not exceed total requests', () => {
+ const factory = createFactory();
+ const requests = rateLimitStreamRequests({
+ factory,
+ immediateCount: 100,
+ maxConcurrentRequests: 100,
+ total: 2,
+ });
+ expect(factory).toHaveBeenCalledTimes(2);
+ expect(requests.length).toBe(2);
+ });
+
+ it('creates immediate requests', () => {
+ const factory = createFactory();
+ const requests = rateLimitStreamRequests({
+ factory,
+ maxConcurrentRequests: 2,
+ total: 2,
+ });
+ expect(factory).toHaveBeenCalledTimes(2);
+ expect(requests.length).toBe(2);
+ });
+
+ it('returns correct values', async () => {
+ const fixture = 'foobar';
+ const factory = createFactory(fixture);
+ const requests = rateLimitStreamRequests({
+ factory,
+ maxConcurrentRequests: 2,
+ total: 2,
+ });
+
+ const decoder = new TextDecoder('utf-8');
+ let result = '';
+ for await (const stream of requests) {
+ await stream.pipeTo(
+ new WritableStream({
+ // eslint-disable-next-line no-loop-func
+ write(content) {
+ result += decoder.decode(content);
+ },
+ }),
+ );
+ }
+
+ expect(result).toBe(fixture + fixture);
+ });
+
+ it('delays rate limited requests', async () => {
+ const factory = createFactory();
+ const requests = rateLimitStreamRequests({
+ factory,
+ maxConcurrentRequests: 2,
+ total: 3,
+ });
+ expect(factory).toHaveBeenCalledTimes(2);
+ expect(requests.length).toBe(3);
+
+ await waitForPromises();
+
+ expect(factory).toHaveBeenCalledTimes(3);
+ });
+
+ it('runs next request after previous has been fulfilled', async () => {
+ let res;
+ const factory = jest
+ .fn()
+ .mockImplementationOnce(
+ () =>
+ new Promise((resolve) => {
+ res = resolve;
+ }),
+ )
+ .mockImplementationOnce(() => Promise.resolve(createStreamResponse()));
+ const requests = rateLimitStreamRequests({
+ factory,
+ maxConcurrentRequests: 1,
+ total: 2,
+ });
+ expect(factory).toHaveBeenCalledTimes(1);
+ expect(requests.length).toBe(2);
+
+ await waitForPromises();
+
+ expect(factory).toHaveBeenCalledTimes(1);
+
+ res(createStreamResponse());
+
+ await waitForPromises();
+
+ expect(factory).toHaveBeenCalledTimes(2);
+ });
+
+ it('uses timer to schedule next request', async () => {
+ let res;
+ const factory = jest
+ .fn()
+ .mockImplementationOnce(
+ () =>
+ new Promise((resolve) => {
+ res = resolve;
+ }),
+ )
+ .mockImplementationOnce(() => Promise.resolve(createStreamResponse()));
+ const requests = rateLimitStreamRequests({
+ factory,
+ immediateCount: 1,
+ maxConcurrentRequests: 2,
+ total: 2,
+ timeout: 9999,
+ });
+ expect(factory).toHaveBeenCalledTimes(1);
+ expect(requests.length).toBe(2);
+
+ await waitForPromises();
+
+ expect(factory).toHaveBeenCalledTimes(1);
+
+ jest.runAllTimers();
+
+ await waitForPromises();
+
+ expect(factory).toHaveBeenCalledTimes(2);
+ res(createStreamResponse());
+ });
+});
diff --git a/spec/frontend/streaming/render_balancer_spec.js b/spec/frontend/streaming/render_balancer_spec.js
new file mode 100644
index 00000000000..dae0c98d678
--- /dev/null
+++ b/spec/frontend/streaming/render_balancer_spec.js
@@ -0,0 +1,69 @@
+import { RenderBalancer } from '~/streaming/render_balancer';
+
+const HIGH_FRAME_TIME = 100;
+const LOW_FRAME_TIME = 10;
+
+describe('renderBalancer', () => {
+ let frameTime = 0;
+ let frameTimeDelta = 0;
+ let decrease;
+ let increase;
+
+ const createBalancer = () => {
+ decrease = jest.fn();
+ increase = jest.fn();
+ return new RenderBalancer({
+ highFrameTime: HIGH_FRAME_TIME,
+ lowFrameTime: LOW_FRAME_TIME,
+ increase,
+ decrease,
+ });
+ };
+
+ const renderTimes = (times) => {
+ const balancer = createBalancer();
+ return new Promise((resolve) => {
+ let counter = 0;
+ balancer.render(() => {
+ if (counter === times) {
+ resolve(counter);
+ return false;
+ }
+ counter += 1;
+ return true;
+ });
+ });
+ };
+
+ beforeEach(() => {
+ jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => {
+ frameTime += frameTimeDelta;
+ cb(frameTime);
+ });
+ });
+
+ afterEach(() => {
+ window.requestAnimationFrame.mockRestore();
+ frameTime = 0;
+ frameTimeDelta = 0;
+ });
+
+ it('renders in a loop', async () => {
+ const count = await renderTimes(5);
+ expect(count).toBe(5);
+ });
+
+ it('calls decrease', async () => {
+ frameTimeDelta = 200;
+ await renderTimes(5);
+ expect(decrease).toHaveBeenCalled();
+ expect(increase).not.toHaveBeenCalled();
+ });
+
+ it('calls increase', async () => {
+ frameTimeDelta = 1;
+ await renderTimes(5);
+ expect(increase).toHaveBeenCalled();
+ expect(decrease).not.toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/streaming/render_html_streams_spec.js b/spec/frontend/streaming/render_html_streams_spec.js
new file mode 100644
index 00000000000..55cef0ea469
--- /dev/null
+++ b/spec/frontend/streaming/render_html_streams_spec.js
@@ -0,0 +1,96 @@
+import { ReadableStream } from 'node:stream/web';
+import { renderHtmlStreams } from '~/streaming/render_html_streams';
+import { HtmlStream } from '~/streaming/html_stream';
+import waitForPromises from 'helpers/wait_for_promises';
+
+jest.mock('~/streaming/html_stream');
+jest.mock('~/streaming/constants', () => {
+ return {
+ HIGH_FRAME_TIME: 0,
+ LOW_FRAME_TIME: 0,
+ MAX_CHUNK_SIZE: 1,
+ MIN_CHUNK_SIZE: 1,
+ };
+});
+
+const firstStreamContent = 'foobar';
+const secondStreamContent = 'bazqux';
+
+describe('renderHtmlStreams', () => {
+ let htmlWriter;
+ const encoder = new TextEncoder();
+ const createSingleChunkStream = (chunk) => {
+ const encoded = encoder.encode(chunk);
+ const stream = new ReadableStream({
+ pull(controller) {
+ controller.enqueue(encoded);
+ controller.close();
+ },
+ });
+ return [stream, encoded];
+ };
+
+ beforeEach(() => {
+ htmlWriter = {
+ write: jest.fn(),
+ close: jest.fn(),
+ abort: jest.fn(),
+ };
+ jest.spyOn(HtmlStream.prototype, 'withChunkWriter').mockReturnValue(htmlWriter);
+ });
+
+ it('renders a single stream', async () => {
+ const [stream, encoded] = createSingleChunkStream(firstStreamContent);
+
+ await renderHtmlStreams([Promise.resolve(stream)], document.body);
+
+ expect(htmlWriter.write).toHaveBeenCalledWith(encoded);
+ expect(htmlWriter.close).toHaveBeenCalledTimes(1);
+ });
+
+ it('renders stream sequence', async () => {
+ const [stream1, encoded1] = createSingleChunkStream(firstStreamContent);
+ const [stream2, encoded2] = createSingleChunkStream(secondStreamContent);
+
+ await renderHtmlStreams([Promise.resolve(stream1), Promise.resolve(stream2)], document.body);
+
+ expect(htmlWriter.write.mock.calls).toMatchObject([[encoded1], [encoded2]]);
+ expect(htmlWriter.close).toHaveBeenCalledTimes(1);
+ });
+
+ it("doesn't wait for the whole sequence to resolve before streaming", async () => {
+ const [stream1, encoded1] = createSingleChunkStream(firstStreamContent);
+ const [stream2, encoded2] = createSingleChunkStream(secondStreamContent);
+
+ let res;
+ const delayedStream = new Promise((resolve) => {
+ res = resolve;
+ });
+
+ renderHtmlStreams([Promise.resolve(stream1), delayedStream], document.body);
+
+ await waitForPromises();
+
+ expect(htmlWriter.write.mock.calls).toMatchObject([[encoded1]]);
+ expect(htmlWriter.close).toHaveBeenCalledTimes(0);
+
+ res(stream2);
+ await waitForPromises();
+
+ expect(htmlWriter.write.mock.calls).toMatchObject([[encoded1], [encoded2]]);
+ expect(htmlWriter.close).toHaveBeenCalledTimes(1);
+ });
+
+ it('closes HtmlStream on error', async () => {
+ const [stream1] = createSingleChunkStream(firstStreamContent);
+ const error = new Error();
+
+ try {
+ await renderHtmlStreams([Promise.resolve(stream1), Promise.reject(error)], document.body);
+ } catch (err) {
+ expect(err).toBe(error);
+ }
+
+ expect(htmlWriter.abort).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/context_switcher_spec.js b/spec/frontend/super_sidebar/components/context_switcher_spec.js
new file mode 100644
index 00000000000..538e87cf843
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/context_switcher_spec.js
@@ -0,0 +1,219 @@
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlSearchBoxByType } from '@gitlab/ui';
+import * as Sentry from '@sentry/browser';
+import { s__ } from '~/locale';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import ContextSwitcher from '~/super_sidebar/components/context_switcher.vue';
+import ProjectsList from '~/super_sidebar/components/projects_list.vue';
+import GroupsList from '~/super_sidebar/components/groups_list.vue';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import searchUserProjectsAndGroupsQuery from '~/super_sidebar/graphql/queries/search_user_groups_and_projects.query.graphql';
+import { trackContextAccess, formatContextSwitcherItems } from '~/super_sidebar/utils';
+import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
+import waitForPromises from 'helpers/wait_for_promises';
+import { stubComponent } from 'helpers/stub_component';
+import { searchUserProjectsAndGroupsResponseMock } from '../mock_data';
+
+jest.mock('~/super_sidebar/utils', () => ({
+ getStorageKeyFor: jest.requireActual('~/super_sidebar/utils').getStorageKeyFor,
+ getTopFrequentItems: jest.requireActual('~/super_sidebar/utils').getTopFrequentItems,
+ formatContextSwitcherItems: jest.requireActual('~/super_sidebar/utils')
+ .formatContextSwitcherItems,
+ trackContextAccess: jest.fn(),
+}));
+
+const username = 'root';
+const projectsPath = 'projectsPath';
+const groupsPath = 'groupsPath';
+
+Vue.use(VueApollo);
+
+describe('ContextSwitcher component', () => {
+ let wrapper;
+ let mockApollo;
+
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+ const findProjectsList = () => wrapper.findComponent(ProjectsList);
+ const findGroupsList = () => wrapper.findComponent(GroupsList);
+
+ const triggerSearchQuery = async () => {
+ findSearchBox().vm.$emit('input', 'foo');
+ await nextTick();
+ jest.advanceTimersByTime(DEFAULT_DEBOUNCE_AND_THROTTLE_MS);
+ return waitForPromises();
+ };
+
+ const searchUserProjectsAndGroupsHandlerSuccess = jest
+ .fn()
+ .mockResolvedValue(searchUserProjectsAndGroupsResponseMock);
+
+ const createWrapper = ({ props = {}, requestHandlers = {} } = {}) => {
+ mockApollo = createMockApollo([
+ [
+ searchUserProjectsAndGroupsQuery,
+ requestHandlers.searchUserProjectsAndGroupsQueryHandler ??
+ searchUserProjectsAndGroupsHandlerSuccess,
+ ],
+ ]);
+
+ wrapper = shallowMountExtended(ContextSwitcher, {
+ apolloProvider: mockApollo,
+ propsData: {
+ username,
+ projectsPath,
+ groupsPath,
+ ...props,
+ },
+ stubs: {
+ GlSearchBoxByType: stubComponent(GlSearchBoxByType, {
+ props: ['placeholder'],
+ }),
+ ProjectsList: stubComponent(ProjectsList, {
+ props: ['username', 'viewAllLink', 'isSearch', 'searchResults'],
+ }),
+ GroupsList: stubComponent(GroupsList, {
+ props: ['username', 'viewAllLink', 'isSearch', 'searchResults'],
+ }),
+ },
+ });
+ };
+
+ describe('default', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('passes the placeholder to the search box', () => {
+ expect(findSearchBox().props('placeholder')).toBe(
+ s__('Navigation|Search for projects or groups'),
+ );
+ });
+
+ it('passes the correct props the frequent projects list', () => {
+ expect(findProjectsList().props()).toEqual({
+ username,
+ viewAllLink: projectsPath,
+ isSearch: false,
+ searchResults: [],
+ });
+ });
+
+ it('passes the correct props the frequent groups list', () => {
+ expect(findGroupsList().props()).toEqual({
+ username,
+ viewAllLink: groupsPath,
+ isSearch: false,
+ searchResults: [],
+ });
+ });
+
+ it('does not trigger the search query on mount', () => {
+ expect(searchUserProjectsAndGroupsHandlerSuccess).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('item access tracking', () => {
+ it('does not track anything if not within a trackable context', () => {
+ createWrapper();
+
+ expect(trackContextAccess).not.toHaveBeenCalled();
+ });
+
+ it('tracks item access if within a trackable context', () => {
+ const currentContext = { namespace: 'groups' };
+ createWrapper({
+ props: {
+ currentContext,
+ },
+ });
+
+ expect(trackContextAccess).toHaveBeenCalledWith(username, currentContext);
+ });
+ });
+
+ describe('on search', () => {
+ beforeEach(() => {
+ createWrapper();
+ return triggerSearchQuery();
+ });
+
+ it('triggers the search query on search', () => {
+ expect(searchUserProjectsAndGroupsHandlerSuccess).toHaveBeenCalled();
+ });
+
+ it('passes the projects to the frequent projects list', () => {
+ expect(findProjectsList().props('isSearch')).toBe(true);
+ expect(findProjectsList().props('searchResults')).toEqual(
+ formatContextSwitcherItems(searchUserProjectsAndGroupsResponseMock.data.projects.nodes),
+ );
+ });
+
+ it('passes the groups to the frequent groups list', () => {
+ expect(findGroupsList().props('isSearch')).toBe(true);
+ expect(findGroupsList().props('searchResults')).toEqual(
+ formatContextSwitcherItems(searchUserProjectsAndGroupsResponseMock.data.user.groups.nodes),
+ );
+ });
+ });
+
+ describe('when search query does not match any items', () => {
+ beforeEach(() => {
+ createWrapper({
+ requestHandlers: {
+ searchUserProjectsAndGroupsQueryHandler: jest.fn().mockResolvedValue({
+ data: {
+ projects: {
+ nodes: [],
+ },
+ user: {
+ id: '1',
+ groups: {
+ nodes: [],
+ },
+ },
+ },
+ }),
+ },
+ });
+ return triggerSearchQuery();
+ });
+
+ it('passes empty results to the lists', () => {
+ expect(findProjectsList().props('isSearch')).toBe(true);
+ expect(findProjectsList().props('searchResults')).toEqual([]);
+ expect(findGroupsList().props('isSearch')).toBe(true);
+ expect(findGroupsList().props('searchResults')).toEqual([]);
+ });
+ });
+
+ describe('when search query fails', () => {
+ beforeEach(() => {
+ jest.spyOn(Sentry, 'captureException');
+ });
+
+ it('captures exception if response is formatted incorrectly', async () => {
+ createWrapper({
+ requestHandlers: {
+ searchUserProjectsAndGroupsQueryHandler: jest.fn().mockResolvedValue({
+ data: {},
+ }),
+ },
+ });
+ await triggerSearchQuery();
+
+ expect(Sentry.captureException).toHaveBeenCalled();
+ });
+
+ it('captures exception if query fails', async () => {
+ createWrapper({
+ requestHandlers: {
+ searchUserProjectsAndGroupsQueryHandler: jest.fn().mockRejectedValue(),
+ },
+ });
+ await triggerSearchQuery();
+
+ expect(Sentry.captureException).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/context_switcher_toggle_spec.js b/spec/frontend/super_sidebar/components/context_switcher_toggle_spec.js
new file mode 100644
index 00000000000..7172b60d0fa
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/context_switcher_toggle_spec.js
@@ -0,0 +1,50 @@
+import { GlAvatar } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import ContextSwitcherToggle from '~/super_sidebar/components/context_switcher_toggle.vue';
+
+describe('ContextSwitcherToggle component', () => {
+ let wrapper;
+
+ const context = {
+ id: 1,
+ title: 'Title',
+ avatar: '/path/to/avatar.png',
+ };
+
+ const findGlAvatar = () => wrapper.getComponent(GlAvatar);
+
+ const createWrapper = (props = {}) => {
+ wrapper = shallowMountExtended(ContextSwitcherToggle, {
+ propsData: {
+ context,
+ expanded: false,
+ ...props,
+ },
+ });
+ };
+
+ describe('with an avatar', () => {
+ it('passes the correct props to GlAvatar', () => {
+ createWrapper();
+ const avatar = findGlAvatar();
+
+ expect(avatar.props('shape')).toBe('rect');
+ expect(avatar.props('entityName')).toBe(context.title);
+ expect(avatar.props('entityId')).toBe(context.id);
+ expect(avatar.props('src')).toBe(context.avatar);
+ });
+
+ it('renders the avatar with a custom shape', () => {
+ const customShape = 'circle';
+ createWrapper({
+ context: {
+ ...context,
+ avatar_shape: customShape,
+ },
+ });
+ const avatar = findGlAvatar();
+
+ expect(avatar.props('shape')).toBe(customShape);
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/frequent_items_list_spec.js b/spec/frontend/super_sidebar/components/frequent_items_list_spec.js
new file mode 100644
index 00000000000..1e98db091f2
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/frequent_items_list_spec.js
@@ -0,0 +1,68 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { s__ } from '~/locale';
+import FrequentItemsList from '~/super_sidebar/components//frequent_items_list.vue';
+import ItemsList from '~/super_sidebar/components/items_list.vue';
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import { cachedFrequentProjects } from '../mock_data';
+
+const title = s__('Navigation|FREQUENT PROJECTS');
+const pristineText = s__('Navigation|Projects you visit often will appear here.');
+const storageKey = 'storageKey';
+const maxItems = 5;
+
+describe('FrequentItemsList component', () => {
+ useLocalStorageSpy();
+
+ let wrapper;
+
+ const findListTitle = () => wrapper.findByTestId('list-title');
+ const findItemsList = () => wrapper.findComponent(ItemsList);
+ const findEmptyText = () => wrapper.findByTestId('empty-text');
+
+ const createWrapper = ({ props = {} } = {}) => {
+ wrapper = shallowMountExtended(FrequentItemsList, {
+ propsData: {
+ title,
+ pristineText,
+ storageKey,
+ maxItems,
+ ...props,
+ },
+ });
+ };
+
+ describe('default', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it("renders the list's title", () => {
+ expect(findListTitle().text()).toBe(title);
+ });
+
+ it('renders the empty text', () => {
+ expect(findEmptyText().exists()).toBe(true);
+ expect(findEmptyText().text()).toBe(pristineText);
+ });
+ });
+
+ describe('when there are cached frequent items', () => {
+ beforeEach(() => {
+ window.localStorage.setItem(storageKey, cachedFrequentProjects);
+ createWrapper();
+ });
+
+ it('attempts to retrieve the items from the local storage', () => {
+ expect(window.localStorage.getItem).toHaveBeenCalledTimes(1);
+ expect(window.localStorage.getItem).toHaveBeenCalledWith(storageKey);
+ });
+
+ it('renders the maximum amount of items', () => {
+ expect(findItemsList().props('items').length).toBe(maxItems);
+ });
+
+ it('does not render the empty text slot', () => {
+ expect(findEmptyText().exists()).toBe(false);
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/global_search/components/global_search_autocomplete_items_spec.js b/spec/frontend/super_sidebar/components/global_search/components/global_search_autocomplete_items_spec.js
new file mode 100644
index 00000000000..e5ba1c63996
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/global_search/components/global_search_autocomplete_items_spec.js
@@ -0,0 +1,238 @@
+import { GlDropdownItem, GlLoadingIcon, GlAvatar, GlAlert, GlDropdownDivider } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+import Vuex from 'vuex';
+import HeaderSearchAutocompleteItems from '~/super_sidebar/components/global_search/components/global_search_autocomplete_items.vue';
+import {
+ LARGE_AVATAR_PX,
+ SMALL_AVATAR_PX,
+} from '~/super_sidebar/components/global_search/constants';
+import {
+ PROJECTS_CATEGORY,
+ GROUPS_CATEGORY,
+ ISSUES_CATEGORY,
+ MERGE_REQUEST_CATEGORY,
+ RECENT_EPICS_CATEGORY,
+} from '~/vue_shared/global_search/constants';
+import {
+ MOCK_GROUPED_AUTOCOMPLETE_OPTIONS,
+ MOCK_SORTED_AUTOCOMPLETE_OPTIONS,
+ MOCK_GROUPED_AUTOCOMPLETE_OPTIONS_SETTINGS_HELP,
+ MOCK_GROUPED_AUTOCOMPLETE_OPTIONS_HELP,
+ MOCK_SEARCH,
+ MOCK_GROUPED_AUTOCOMPLETE_OPTIONS_2,
+} from '../mock_data';
+
+Vue.use(Vuex);
+
+describe('HeaderSearchAutocompleteItems', () => {
+ let wrapper;
+
+ const createComponent = (initialState, mockGetters, props) => {
+ const store = new Vuex.Store({
+ state: {
+ loading: false,
+ ...initialState,
+ },
+ getters: {
+ autocompleteGroupedSearchOptions: () => MOCK_GROUPED_AUTOCOMPLETE_OPTIONS,
+ ...mockGetters,
+ },
+ });
+
+ wrapper = shallowMount(HeaderSearchAutocompleteItems, {
+ store,
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findGlDropdownDividers = () => wrapper.findAllComponents(GlDropdownDivider);
+ const findFirstDropdownItem = () => findDropdownItems().at(0);
+ const findDropdownItemTitles = () =>
+ findDropdownItems().wrappers.map((w) => w.findAll('span').at(1).text());
+ const findDropdownItemSubTitles = () =>
+ findDropdownItems()
+ .wrappers.filter((w) => w.findAll('span').length > 2)
+ .map((w) => w.findAll('span').at(2).text());
+ const findDropdownItemLinks = () => findDropdownItems().wrappers.map((w) => w.attributes('href'));
+ const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findGlAvatar = () => wrapper.findComponent(GlAvatar);
+ const findGlAlert = () => wrapper.findComponent(GlAlert);
+
+ describe('template', () => {
+ describe('when loading is true', () => {
+ beforeEach(() => {
+ createComponent({ loading: true });
+ });
+
+ it('renders GlLoadingIcon', () => {
+ expect(findGlLoadingIcon().exists()).toBe(true);
+ });
+
+ it('does not render autocomplete options', () => {
+ expect(findDropdownItems()).toHaveLength(0);
+ });
+ });
+
+ describe('when api returns error', () => {
+ beforeEach(() => {
+ createComponent({ autocompleteError: true });
+ });
+
+ it('renders Alert', () => {
+ expect(findGlAlert().exists()).toBe(true);
+ });
+ });
+ describe('when loading is false', () => {
+ beforeEach(() => {
+ createComponent({ loading: false });
+ });
+
+ it('does not render GlLoadingIcon', () => {
+ expect(findGlLoadingIcon().exists()).toBe(false);
+ });
+
+ describe('Dropdown items', () => {
+ it('renders item for each option in autocomplete option', () => {
+ expect(findDropdownItems()).toHaveLength(MOCK_SORTED_AUTOCOMPLETE_OPTIONS.length);
+ });
+
+ it('renders titles correctly', () => {
+ const expectedTitles = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.map((o) => o.value || o.label);
+ expect(findDropdownItemTitles()).toStrictEqual(expectedTitles);
+ });
+
+ it('renders sub-titles correctly', () => {
+ const expectedSubTitles = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.filter((o) => o.value).map(
+ (o) => o.label,
+ );
+ expect(findDropdownItemSubTitles()).toStrictEqual(expectedSubTitles);
+ });
+
+ it('renders links correctly', () => {
+ const expectedLinks = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.map((o) => o.url);
+ expect(findDropdownItemLinks()).toStrictEqual(expectedLinks);
+ });
+ });
+
+ describe.each`
+ item | showAvatar | avatarSize | searchContext | entityId | entityName
+ ${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ project: { id: 29 } }} | ${'29'} | ${''}
+ ${{ data: [{ category: GROUPS_CATEGORY, avatar_url: '/123' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ group: { id: 12 } }} | ${'12'} | ${''}
+ ${{ data: [{ category: 'Help', avatar_url: '' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${null} | ${'0'} | ${''}
+ ${{ data: [{ category: 'Settings' }] }} | ${false} | ${false} | ${null} | ${false} | ${false}
+ ${{ data: [{ category: GROUPS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ group: { id: 1, name: 'test1' } }} | ${'1'} | ${'test1'}
+ ${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ project: { id: 2, name: 'test2' } }} | ${'2'} | ${'test2'}
+ ${{ data: [{ category: ISSUES_CATEGORY, avatar_url: null }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ project: { id: 3, name: 'test3' } }} | ${'3'} | ${'test3'}
+ ${{ data: [{ category: MERGE_REQUEST_CATEGORY, avatar_url: null }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ project: { id: 4, name: 'test4' } }} | ${'4'} | ${'test4'}
+ ${{ data: [{ category: RECENT_EPICS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ group: { id: 5, name: 'test5' } }} | ${'5'} | ${'test5'}
+ ${{ data: [{ category: GROUPS_CATEGORY, avatar_url: null, group_id: 6, group_name: 'test6' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${null} | ${'6'} | ${'test6'}
+ ${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null, project_id: 7, project_name: 'test7' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${null} | ${'7'} | ${'test7'}
+ ${{ data: [{ category: ISSUES_CATEGORY, avatar_url: null, project_id: 8, project_name: 'test8' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${null} | ${'8'} | ${'test8'}
+ ${{ data: [{ category: MERGE_REQUEST_CATEGORY, avatar_url: null, project_id: 9, project_name: 'test9' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${null} | ${'9'} | ${'test9'}
+ ${{ data: [{ category: RECENT_EPICS_CATEGORY, avatar_url: null, group_id: 10, group_name: 'test10' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${null} | ${'10'} | ${'test10'}
+ ${{ data: [{ category: GROUPS_CATEGORY, avatar_url: null, group_id: 11, group_name: 'test11' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ group: { id: 1, name: 'test1' } }} | ${'11'} | ${'test11'}
+ ${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null, project_id: 12, project_name: 'test12' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ project: { id: 2, name: 'test2' } }} | ${'12'} | ${'test12'}
+ ${{ data: [{ category: ISSUES_CATEGORY, avatar_url: null, project_id: 13, project_name: 'test13' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ project: { id: 3, name: 'test3' } }} | ${'13'} | ${'test13'}
+ ${{ data: [{ category: MERGE_REQUEST_CATEGORY, avatar_url: null, project_id: 14, project_name: 'test14' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ project: { id: 4, name: 'test4' } }} | ${'14'} | ${'test14'}
+ ${{ data: [{ category: RECENT_EPICS_CATEGORY, avatar_url: null, group_id: 15, group_name: 'test15' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ group: { id: 5, name: 'test5' } }} | ${'15'} | ${'test15'}
+ `('GlAvatar', ({ item, showAvatar, avatarSize, searchContext, entityId, entityName }) => {
+ describe(`when category is ${item.data[0].category} and avatar_url is ${item.data[0].avatar_url}`, () => {
+ beforeEach(() => {
+ createComponent({ searchContext }, { autocompleteGroupedSearchOptions: () => [item] });
+ });
+
+ it(`should${showAvatar ? '' : ' not'} render`, () => {
+ expect(findGlAvatar().exists()).toBe(showAvatar);
+ });
+
+ it(`should set avatarSize to ${avatarSize}`, () => {
+ expect(findGlAvatar().exists() && findGlAvatar().attributes('size')).toBe(avatarSize);
+ });
+
+ it(`should set avatar entityId to ${entityId}`, () => {
+ expect(findGlAvatar().exists() && findGlAvatar().attributes('entityid')).toBe(entityId);
+ });
+
+ it(`should set avatar entityName to ${entityName}`, () => {
+ expect(findGlAvatar().exists() && findGlAvatar().attributes('entityname')).toBe(
+ entityName,
+ );
+ });
+ });
+ });
+ });
+
+ describe.each`
+ currentFocusedOption | isFocused | ariaSelected
+ ${null} | ${false} | ${undefined}
+ ${{ html_id: 'not-a-match' }} | ${false} | ${undefined}
+ ${MOCK_SORTED_AUTOCOMPLETE_OPTIONS[0]} | ${true} | ${'true'}
+ `('isOptionFocused', ({ currentFocusedOption, isFocused, ariaSelected }) => {
+ describe(`when currentFocusedOption.html_id is ${currentFocusedOption?.html_id}`, () => {
+ beforeEach(() => {
+ createComponent({}, {}, { currentFocusedOption });
+ });
+
+ it(`should${isFocused ? '' : ' not'} have gl-bg-gray-50 applied`, () => {
+ expect(findFirstDropdownItem().classes('gl-bg-gray-50')).toBe(isFocused);
+ });
+
+ it(`sets "aria-selected to ${ariaSelected}`, () => {
+ expect(findFirstDropdownItem().attributes('aria-selected')).toBe(ariaSelected);
+ });
+ });
+ });
+
+ describe.each`
+ search | items | dividerCount
+ ${null} | ${[]} | ${0}
+ ${''} | ${[]} | ${0}
+ ${'1'} | ${[]} | ${0}
+ ${')'} | ${[]} | ${0}
+ ${'t'} | ${MOCK_GROUPED_AUTOCOMPLETE_OPTIONS_SETTINGS_HELP} | ${1}
+ ${'te'} | ${MOCK_GROUPED_AUTOCOMPLETE_OPTIONS_HELP} | ${0}
+ ${'tes'} | ${MOCK_GROUPED_AUTOCOMPLETE_OPTIONS_2} | ${1}
+ ${MOCK_SEARCH} | ${MOCK_GROUPED_AUTOCOMPLETE_OPTIONS_2} | ${1}
+ `('Header Search Dropdown Dividers', ({ search, items, dividerCount }) => {
+ describe(`when search is ${search}`, () => {
+ beforeEach(() => {
+ createComponent(
+ { search },
+ {
+ autocompleteGroupedSearchOptions: () => items,
+ },
+ {},
+ );
+ });
+
+ it(`component should have ${dividerCount} dividers`, () => {
+ expect(findGlDropdownDividers()).toHaveLength(dividerCount);
+ });
+ });
+ });
+ });
+
+ describe('watchers', () => {
+ describe('currentFocusedOption', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('when focused changes to existing element calls scroll into view on the newly focused element', async () => {
+ const focusedElement = findFirstDropdownItem().element;
+ const scrollSpy = jest.spyOn(focusedElement, 'scrollIntoView');
+
+ wrapper.setProps({ currentFocusedOption: MOCK_SORTED_AUTOCOMPLETE_OPTIONS[0] });
+
+ await nextTick();
+
+ expect(scrollSpy).toHaveBeenCalledWith(false);
+ scrollSpy.mockRestore();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js
new file mode 100644
index 00000000000..132f8e60598
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/global_search/components/global_search_default_items_spec.js
@@ -0,0 +1,102 @@
+import { GlDropdownItem, GlDropdownSectionHeader } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import Vuex from 'vuex';
+import HeaderSearchDefaultItems from '~/super_sidebar/components/global_search/components/global_search_default_items.vue';
+import { MOCK_SEARCH_CONTEXT, MOCK_DEFAULT_SEARCH_OPTIONS } from '../mock_data';
+
+Vue.use(Vuex);
+
+describe('HeaderSearchDefaultItems', () => {
+ let wrapper;
+
+ const createComponent = (initialState, props) => {
+ const store = new Vuex.Store({
+ state: {
+ searchContext: MOCK_SEARCH_CONTEXT,
+ ...initialState,
+ },
+ getters: {
+ defaultSearchOptions: () => MOCK_DEFAULT_SEARCH_OPTIONS,
+ },
+ });
+
+ wrapper = shallowMount(HeaderSearchDefaultItems, {
+ store,
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ const findDropdownHeader = () => wrapper.findComponent(GlDropdownSectionHeader);
+ const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findFirstDropdownItem = () => findDropdownItems().at(0);
+ const findDropdownItemTitles = () => findDropdownItems().wrappers.map((w) => w.text());
+ const findDropdownItemLinks = () => findDropdownItems().wrappers.map((w) => w.attributes('href'));
+
+ describe('template', () => {
+ describe('Dropdown items', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders item for each option in defaultSearchOptions', () => {
+ expect(findDropdownItems()).toHaveLength(MOCK_DEFAULT_SEARCH_OPTIONS.length);
+ });
+
+ it('renders titles correctly', () => {
+ const expectedTitles = MOCK_DEFAULT_SEARCH_OPTIONS.map((o) => o.title);
+ expect(findDropdownItemTitles()).toStrictEqual(expectedTitles);
+ });
+
+ it('renders links correctly', () => {
+ const expectedLinks = MOCK_DEFAULT_SEARCH_OPTIONS.map((o) => o.url);
+ expect(findDropdownItemLinks()).toStrictEqual(expectedLinks);
+ });
+ });
+
+ describe.each`
+ group | project | dropdownTitle
+ ${null} | ${null} | ${'All GitLab'}
+ ${{ name: 'Test Group' }} | ${null} | ${'Test Group'}
+ ${{ name: 'Test Group' }} | ${{ name: 'Test Project' }} | ${'Test Project'}
+ `('Dropdown Header', ({ group, project, dropdownTitle }) => {
+ describe(`when group is ${group?.name} and project is ${project?.name}`, () => {
+ beforeEach(() => {
+ createComponent({
+ searchContext: {
+ group,
+ project,
+ },
+ });
+ });
+
+ it(`should render as ${dropdownTitle}`, () => {
+ expect(findDropdownHeader().text()).toBe(dropdownTitle);
+ });
+ });
+ });
+
+ describe.each`
+ currentFocusedOption | isFocused | ariaSelected
+ ${null} | ${false} | ${undefined}
+ ${{ html_id: 'not-a-match' }} | ${false} | ${undefined}
+ ${MOCK_DEFAULT_SEARCH_OPTIONS[0]} | ${true} | ${'true'}
+ `('isOptionFocused', ({ currentFocusedOption, isFocused, ariaSelected }) => {
+ describe(`when currentFocusedOption.html_id is ${currentFocusedOption?.html_id}`, () => {
+ beforeEach(() => {
+ createComponent({}, { currentFocusedOption });
+ });
+
+ it(`should${isFocused ? '' : ' not'} have gl-bg-gray-50 applied`, () => {
+ expect(findFirstDropdownItem().classes('gl-bg-gray-50')).toBe(isFocused);
+ });
+
+ it(`sets "aria-selected to ${ariaSelected}`, () => {
+ expect(findFirstDropdownItem().attributes('aria-selected')).toBe(ariaSelected);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/global_search/components/global_search_scoped_items_spec.js b/spec/frontend/super_sidebar/components/global_search/components/global_search_scoped_items_spec.js
new file mode 100644
index 00000000000..fa91ef43ced
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/global_search/components/global_search_scoped_items_spec.js
@@ -0,0 +1,120 @@
+import { GlDropdownItem, GlToken, GlIcon } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import Vuex from 'vuex';
+import { trimText } from 'helpers/text_helper';
+import HeaderSearchScopedItems from '~/super_sidebar/components/global_search/components/global_search_scoped_items.vue';
+import { truncate } from '~/lib/utils/text_utility';
+import { SCOPE_TOKEN_MAX_LENGTH } from '~/super_sidebar/components/global_search/constants';
+import { MSG_IN_ALL_GITLAB } from '~/vue_shared/global_search/constants';
+import {
+ MOCK_SEARCH,
+ MOCK_SCOPED_SEARCH_OPTIONS,
+ MOCK_GROUPED_AUTOCOMPLETE_OPTIONS,
+} from '../mock_data';
+
+Vue.use(Vuex);
+
+describe('HeaderSearchScopedItems', () => {
+ let wrapper;
+
+ const createComponent = (initialState, mockGetters, props) => {
+ const store = new Vuex.Store({
+ state: {
+ search: MOCK_SEARCH,
+ ...initialState,
+ },
+ getters: {
+ scopedSearchOptions: () => MOCK_SCOPED_SEARCH_OPTIONS,
+ autocompleteGroupedSearchOptions: () => MOCK_GROUPED_AUTOCOMPLETE_OPTIONS,
+ ...mockGetters,
+ },
+ });
+
+ wrapper = shallowMount(HeaderSearchScopedItems, {
+ store,
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findFirstDropdownItem = () => findDropdownItems().at(0);
+ const findDropdownItemTitles = () => findDropdownItems().wrappers.map((w) => trimText(w.text()));
+ const findScopeTokens = () => wrapper.findAllComponents(GlToken);
+ const findScopeTokensText = () => findScopeTokens().wrappers.map((w) => trimText(w.text()));
+ const findScopeTokensIcons = () =>
+ findScopeTokens().wrappers.map((w) => w.findAllComponents(GlIcon));
+ const findDropdownItemAriaLabels = () =>
+ findDropdownItems().wrappers.map((w) => trimText(w.attributes('aria-label')));
+ const findDropdownItemLinks = () => findDropdownItems().wrappers.map((w) => w.attributes('href'));
+
+ describe('template', () => {
+ describe('Dropdown items', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders item for each option in scopedSearchOptions', () => {
+ expect(findDropdownItems()).toHaveLength(MOCK_SCOPED_SEARCH_OPTIONS.length);
+ });
+
+ it('renders titles correctly', () => {
+ findDropdownItemTitles().forEach((title) => expect(title).toContain(MOCK_SEARCH));
+ });
+
+ it('renders scope names correctly', () => {
+ const expectedTitles = MOCK_SCOPED_SEARCH_OPTIONS.map((o) =>
+ truncate(trimText(`in ${o.description || o.scope}`), SCOPE_TOKEN_MAX_LENGTH),
+ );
+
+ expect(findScopeTokensText()).toStrictEqual(expectedTitles);
+ });
+
+ it('renders scope icons correctly', () => {
+ findScopeTokensIcons().forEach((icon, i) => {
+ const w = icon.wrappers[0];
+ expect(w?.attributes('name')).toBe(MOCK_SCOPED_SEARCH_OPTIONS[i].icon);
+ });
+ });
+
+ it(`renders scope ${MSG_IN_ALL_GITLAB} correctly`, () => {
+ expect(findScopeTokens().at(-1).findComponent(GlIcon).exists()).toBe(false);
+ });
+
+ it('renders aria-labels correctly', () => {
+ const expectedLabels = MOCK_SCOPED_SEARCH_OPTIONS.map((o) =>
+ trimText(`${MOCK_SEARCH} ${o.description || o.icon} ${o.scope || ''}`),
+ );
+ expect(findDropdownItemAriaLabels()).toStrictEqual(expectedLabels);
+ });
+
+ it('renders links correctly', () => {
+ const expectedLinks = MOCK_SCOPED_SEARCH_OPTIONS.map((o) => o.url);
+ expect(findDropdownItemLinks()).toStrictEqual(expectedLinks);
+ });
+ });
+
+ describe.each`
+ currentFocusedOption | isFocused | ariaSelected
+ ${null} | ${false} | ${undefined}
+ ${{ html_id: 'not-a-match' }} | ${false} | ${undefined}
+ ${MOCK_SCOPED_SEARCH_OPTIONS[0]} | ${true} | ${'true'}
+ `('isOptionFocused', ({ currentFocusedOption, isFocused, ariaSelected }) => {
+ describe(`when currentFocusedOption.html_id is ${currentFocusedOption?.html_id}`, () => {
+ beforeEach(() => {
+ createComponent({}, {}, { currentFocusedOption });
+ });
+
+ it(`should${isFocused ? '' : ' not'} have gl-bg-gray-50 applied`, () => {
+ expect(findFirstDropdownItem().classes('gl-bg-gray-50')).toBe(isFocused);
+ });
+
+ it(`sets "aria-selected to ${ariaSelected}`, () => {
+ expect(findFirstDropdownItem().attributes('aria-selected')).toBe(ariaSelected);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js b/spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js
new file mode 100644
index 00000000000..0dcfc448125
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js
@@ -0,0 +1,516 @@
+import { GlSearchBoxByType, GlToken, GlIcon } from '@gitlab/ui';
+import Vue, { nextTick } from 'vue';
+import Vuex from 'vuex';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { mockTracking } from 'helpers/tracking_helper';
+import { s__, sprintf } from '~/locale';
+import HeaderSearchApp from '~/super_sidebar/components/global_search/components/global_search.vue';
+import HeaderSearchAutocompleteItems from '~/super_sidebar/components/global_search/components/global_search_autocomplete_items.vue';
+import HeaderSearchDefaultItems from '~/super_sidebar/components/global_search/components/global_search_default_items.vue';
+import HeaderSearchScopedItems from '~/super_sidebar/components/global_search/components/global_search_scoped_items.vue';
+import {
+ SEARCH_INPUT_DESCRIPTION,
+ SEARCH_RESULTS_DESCRIPTION,
+ SEARCH_BOX_INDEX,
+ ICON_PROJECT,
+ ICON_GROUP,
+ ICON_SUBGROUP,
+ SCOPE_TOKEN_MAX_LENGTH,
+ IS_SEARCHING,
+ IS_NOT_FOCUSED,
+ IS_FOCUSED,
+ SEARCH_SHORTCUTS_MIN_CHARACTERS,
+} from '~/super_sidebar/components/global_search/constants';
+import DropdownKeyboardNavigation from '~/vue_shared/components/dropdown_keyboard_navigation.vue';
+import { ENTER_KEY } from '~/lib/utils/keys';
+import { visitUrl } from '~/lib/utils/url_utility';
+import { truncate } from '~/lib/utils/text_utility';
+import {
+ MOCK_SEARCH,
+ MOCK_SEARCH_QUERY,
+ MOCK_USERNAME,
+ MOCK_DEFAULT_SEARCH_OPTIONS,
+ MOCK_SCOPED_SEARCH_OPTIONS,
+ MOCK_SEARCH_CONTEXT_FULL,
+} from '../mock_data';
+
+Vue.use(Vuex);
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ visitUrl: jest.fn(),
+}));
+
+describe('HeaderSearchApp', () => {
+ let wrapper;
+
+ const actionSpies = {
+ setSearch: jest.fn(),
+ fetchAutocompleteOptions: jest.fn(),
+ clearAutocomplete: jest.fn(),
+ };
+
+ const createComponent = (initialState, mockGetters) => {
+ const store = new Vuex.Store({
+ state: {
+ ...initialState,
+ },
+ actions: actionSpies,
+ getters: {
+ searchQuery: () => MOCK_SEARCH_QUERY,
+ searchOptions: () => MOCK_DEFAULT_SEARCH_OPTIONS,
+ ...mockGetters,
+ },
+ });
+
+ wrapper = shallowMountExtended(HeaderSearchApp, {
+ store,
+ });
+ };
+
+ const formatScopeName = (scopeName) => {
+ if (!scopeName) {
+ return false;
+ }
+ const searchResultsScope = s__('GlobalSearch|in %{scope}');
+ return truncate(
+ sprintf(searchResultsScope, {
+ scope: scopeName,
+ }),
+ SCOPE_TOKEN_MAX_LENGTH,
+ );
+ };
+
+ const findHeaderSearchForm = () => wrapper.findByTestId('header-search-form');
+ const findHeaderSearchInput = () => wrapper.findComponent(GlSearchBoxByType);
+ const findScopeToken = () => wrapper.findComponent(GlToken);
+ const findHeaderSearchInputKBD = () => wrapper.find('.keyboard-shortcut-helper');
+ const findHeaderSearchDropdown = () => wrapper.findByTestId('header-search-dropdown-menu');
+ const findHeaderSearchDefaultItems = () => wrapper.findComponent(HeaderSearchDefaultItems);
+ const findHeaderSearchScopedItems = () => wrapper.findComponent(HeaderSearchScopedItems);
+ const findHeaderSearchAutocompleteItems = () =>
+ wrapper.findComponent(HeaderSearchAutocompleteItems);
+ const findDropdownKeyboardNavigation = () => wrapper.findComponent(DropdownKeyboardNavigation);
+ const findSearchInputDescription = () => wrapper.find(`#${SEARCH_INPUT_DESCRIPTION}`);
+ const findSearchResultsDescription = () => wrapper.findByTestId(SEARCH_RESULTS_DESCRIPTION);
+
+ describe('template', () => {
+ describe('always renders', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('Header Search Input', () => {
+ expect(findHeaderSearchInput().exists()).toBe(true);
+ });
+
+ it('Header Search Input KBD hint', () => {
+ expect(findHeaderSearchInputKBD().exists()).toBe(true);
+ expect(findHeaderSearchInputKBD().text()).toContain('/');
+ expect(findHeaderSearchInputKBD().attributes('title')).toContain(
+ 'Use the shortcut key <kbd>/</kbd> to start a search',
+ );
+ });
+
+ it('Search Input Description', () => {
+ expect(findSearchInputDescription().exists()).toBe(true);
+ });
+
+ it('Search Results Description', () => {
+ expect(findSearchResultsDescription().exists()).toBe(true);
+ });
+ });
+
+ describe.each`
+ showDropdown | username | showSearchDropdown
+ ${false} | ${null} | ${false}
+ ${false} | ${MOCK_USERNAME} | ${false}
+ ${true} | ${null} | ${false}
+ ${true} | ${MOCK_USERNAME} | ${true}
+ `('Header Search Dropdown', ({ showDropdown, username, showSearchDropdown }) => {
+ describe(`when showDropdown is ${showDropdown} and current_username is ${username}`, () => {
+ beforeEach(() => {
+ window.gon.current_username = username;
+ createComponent();
+ findHeaderSearchInput().vm.$emit(showDropdown ? 'click' : '');
+ });
+
+ it(`should${showSearchDropdown ? '' : ' not'} render`, () => {
+ expect(findHeaderSearchDropdown().exists()).toBe(showSearchDropdown);
+ });
+ });
+ });
+
+ describe.each`
+ search | showDefault | showScoped | showAutocomplete
+ ${null} | ${true} | ${false} | ${false}
+ ${''} | ${true} | ${false} | ${false}
+ ${'t'} | ${false} | ${false} | ${true}
+ ${'te'} | ${false} | ${false} | ${true}
+ ${'tes'} | ${false} | ${true} | ${true}
+ ${MOCK_SEARCH} | ${false} | ${true} | ${true}
+ `('Header Search Dropdown Items', ({ search, showDefault, showScoped, showAutocomplete }) => {
+ describe(`when search is ${search}`, () => {
+ beforeEach(() => {
+ window.gon.current_username = MOCK_USERNAME;
+ createComponent({ search }, {});
+ findHeaderSearchInput().vm.$emit('click');
+ });
+
+ it(`should${showDefault ? '' : ' not'} render the Default Dropdown Items`, () => {
+ expect(findHeaderSearchDefaultItems().exists()).toBe(showDefault);
+ });
+
+ it(`should${showScoped ? '' : ' not'} render the Scoped Dropdown Items`, () => {
+ expect(findHeaderSearchScopedItems().exists()).toBe(showScoped);
+ });
+
+ it(`should${showAutocomplete ? '' : ' not'} render the Autocomplete Dropdown Items`, () => {
+ expect(findHeaderSearchAutocompleteItems().exists()).toBe(showAutocomplete);
+ });
+
+ it(`should render the Dropdown Navigation Component`, () => {
+ expect(findDropdownKeyboardNavigation().exists()).toBe(true);
+ });
+
+ it(`should close the dropdown when press escape key`, async () => {
+ findHeaderSearchInput().vm.$emit('keydown', new KeyboardEvent({ key: 27 }));
+ await nextTick();
+ expect(findHeaderSearchDropdown().exists()).toBe(false);
+ expect(wrapper.emitted().expandSearchBar.length).toBe(1);
+ });
+ });
+ });
+
+ describe.each`
+ username | showDropdown | expectedDesc
+ ${null} | ${false} | ${HeaderSearchApp.i18n.SEARCH_INPUT_DESCRIBE_BY_NO_DROPDOWN}
+ ${null} | ${true} | ${HeaderSearchApp.i18n.SEARCH_INPUT_DESCRIBE_BY_NO_DROPDOWN}
+ ${MOCK_USERNAME} | ${false} | ${HeaderSearchApp.i18n.SEARCH_INPUT_DESCRIBE_BY_WITH_DROPDOWN}
+ ${MOCK_USERNAME} | ${true} | ${HeaderSearchApp.i18n.SEARCH_INPUT_DESCRIBE_BY_WITH_DROPDOWN}
+ `('Search Input Description', ({ username, showDropdown, expectedDesc }) => {
+ describe(`current_username is ${username} and showDropdown is ${showDropdown}`, () => {
+ beforeEach(() => {
+ window.gon.current_username = username;
+ createComponent();
+ findHeaderSearchInput().vm.$emit(showDropdown ? 'click' : '');
+ });
+
+ it(`sets description to ${expectedDesc}`, () => {
+ expect(findSearchInputDescription().text()).toBe(expectedDesc);
+ });
+ });
+ });
+
+ describe.each`
+ username | showDropdown | search | loading | searchOptions | expectedDesc
+ ${null} | ${true} | ${''} | ${false} | ${[]} | ${''}
+ ${MOCK_USERNAME} | ${false} | ${''} | ${false} | ${[]} | ${''}
+ ${MOCK_USERNAME} | ${true} | ${''} | ${false} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${`${MOCK_DEFAULT_SEARCH_OPTIONS.length} default results provided. Use the up and down arrow keys to navigate search results list.`}
+ ${MOCK_USERNAME} | ${true} | ${''} | ${true} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${`${MOCK_DEFAULT_SEARCH_OPTIONS.length} default results provided. Use the up and down arrow keys to navigate search results list.`}
+ ${MOCK_USERNAME} | ${true} | ${MOCK_SEARCH} | ${false} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${`Results updated. ${MOCK_SCOPED_SEARCH_OPTIONS.length} results available. Use the up and down arrow keys to navigate search results list, or ENTER to submit.`}
+ ${MOCK_USERNAME} | ${true} | ${MOCK_SEARCH} | ${true} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${HeaderSearchApp.i18n.SEARCH_RESULTS_LOADING}
+ `(
+ 'Search Results Description',
+ ({ username, showDropdown, search, loading, searchOptions, expectedDesc }) => {
+ describe(`search is "${search}", loading is ${loading}, and showSearchDropdown is ${showDropdown}`, () => {
+ beforeEach(() => {
+ window.gon.current_username = username;
+ createComponent(
+ {
+ search,
+ loading,
+ },
+ {
+ searchOptions: () => searchOptions,
+ },
+ );
+ findHeaderSearchInput().vm.$emit(showDropdown ? 'click' : '');
+ });
+
+ it(`sets description to ${expectedDesc}`, () => {
+ expect(findSearchResultsDescription().text()).toBe(expectedDesc);
+ });
+ });
+ },
+ );
+
+ describe('input box', () => {
+ describe.each`
+ search | searchOptions | hasToken
+ ${MOCK_SEARCH} | ${[MOCK_SCOPED_SEARCH_OPTIONS[0]]} | ${true}
+ ${MOCK_SEARCH} | ${[MOCK_SCOPED_SEARCH_OPTIONS[1]]} | ${true}
+ ${MOCK_SEARCH} | ${[MOCK_SCOPED_SEARCH_OPTIONS[2]]} | ${true}
+ ${MOCK_SEARCH} | ${[MOCK_SCOPED_SEARCH_OPTIONS[3]]} | ${true}
+ ${MOCK_SEARCH} | ${[MOCK_SCOPED_SEARCH_OPTIONS[4]]} | ${true}
+ ${'te'} | ${[MOCK_SCOPED_SEARCH_OPTIONS[5]]} | ${false}
+ ${'x'} | ${[]} | ${false}
+ `('token', ({ search, searchOptions, hasToken }) => {
+ beforeEach(() => {
+ window.gon.current_username = MOCK_USERNAME;
+ createComponent(
+ { search },
+ {
+ searchOptions: () => searchOptions,
+ },
+ );
+ findHeaderSearchInput().vm.$emit('click');
+ });
+
+ it(`${hasToken ? 'is' : 'is NOT'} rendered when data set has type "${
+ searchOptions[0]?.html_id
+ }"`, () => {
+ expect(findScopeToken().exists()).toBe(hasToken);
+ });
+
+ it(`text ${hasToken ? 'is correctly' : 'is NOT'} rendered when text is "${
+ searchOptions[0]?.scope || searchOptions[0]?.description
+ }"`, () => {
+ expect(findScopeToken().exists() && findScopeToken().text()).toBe(
+ formatScopeName(searchOptions[0]?.scope || searchOptions[0]?.description),
+ );
+ });
+ });
+ });
+
+ describe('form', () => {
+ describe.each`
+ searchContext | search | searchOptions | isFocused
+ ${MOCK_SEARCH_CONTEXT_FULL} | ${null} | ${[]} | ${true}
+ ${MOCK_SEARCH_CONTEXT_FULL} | ${MOCK_SEARCH} | ${[]} | ${true}
+ ${MOCK_SEARCH_CONTEXT_FULL} | ${MOCK_SEARCH} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${true}
+ ${MOCK_SEARCH_CONTEXT_FULL} | ${MOCK_SEARCH} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${false}
+ ${null} | ${MOCK_SEARCH} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${true}
+ ${null} | ${null} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${true}
+ ${null} | ${null} | ${[]} | ${true}
+ `('wrapper', ({ searchContext, search, searchOptions, isFocused }) => {
+ beforeEach(() => {
+ window.gon.current_username = MOCK_USERNAME;
+ createComponent({ search, searchContext }, { searchOptions: () => searchOptions });
+ if (isFocused) {
+ findHeaderSearchInput().vm.$emit('click');
+ }
+ });
+
+ const isSearching = search?.length > SEARCH_SHORTCUTS_MIN_CHARACTERS;
+
+ it(`classes ${isSearching ? 'contain' : 'do not contain'} "${IS_SEARCHING}"`, () => {
+ if (isSearching) {
+ expect(findHeaderSearchForm().classes()).toContain(IS_SEARCHING);
+ return;
+ }
+ if (!isSearching) {
+ expect(findHeaderSearchForm().classes()).not.toContain(IS_SEARCHING);
+ }
+ });
+
+ it(`classes ${isSearching ? 'contain' : 'do not contain'} "${
+ isFocused ? IS_FOCUSED : IS_NOT_FOCUSED
+ }"`, () => {
+ expect(findHeaderSearchForm().classes()).toContain(
+ isFocused ? IS_FOCUSED : IS_NOT_FOCUSED,
+ );
+ });
+ });
+ });
+
+ describe.each`
+ search | searchOptions | hasIcon | iconName
+ ${MOCK_SEARCH} | ${[MOCK_SCOPED_SEARCH_OPTIONS[0]]} | ${true} | ${ICON_PROJECT}
+ ${MOCK_SEARCH} | ${[MOCK_SCOPED_SEARCH_OPTIONS[2]]} | ${true} | ${ICON_GROUP}
+ ${MOCK_SEARCH} | ${[MOCK_SCOPED_SEARCH_OPTIONS[3]]} | ${true} | ${ICON_SUBGROUP}
+ ${MOCK_SEARCH} | ${[MOCK_SCOPED_SEARCH_OPTIONS[4]]} | ${false} | ${false}
+ `('token', ({ search, searchOptions, hasIcon, iconName }) => {
+ beforeEach(() => {
+ window.gon.current_username = MOCK_USERNAME;
+ createComponent(
+ { search },
+ {
+ searchOptions: () => searchOptions,
+ },
+ );
+ findHeaderSearchInput().vm.$emit('click');
+ });
+
+ it(`icon for data set type "${searchOptions[0]?.html_id}" ${
+ hasIcon ? 'is' : 'is NOT'
+ } rendered`, () => {
+ expect(findScopeToken().findComponent(GlIcon).exists()).toBe(hasIcon);
+ });
+
+ it(`render ${iconName ? `"${iconName}"` : 'NO'} icon for data set type "${
+ searchOptions[0]?.html_id
+ }"`, () => {
+ expect(
+ findScopeToken().findComponent(GlIcon).exists() &&
+ findScopeToken().findComponent(GlIcon).attributes('name'),
+ ).toBe(iconName);
+ });
+ });
+ });
+
+ describe('events', () => {
+ beforeEach(() => {
+ window.gon.current_username = MOCK_USERNAME;
+ createComponent();
+ });
+
+ describe('Header Search Input', () => {
+ describe('when dropdown is closed', () => {
+ let trackingSpy;
+
+ beforeEach(() => {
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ });
+
+ it('onFocus opens dropdown and triggers snowplow event', async () => {
+ expect(findHeaderSearchDropdown().exists()).toBe(false);
+ findHeaderSearchInput().vm.$emit('focus');
+
+ await nextTick();
+
+ expect(findHeaderSearchDropdown().exists()).toBe(true);
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'focus_input', {
+ label: 'global_search',
+ property: 'navigation_top',
+ });
+ });
+
+ it('onClick opens dropdown and triggers snowplow event', async () => {
+ expect(findHeaderSearchDropdown().exists()).toBe(false);
+ findHeaderSearchInput().vm.$emit('click');
+
+ await nextTick();
+
+ expect(findHeaderSearchDropdown().exists()).toBe(true);
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'focus_input', {
+ label: 'global_search',
+ property: 'navigation_top',
+ });
+ });
+
+ it('onClick followed by onFocus only triggers a single snowplow event', async () => {
+ findHeaderSearchInput().vm.$emit('click');
+ findHeaderSearchInput().vm.$emit('focus');
+
+ expect(trackingSpy).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('onInput', () => {
+ describe('when search has text', () => {
+ beforeEach(() => {
+ findHeaderSearchInput().vm.$emit('input', MOCK_SEARCH);
+ });
+
+ it('calls setSearch with search term', () => {
+ expect(actionSpies.setSearch).toHaveBeenCalledWith(expect.any(Object), MOCK_SEARCH);
+ });
+
+ it('calls fetchAutocompleteOptions', () => {
+ expect(actionSpies.fetchAutocompleteOptions).toHaveBeenCalled();
+ });
+
+ it('does not call clearAutocomplete', () => {
+ expect(actionSpies.clearAutocomplete).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when search is emptied', () => {
+ beforeEach(() => {
+ findHeaderSearchInput().vm.$emit('input', '');
+ });
+
+ it('calls setSearch with empty term', () => {
+ expect(actionSpies.setSearch).toHaveBeenCalledWith(expect.any(Object), '');
+ });
+
+ it('does not call fetchAutocompleteOptions', () => {
+ expect(actionSpies.fetchAutocompleteOptions).not.toHaveBeenCalled();
+ });
+
+ it('calls clearAutocomplete', () => {
+ expect(actionSpies.clearAutocomplete).toHaveBeenCalled();
+ });
+ });
+ });
+ });
+
+ describe('Dropdown Keyboard Navigation', () => {
+ beforeEach(() => {
+ findHeaderSearchInput().vm.$emit('click');
+ });
+
+ it('closes dropdown when @tab is emitted', async () => {
+ expect(findHeaderSearchDropdown().exists()).toBe(true);
+ findDropdownKeyboardNavigation().vm.$emit('tab');
+
+ await nextTick();
+
+ expect(findHeaderSearchDropdown().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('computed', () => {
+ describe.each`
+ MOCK_INDEX | search
+ ${1} | ${null}
+ ${SEARCH_BOX_INDEX} | ${'test'}
+ ${2} | ${'test1'}
+ `('currentFocusedOption', ({ MOCK_INDEX, search }) => {
+ beforeEach(() => {
+ window.gon.current_username = MOCK_USERNAME;
+ createComponent({ search });
+ findHeaderSearchInput().vm.$emit('click');
+ });
+
+ it(`when currentFocusIndex changes to ${MOCK_INDEX} updates the data to searchOptions[${MOCK_INDEX}]`, () => {
+ findDropdownKeyboardNavigation().vm.$emit('change', MOCK_INDEX);
+ expect(wrapper.vm.currentFocusedOption).toBe(MOCK_DEFAULT_SEARCH_OPTIONS[MOCK_INDEX]);
+ });
+ });
+ });
+
+ describe('Submitting a search', () => {
+ describe('with no currentFocusedOption', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('onKey-enter submits a search', () => {
+ findHeaderSearchInput().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY }));
+
+ expect(visitUrl).toHaveBeenCalledWith(MOCK_SEARCH_QUERY);
+ });
+ });
+
+ describe('with less than min characters and no dropdown results', () => {
+ beforeEach(() => {
+ createComponent({ search: 'x' });
+ });
+
+ it('onKey-enter will NOT submit a search', () => {
+ findHeaderSearchInput().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY }));
+
+ expect(visitUrl).not.toHaveBeenCalledWith(MOCK_SEARCH_QUERY);
+ });
+ });
+
+ describe('with currentFocusedOption', () => {
+ const MOCK_INDEX = 1;
+
+ beforeEach(() => {
+ window.gon.current_username = MOCK_USERNAME;
+ createComponent();
+ findHeaderSearchInput().vm.$emit('click');
+ });
+
+ it('onKey-enter clicks the selected dropdown item rather than submitting a search', () => {
+ findDropdownKeyboardNavigation().vm.$emit('change', MOCK_INDEX);
+
+ findHeaderSearchInput().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY }));
+ expect(visitUrl).toHaveBeenCalledWith(MOCK_DEFAULT_SEARCH_OPTIONS[MOCK_INDEX].url);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/global_search/mock_data.js b/spec/frontend/super_sidebar/components/global_search/mock_data.js
new file mode 100644
index 00000000000..58e578e4c4c
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/global_search/mock_data.js
@@ -0,0 +1,404 @@
+import {
+ ICON_PROJECT,
+ ICON_GROUP,
+ ICON_SUBGROUP,
+} from '~/super_sidebar/components/global_search/constants';
+import {
+ PROJECTS_CATEGORY,
+ GROUPS_CATEGORY,
+ MSG_ISSUES_ASSIGNED_TO_ME,
+ MSG_ISSUES_IVE_CREATED,
+ MSG_MR_ASSIGNED_TO_ME,
+ MSG_MR_IM_REVIEWER,
+ MSG_MR_IVE_CREATED,
+ MSG_IN_ALL_GITLAB,
+} from '~/vue_shared/global_search/constants';
+
+export const MOCK_USERNAME = 'anyone';
+
+export const MOCK_SEARCH_PATH = '/search';
+
+export const MOCK_ISSUE_PATH = '/dashboard/issues';
+
+export const MOCK_MR_PATH = '/dashboard/merge_requests';
+
+export const MOCK_ALL_PATH = '/';
+
+export const MOCK_AUTOCOMPLETE_PATH = '/autocomplete';
+
+export const MOCK_PROJECT = {
+ id: 123,
+ name: 'MockProject',
+ path: '/mock-project',
+};
+
+export const MOCK_PROJECT_LONG = {
+ id: 124,
+ name: 'Mock Project Name That Is Ridiculously Long And It Goes Forever',
+ path: '/mock-project-name-that-is-ridiculously-long-and-it-goes-forever',
+};
+
+export const MOCK_GROUP = {
+ id: 321,
+ name: 'MockGroup',
+ path: '/mock-group',
+};
+
+export const MOCK_SUBGROUP = {
+ id: 322,
+ name: 'MockSubGroup',
+ path: `${MOCK_GROUP}/mock-subgroup`,
+};
+
+export const MOCK_SEARCH_QUERY = 'http://gitlab.com/search?search=test';
+
+export const MOCK_SEARCH = 'test';
+
+export const MOCK_SEARCH_CONTEXT = {
+ project: null,
+ project_metadata: {},
+ group: null,
+ group_metadata: {},
+};
+
+export const MOCK_SEARCH_CONTEXT_FULL = {
+ group: {
+ id: 31,
+ name: 'testGroup',
+ full_name: 'testGroup',
+ },
+ group_metadata: {
+ group_path: 'testGroup',
+ name: 'testGroup',
+ issues_path: '/groups/testGroup/-/issues',
+ mr_path: '/groups/testGroup/-/merge_requests',
+ },
+};
+
+export const MOCK_DEFAULT_SEARCH_OPTIONS = [
+ {
+ html_id: 'default-issues-assigned',
+ title: MSG_ISSUES_ASSIGNED_TO_ME,
+ url: `${MOCK_ISSUE_PATH}/?assignee_username=${MOCK_USERNAME}`,
+ },
+ {
+ html_id: 'default-issues-created',
+ title: MSG_ISSUES_IVE_CREATED,
+ url: `${MOCK_ISSUE_PATH}/?author_username=${MOCK_USERNAME}`,
+ },
+ {
+ html_id: 'default-mrs-assigned',
+ title: MSG_MR_ASSIGNED_TO_ME,
+ url: `${MOCK_MR_PATH}/?assignee_username=${MOCK_USERNAME}`,
+ },
+ {
+ html_id: 'default-mrs-reviewer',
+ title: MSG_MR_IM_REVIEWER,
+ url: `${MOCK_MR_PATH}/?reviewer_username=${MOCK_USERNAME}`,
+ },
+ {
+ html_id: 'default-mrs-created',
+ title: MSG_MR_IVE_CREATED,
+ url: `${MOCK_MR_PATH}/?author_username=${MOCK_USERNAME}`,
+ },
+];
+
+export const MOCK_SCOPED_SEARCH_OPTIONS = [
+ {
+ html_id: 'scoped-in-project',
+ scope: MOCK_PROJECT.name,
+ scopeCategory: PROJECTS_CATEGORY,
+ icon: ICON_PROJECT,
+ url: MOCK_PROJECT.path,
+ },
+ {
+ html_id: 'scoped-in-project-long',
+ scope: MOCK_PROJECT_LONG.name,
+ scopeCategory: PROJECTS_CATEGORY,
+ icon: ICON_PROJECT,
+ url: MOCK_PROJECT_LONG.path,
+ },
+ {
+ html_id: 'scoped-in-group',
+ scope: MOCK_GROUP.name,
+ scopeCategory: GROUPS_CATEGORY,
+ icon: ICON_GROUP,
+ url: MOCK_GROUP.path,
+ },
+ {
+ html_id: 'scoped-in-subgroup',
+ scope: MOCK_SUBGROUP.name,
+ scopeCategory: GROUPS_CATEGORY,
+ icon: ICON_SUBGROUP,
+ url: MOCK_SUBGROUP.path,
+ },
+ {
+ html_id: 'scoped-in-all',
+ description: MSG_IN_ALL_GITLAB,
+ url: MOCK_ALL_PATH,
+ },
+];
+
+export const MOCK_SCOPED_SEARCH_OPTIONS_DEF = [
+ {
+ html_id: 'scoped-in-project',
+ scope: MOCK_PROJECT.name,
+ scopeCategory: PROJECTS_CATEGORY,
+ icon: ICON_PROJECT,
+ url: MOCK_PROJECT.path,
+ },
+ {
+ html_id: 'scoped-in-group',
+ scope: MOCK_GROUP.name,
+ scopeCategory: GROUPS_CATEGORY,
+ icon: ICON_GROUP,
+ url: MOCK_GROUP.path,
+ },
+ {
+ html_id: 'scoped-in-all',
+ description: MSG_IN_ALL_GITLAB,
+ url: MOCK_ALL_PATH,
+ },
+];
+
+export const MOCK_AUTOCOMPLETE_OPTIONS_RES = [
+ {
+ category: 'Projects',
+ id: 1,
+ label: 'Gitlab Org / MockProject1',
+ value: 'MockProject1',
+ url: 'project/1',
+ },
+ {
+ category: 'Groups',
+ id: 1,
+ label: 'Gitlab Org / MockGroup1',
+ value: 'MockGroup1',
+ url: 'group/1',
+ },
+ {
+ category: 'Projects',
+ id: 2,
+ label: 'Gitlab Org / MockProject2',
+ value: 'MockProject2',
+ url: 'project/2',
+ },
+ {
+ category: 'Help',
+ label: 'GitLab Help',
+ url: 'help/gitlab',
+ },
+];
+
+export const MOCK_AUTOCOMPLETE_OPTIONS = [
+ {
+ category: 'Projects',
+ html_id: 'autocomplete-Projects-0',
+ id: 1,
+ label: 'Gitlab Org / MockProject1',
+ value: 'MockProject1',
+ url: 'project/1',
+ },
+ {
+ category: 'Groups',
+ html_id: 'autocomplete-Groups-1',
+ id: 1,
+ label: 'Gitlab Org / MockGroup1',
+ value: 'MockGroup1',
+ url: 'group/1',
+ },
+ {
+ category: 'Projects',
+ html_id: 'autocomplete-Projects-2',
+ id: 2,
+ label: 'Gitlab Org / MockProject2',
+ value: 'MockProject2',
+ url: 'project/2',
+ },
+ {
+ category: 'Help',
+ html_id: 'autocomplete-Help-3',
+ label: 'GitLab Help',
+ url: 'help/gitlab',
+ },
+];
+
+export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [
+ {
+ category: 'Groups',
+ data: [
+ {
+ category: 'Groups',
+ html_id: 'autocomplete-Groups-1',
+
+ id: 1,
+ label: 'Gitlab Org / MockGroup1',
+ value: 'MockGroup1',
+ url: 'group/1',
+ },
+ ],
+ },
+ {
+ category: 'Projects',
+ data: [
+ {
+ category: 'Projects',
+ html_id: 'autocomplete-Projects-0',
+
+ id: 1,
+ label: 'Gitlab Org / MockProject1',
+ value: 'MockProject1',
+ url: 'project/1',
+ },
+ {
+ category: 'Projects',
+ html_id: 'autocomplete-Projects-2',
+
+ id: 2,
+ label: 'Gitlab Org / MockProject2',
+ value: 'MockProject2',
+ url: 'project/2',
+ },
+ ],
+ },
+ {
+ category: 'Help',
+ data: [
+ {
+ category: 'Help',
+ html_id: 'autocomplete-Help-3',
+
+ label: 'GitLab Help',
+ url: 'help/gitlab',
+ },
+ ],
+ },
+];
+
+export const MOCK_SORTED_AUTOCOMPLETE_OPTIONS = [
+ {
+ category: 'Groups',
+ html_id: 'autocomplete-Groups-1',
+ id: 1,
+ label: 'Gitlab Org / MockGroup1',
+ value: 'MockGroup1',
+ url: 'group/1',
+ },
+ {
+ category: 'Projects',
+ html_id: 'autocomplete-Projects-0',
+ id: 1,
+ label: 'Gitlab Org / MockProject1',
+ value: 'MockProject1',
+ url: 'project/1',
+ },
+ {
+ category: 'Projects',
+ html_id: 'autocomplete-Projects-2',
+ id: 2,
+ label: 'Gitlab Org / MockProject2',
+ value: 'MockProject2',
+ url: 'project/2',
+ },
+ {
+ category: 'Help',
+ html_id: 'autocomplete-Help-3',
+ label: 'GitLab Help',
+ url: 'help/gitlab',
+ },
+];
+
+export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS_HELP = [
+ {
+ category: 'Help',
+ data: [
+ {
+ html_id: 'autocomplete-Help-1',
+ category: 'Help',
+ label: 'Rake Tasks Help',
+ url: '/help/raketasks/index',
+ },
+ {
+ html_id: 'autocomplete-Help-2',
+ category: 'Help',
+ label: 'System Hooks Help',
+ url: '/help/system_hooks/system_hooks',
+ },
+ ],
+ },
+];
+
+export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS_SETTINGS_HELP = [
+ {
+ category: 'Settings',
+ data: [
+ {
+ html_id: 'autocomplete-Settings-0',
+ category: 'Settings',
+ label: 'User settings',
+ url: '/-/profile',
+ },
+ {
+ html_id: 'autocomplete-Settings-3',
+ category: 'Settings',
+ label: 'Admin Section',
+ url: '/admin',
+ },
+ ],
+ },
+ {
+ category: 'Help',
+ data: [
+ {
+ html_id: 'autocomplete-Help-1',
+ category: 'Help',
+ label: 'Rake Tasks Help',
+ url: '/help/raketasks/index',
+ },
+ {
+ html_id: 'autocomplete-Help-2',
+ category: 'Help',
+ label: 'System Hooks Help',
+ url: '/help/system_hooks/system_hooks',
+ },
+ ],
+ },
+];
+
+export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS_2 = [
+ {
+ category: 'Groups',
+ data: [
+ {
+ html_id: 'autocomplete-Groups-0',
+ category: 'Groups',
+ id: 148,
+ label: 'Jashkenas / Test Subgroup / test-subgroup',
+ url: '/jashkenas/test-subgroup/test-subgroup',
+ avatar_url: '',
+ },
+ {
+ html_id: 'autocomplete-Groups-1',
+ category: 'Groups',
+ id: 147,
+ label: 'Jashkenas / Test Subgroup',
+ url: '/jashkenas/test-subgroup',
+ avatar_url: '',
+ },
+ ],
+ },
+ {
+ category: 'Projects',
+ data: [
+ {
+ html_id: 'autocomplete-Projects-2',
+ category: 'Projects',
+ id: 1,
+ value: 'Gitlab Test',
+ label: 'Gitlab Org / Gitlab Test',
+ url: '/gitlab-org/gitlab-test',
+ avatar_url: '/uploads/-/system/project/avatar/1/icons8-gitlab-512.png',
+ },
+ ],
+ },
+];
diff --git a/spec/frontend/super_sidebar/components/global_search/store/actions_spec.js b/spec/frontend/super_sidebar/components/global_search/store/actions_spec.js
new file mode 100644
index 00000000000..c87b4513309
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/global_search/store/actions_spec.js
@@ -0,0 +1,113 @@
+import MockAdapter from 'axios-mock-adapter';
+import testAction from 'helpers/vuex_action_helper';
+import * as actions from '~/super_sidebar/components/global_search/store/actions';
+import * as types from '~/super_sidebar/components/global_search/store/mutation_types';
+import initState from '~/super_sidebar/components/global_search/store/state';
+import axios from '~/lib/utils/axios_utils';
+import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
+import {
+ MOCK_SEARCH,
+ MOCK_AUTOCOMPLETE_OPTIONS_RES,
+ MOCK_AUTOCOMPLETE_PATH,
+ MOCK_PROJECT,
+ MOCK_SEARCH_CONTEXT,
+ MOCK_SEARCH_PATH,
+ MOCK_MR_PATH,
+ MOCK_ISSUE_PATH,
+} from '../mock_data';
+
+jest.mock('~/alert');
+
+describe('Header Search Store Actions', () => {
+ let state;
+ let mock;
+
+ const createState = (initialState) =>
+ initState({
+ searchPath: MOCK_SEARCH_PATH,
+ issuesPath: MOCK_ISSUE_PATH,
+ mrPath: MOCK_MR_PATH,
+ autocompletePath: MOCK_AUTOCOMPLETE_PATH,
+ searchContext: MOCK_SEARCH_CONTEXT,
+ ...initialState,
+ });
+
+ afterEach(() => {
+ state = null;
+ mock.restore();
+ });
+
+ describe.each`
+ axiosMock | type | expectedMutations
+ ${{ method: 'onGet', code: HTTP_STATUS_OK, res: MOCK_AUTOCOMPLETE_OPTIONS_RES }} | ${'success'} | ${[{ type: types.REQUEST_AUTOCOMPLETE }, { type: types.RECEIVE_AUTOCOMPLETE_SUCCESS, payload: MOCK_AUTOCOMPLETE_OPTIONS_RES }, { type: types.RECEIVE_AUTOCOMPLETE_SUCCESS, payload: MOCK_AUTOCOMPLETE_OPTIONS_RES }]}
+ ${{ method: 'onGet', code: HTTP_STATUS_INTERNAL_SERVER_ERROR, res: null }} | ${'error'} | ${[{ type: types.REQUEST_AUTOCOMPLETE }, { type: types.RECEIVE_AUTOCOMPLETE_ERROR }, { type: types.RECEIVE_AUTOCOMPLETE_ERROR }]}
+ `('fetchAutocompleteOptions', ({ axiosMock, type, expectedMutations }) => {
+ describe(`on ${type}`, () => {
+ beforeEach(() => {
+ state = createState({});
+ mock = new MockAdapter(axios);
+ mock[axiosMock.method]().reply(axiosMock.code, axiosMock.res);
+ });
+ it(`should dispatch the correct mutations`, () => {
+ return testAction({
+ action: actions.fetchAutocompleteOptions,
+ state,
+ expectedMutations,
+ });
+ });
+ });
+ });
+
+ describe.each`
+ project | ref | fetchType | expectedPath
+ ${null} | ${null} | ${null} | ${`${MOCK_AUTOCOMPLETE_PATH}?term=${MOCK_SEARCH}`}
+ ${MOCK_PROJECT} | ${null} | ${'generic'} | ${`${MOCK_AUTOCOMPLETE_PATH}?term=${MOCK_SEARCH}&project_id=${MOCK_PROJECT.id}&filter=generic`}
+ ${null} | ${MOCK_PROJECT.id} | ${'generic'} | ${`${MOCK_AUTOCOMPLETE_PATH}?term=${MOCK_SEARCH}&project_ref=${MOCK_PROJECT.id}&filter=generic`}
+ ${MOCK_PROJECT} | ${MOCK_PROJECT.id} | ${'search'} | ${`${MOCK_AUTOCOMPLETE_PATH}?term=${MOCK_SEARCH}&project_id=${MOCK_PROJECT.id}&project_ref=${MOCK_PROJECT.id}&filter=search`}
+ `('autocompleteQuery', ({ project, ref, fetchType, expectedPath }) => {
+ describe(`when project is ${project?.name} and project ref is ${ref}`, () => {
+ beforeEach(() => {
+ state = createState({
+ search: MOCK_SEARCH,
+ searchContext: {
+ project,
+ ref,
+ },
+ });
+ });
+
+ it(`should return ${expectedPath}`, () => {
+ expect(actions.autocompleteQuery({ state, fetchType })).toBe(expectedPath);
+ });
+ });
+ });
+
+ describe('clearAutocomplete', () => {
+ beforeEach(() => {
+ state = createState({});
+ });
+
+ it('calls the CLEAR_AUTOCOMPLETE mutation', () => {
+ return testAction({
+ action: actions.clearAutocomplete,
+ state,
+ expectedMutations: [{ type: types.CLEAR_AUTOCOMPLETE }],
+ });
+ });
+ });
+
+ describe('setSearch', () => {
+ beforeEach(() => {
+ state = createState({});
+ });
+
+ it('calls the SET_SEARCH mutation', () => {
+ return testAction({
+ action: actions.setSearch,
+ payload: MOCK_SEARCH,
+ state,
+ expectedMutations: [{ type: types.SET_SEARCH, payload: MOCK_SEARCH }],
+ });
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/global_search/store/getters_spec.js b/spec/frontend/super_sidebar/components/global_search/store/getters_spec.js
new file mode 100644
index 00000000000..dca96da01a7
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/global_search/store/getters_spec.js
@@ -0,0 +1,333 @@
+import * as getters from '~/super_sidebar/components/global_search/store/getters';
+import initState from '~/super_sidebar/components/global_search/store/state';
+import {
+ MOCK_USERNAME,
+ MOCK_SEARCH_PATH,
+ MOCK_ISSUE_PATH,
+ MOCK_MR_PATH,
+ MOCK_AUTOCOMPLETE_PATH,
+ MOCK_SEARCH_CONTEXT,
+ MOCK_DEFAULT_SEARCH_OPTIONS,
+ MOCK_SCOPED_SEARCH_OPTIONS,
+ MOCK_SCOPED_SEARCH_OPTIONS_DEF,
+ MOCK_PROJECT,
+ MOCK_GROUP,
+ MOCK_ALL_PATH,
+ MOCK_SEARCH,
+ MOCK_AUTOCOMPLETE_OPTIONS,
+ MOCK_GROUPED_AUTOCOMPLETE_OPTIONS,
+ MOCK_SORTED_AUTOCOMPLETE_OPTIONS,
+} from '../mock_data';
+
+describe('Header Search Store Getters', () => {
+ let state;
+
+ const createState = (initialState) => {
+ state = initState({
+ searchPath: MOCK_SEARCH_PATH,
+ issuesPath: MOCK_ISSUE_PATH,
+ mrPath: MOCK_MR_PATH,
+ autocompletePath: MOCK_AUTOCOMPLETE_PATH,
+ searchContext: MOCK_SEARCH_CONTEXT,
+ ...initialState,
+ });
+ };
+
+ afterEach(() => {
+ state = null;
+ });
+
+ describe.each`
+ group | project | scope | forSnippets | codeSearch | ref | expectedPath
+ ${null} | ${null} | ${null} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar`}
+ ${null} | ${null} | ${null} | ${true} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&snippets=true`}
+ ${null} | ${null} | ${null} | ${false} | ${true} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&search_code=true`}
+ ${null} | ${null} | ${null} | ${false} | ${false} | ${'test-branch'} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&repository_ref=test-branch`}
+ ${MOCK_GROUP} | ${null} | ${null} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&group_id=${MOCK_GROUP.id}`}
+ ${null} | ${MOCK_PROJECT} | ${null} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&project_id=${MOCK_PROJECT.id}`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${null} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&project_id=${MOCK_PROJECT.id}&group_id=${MOCK_GROUP.id}`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${'issues'} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&project_id=${MOCK_PROJECT.id}&group_id=${MOCK_GROUP.id}&scope=issues`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${'issues'} | ${true} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&project_id=${MOCK_PROJECT.id}&group_id=${MOCK_GROUP.id}&scope=issues&snippets=true`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${'issues'} | ${true} | ${true} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&project_id=${MOCK_PROJECT.id}&group_id=${MOCK_GROUP.id}&scope=issues&snippets=true&search_code=true`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${'issues'} | ${true} | ${true} | ${'test-branch'} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&project_id=${MOCK_PROJECT.id}&group_id=${MOCK_GROUP.id}&scope=issues&snippets=true&search_code=true&repository_ref=test-branch`}
+ `('searchQuery', ({ group, project, scope, forSnippets, codeSearch, ref, expectedPath }) => {
+ describe(`when group is ${group?.name}, project is ${project?.name}, scope is ${scope}, for_snippets is ${forSnippets}, code_search is ${codeSearch}, and ref is ${ref}`, () => {
+ beforeEach(() => {
+ createState({
+ searchContext: {
+ group,
+ project,
+ scope,
+ for_snippets: forSnippets,
+ code_search: codeSearch,
+ ref,
+ },
+ });
+ state.search = MOCK_SEARCH;
+ });
+
+ it(`should return ${expectedPath}`, () => {
+ expect(getters.searchQuery(state)).toBe(expectedPath);
+ });
+ });
+ });
+
+ describe.each`
+ group | group_metadata | project | project_metadata | expectedPath
+ ${null} | ${null} | ${null} | ${null} | ${MOCK_ISSUE_PATH}
+ ${{ name: 'Test Group' }} | ${{ issues_path: 'group/path' }} | ${null} | ${null} | ${'group/path'}
+ ${{ name: 'Test Group' }} | ${{ issues_path: 'group/path' }} | ${{ name: 'Test Project' }} | ${{ issues_path: 'project/path' }} | ${'project/path'}
+ `('scopedIssuesPath', ({ group, group_metadata, project, project_metadata, expectedPath }) => {
+ describe(`when group is ${group?.name} and project is ${project?.name}`, () => {
+ beforeEach(() => {
+ createState({
+ searchContext: {
+ group,
+ group_metadata,
+ project,
+ project_metadata,
+ },
+ });
+ });
+
+ it(`should return ${expectedPath}`, () => {
+ expect(getters.scopedIssuesPath(state)).toBe(expectedPath);
+ });
+ });
+ });
+
+ describe.each`
+ group | group_metadata | project | project_metadata | expectedPath
+ ${null} | ${null} | ${null} | ${null} | ${MOCK_MR_PATH}
+ ${{ name: 'Test Group' }} | ${{ mr_path: 'group/path' }} | ${null} | ${null} | ${'group/path'}
+ ${{ name: 'Test Group' }} | ${{ mr_path: 'group/path' }} | ${{ name: 'Test Project' }} | ${{ mr_path: 'project/path' }} | ${'project/path'}
+ `('scopedMRPath', ({ group, group_metadata, project, project_metadata, expectedPath }) => {
+ describe(`when group is ${group?.name} and project is ${project?.name}`, () => {
+ beforeEach(() => {
+ createState({
+ searchContext: {
+ group,
+ group_metadata,
+ project,
+ project_metadata,
+ },
+ });
+ });
+
+ it(`should return ${expectedPath}`, () => {
+ expect(getters.scopedMRPath(state)).toBe(expectedPath);
+ });
+ });
+ });
+
+ describe.each`
+ group | project | scope | forSnippets | codeSearch | ref | expectedPath
+ ${null} | ${null} | ${null} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar`}
+ ${null} | ${null} | ${null} | ${true} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&snippets=true`}
+ ${null} | ${null} | ${null} | ${false} | ${true} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&search_code=true`}
+ ${null} | ${null} | ${null} | ${false} | ${false} | ${'test-branch'} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&repository_ref=test-branch`}
+ ${MOCK_GROUP} | ${null} | ${null} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&group_id=${MOCK_GROUP.id}`}
+ ${null} | ${MOCK_PROJECT} | ${null} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&project_id=${MOCK_PROJECT.id}`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${null} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&project_id=${MOCK_PROJECT.id}&group_id=${MOCK_GROUP.id}`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${'issues'} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&project_id=${MOCK_PROJECT.id}&group_id=${MOCK_GROUP.id}&scope=issues`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${'issues'} | ${true} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&project_id=${MOCK_PROJECT.id}&group_id=${MOCK_GROUP.id}&scope=issues&snippets=true`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${'issues'} | ${true} | ${true} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&project_id=${MOCK_PROJECT.id}&group_id=${MOCK_GROUP.id}&scope=issues&snippets=true&search_code=true`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${'issues'} | ${true} | ${true} | ${'test-branch'} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&project_id=${MOCK_PROJECT.id}&group_id=${MOCK_GROUP.id}&scope=issues&snippets=true&search_code=true&repository_ref=test-branch`}
+ `('projectUrl', ({ group, project, scope, forSnippets, codeSearch, ref, expectedPath }) => {
+ describe(`when group is ${group?.name}, project is ${project?.name}, scope is ${scope}, for_snippets is ${forSnippets}, code_search is ${codeSearch}, and ref is ${ref}`, () => {
+ beforeEach(() => {
+ createState({
+ searchContext: {
+ group,
+ project,
+ scope,
+ for_snippets: forSnippets,
+ code_search: codeSearch,
+ ref,
+ },
+ });
+ state.search = MOCK_SEARCH;
+ });
+
+ it(`should return ${expectedPath}`, () => {
+ expect(getters.projectUrl(state)).toBe(expectedPath);
+ });
+ });
+ });
+
+ describe.each`
+ group | project | scope | forSnippets | codeSearch | ref | expectedPath
+ ${null} | ${null} | ${null} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar`}
+ ${null} | ${null} | ${null} | ${true} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&snippets=true`}
+ ${null} | ${null} | ${null} | ${false} | ${true} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&search_code=true`}
+ ${null} | ${null} | ${null} | ${false} | ${false} | ${'test-branch'} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&repository_ref=test-branch`}
+ ${MOCK_GROUP} | ${null} | ${null} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&group_id=${MOCK_GROUP.id}`}
+ ${null} | ${MOCK_PROJECT} | ${null} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${null} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&group_id=${MOCK_GROUP.id}`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${'issues'} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&group_id=${MOCK_GROUP.id}&scope=issues`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${'issues'} | ${true} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&group_id=${MOCK_GROUP.id}&scope=issues&snippets=true`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${'issues'} | ${true} | ${true} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&group_id=${MOCK_GROUP.id}&scope=issues&snippets=true&search_code=true`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${'issues'} | ${true} | ${true} | ${'test-branch'} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&group_id=${MOCK_GROUP.id}&scope=issues&snippets=true&search_code=true&repository_ref=test-branch`}
+ `('groupUrl', ({ group, project, scope, forSnippets, codeSearch, ref, expectedPath }) => {
+ describe(`when group is ${group?.name}, project is ${project?.name}, scope is ${scope}, for_snippets is ${forSnippets}, code_search is ${codeSearch}, and ref is ${ref}`, () => {
+ beforeEach(() => {
+ createState({
+ searchContext: {
+ group,
+ project,
+ scope,
+ for_snippets: forSnippets,
+ code_search: codeSearch,
+ ref,
+ },
+ });
+ state.search = MOCK_SEARCH;
+ });
+
+ it(`should return ${expectedPath}`, () => {
+ expect(getters.groupUrl(state)).toBe(expectedPath);
+ });
+ });
+ });
+
+ describe.each`
+ group | project | scope | forSnippets | codeSearch | ref | expectedPath
+ ${null} | ${null} | ${null} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar`}
+ ${null} | ${null} | ${null} | ${true} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&snippets=true`}
+ ${null} | ${null} | ${null} | ${false} | ${true} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&search_code=true`}
+ ${null} | ${null} | ${null} | ${false} | ${false} | ${'test-branch'} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&repository_ref=test-branch`}
+ ${MOCK_GROUP} | ${null} | ${null} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar`}
+ ${null} | ${MOCK_PROJECT} | ${null} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${null} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${'issues'} | ${false} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&scope=issues`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${'issues'} | ${true} | ${false} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&scope=issues&snippets=true`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${'issues'} | ${true} | ${true} | ${null} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&scope=issues&snippets=true&search_code=true`}
+ ${MOCK_GROUP} | ${MOCK_PROJECT} | ${'issues'} | ${true} | ${true} | ${'test-branch'} | ${`${MOCK_SEARCH_PATH}?search=${MOCK_SEARCH}&nav_source=navbar&scope=issues&snippets=true&search_code=true&repository_ref=test-branch`}
+ `('allUrl', ({ group, project, scope, forSnippets, codeSearch, ref, expectedPath }) => {
+ describe(`when group is ${group?.name}, project is ${project?.name}, scope is ${scope}, for_snippets is ${forSnippets}, code_search is ${codeSearch}, and ref is ${ref}`, () => {
+ beforeEach(() => {
+ createState({
+ searchContext: {
+ group,
+ project,
+ scope,
+ for_snippets: forSnippets,
+ code_search: codeSearch,
+ ref,
+ },
+ });
+ state.search = MOCK_SEARCH;
+ });
+
+ it(`should return ${expectedPath}`, () => {
+ expect(getters.allUrl(state)).toBe(expectedPath);
+ });
+ });
+ });
+
+ describe('defaultSearchOptions', () => {
+ const mockGetters = {
+ scopedIssuesPath: MOCK_ISSUE_PATH,
+ scopedMRPath: MOCK_MR_PATH,
+ };
+
+ beforeEach(() => {
+ createState();
+ window.gon.current_username = MOCK_USERNAME;
+ });
+
+ it('returns the correct array', () => {
+ expect(getters.defaultSearchOptions(state, mockGetters)).toStrictEqual(
+ MOCK_DEFAULT_SEARCH_OPTIONS,
+ );
+ });
+
+ it('returns the correct array if issues path is false', () => {
+ mockGetters.scopedIssuesPath = undefined;
+ expect(getters.defaultSearchOptions(state, mockGetters)).toStrictEqual(
+ MOCK_DEFAULT_SEARCH_OPTIONS.slice(2, MOCK_DEFAULT_SEARCH_OPTIONS.length),
+ );
+ });
+ });
+
+ describe('scopedSearchOptions', () => {
+ const mockGetters = {
+ projectUrl: MOCK_PROJECT.path,
+ groupUrl: MOCK_GROUP.path,
+ allUrl: MOCK_ALL_PATH,
+ };
+
+ beforeEach(() => {
+ createState({
+ searchContext: {
+ project: MOCK_PROJECT,
+ group: MOCK_GROUP,
+ },
+ });
+ });
+
+ it('returns the correct array', () => {
+ expect(getters.scopedSearchOptions(state, mockGetters)).toStrictEqual(
+ MOCK_SCOPED_SEARCH_OPTIONS_DEF,
+ );
+ });
+ });
+
+ describe('autocompleteGroupedSearchOptions', () => {
+ beforeEach(() => {
+ createState();
+ state.autocompleteOptions = MOCK_AUTOCOMPLETE_OPTIONS;
+ });
+
+ it('returns the correct grouped array', () => {
+ expect(getters.autocompleteGroupedSearchOptions(state)).toStrictEqual(
+ MOCK_GROUPED_AUTOCOMPLETE_OPTIONS,
+ );
+ });
+ });
+
+ describe.each`
+ search | defaultSearchOptions | scopedSearchOptions | autocompleteGroupedSearchOptions | expectedArray
+ ${null} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${MOCK_GROUPED_AUTOCOMPLETE_OPTIONS} | ${MOCK_DEFAULT_SEARCH_OPTIONS}
+ ${MOCK_SEARCH} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${[]} | ${MOCK_SCOPED_SEARCH_OPTIONS}
+ ${MOCK_SEARCH} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${[]} | ${MOCK_GROUPED_AUTOCOMPLETE_OPTIONS} | ${MOCK_SORTED_AUTOCOMPLETE_OPTIONS}
+ ${MOCK_SEARCH} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${MOCK_GROUPED_AUTOCOMPLETE_OPTIONS} | ${MOCK_SCOPED_SEARCH_OPTIONS.concat(MOCK_SORTED_AUTOCOMPLETE_OPTIONS)}
+ ${1} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${[]} | ${[]} | ${[]}
+ ${'('} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${[]} | ${[]} | ${[]}
+ ${'t'} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${MOCK_GROUPED_AUTOCOMPLETE_OPTIONS} | ${MOCK_SORTED_AUTOCOMPLETE_OPTIONS}
+ ${'te'} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${MOCK_GROUPED_AUTOCOMPLETE_OPTIONS} | ${MOCK_SORTED_AUTOCOMPLETE_OPTIONS}
+ ${'tes'} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${MOCK_GROUPED_AUTOCOMPLETE_OPTIONS} | ${MOCK_SCOPED_SEARCH_OPTIONS.concat(MOCK_SORTED_AUTOCOMPLETE_OPTIONS)}
+ `(
+ 'searchOptions',
+ ({
+ search,
+ defaultSearchOptions,
+ scopedSearchOptions,
+ autocompleteGroupedSearchOptions,
+ expectedArray,
+ }) => {
+ describe(`when search is ${search} and the defaultSearchOptions${
+ defaultSearchOptions.length ? '' : ' do not'
+ } exist, scopedSearchOptions${
+ scopedSearchOptions.length ? '' : ' do not'
+ } exist, and autocompleteGroupedSearchOptions${
+ autocompleteGroupedSearchOptions.length ? '' : ' do not'
+ } exist`, () => {
+ const mockGetters = {
+ defaultSearchOptions,
+ scopedSearchOptions,
+ autocompleteGroupedSearchOptions,
+ };
+
+ beforeEach(() => {
+ createState();
+ state.search = search;
+ });
+
+ it(`should return the correct combined array`, () => {
+ expect(getters.searchOptions(state, mockGetters)).toStrictEqual(expectedArray);
+ });
+ });
+ },
+ );
+});
diff --git a/spec/frontend/super_sidebar/components/global_search/store/mutations_spec.js b/spec/frontend/super_sidebar/components/global_search/store/mutations_spec.js
new file mode 100644
index 00000000000..d2dc484e825
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/global_search/store/mutations_spec.js
@@ -0,0 +1,63 @@
+import * as types from '~/super_sidebar/components/global_search/store/mutation_types';
+import mutations from '~/super_sidebar/components/global_search/store/mutations';
+import createState from '~/super_sidebar/components/global_search/store/state';
+import {
+ MOCK_SEARCH,
+ MOCK_AUTOCOMPLETE_OPTIONS_RES,
+ MOCK_AUTOCOMPLETE_OPTIONS,
+} from '../mock_data';
+
+describe('Header Search Store Mutations', () => {
+ let state;
+
+ beforeEach(() => {
+ state = createState({});
+ });
+
+ describe('REQUEST_AUTOCOMPLETE', () => {
+ it('sets loading to true and empties autocompleteOptions array', () => {
+ mutations[types.REQUEST_AUTOCOMPLETE](state);
+
+ expect(state.loading).toBe(true);
+ expect(state.autocompleteOptions).toStrictEqual([]);
+ expect(state.autocompleteError).toBe(false);
+ });
+ });
+
+ describe('RECEIVE_AUTOCOMPLETE_SUCCESS', () => {
+ it('sets loading to false and then formats and sets the autocompleteOptions array', () => {
+ mutations[types.RECEIVE_AUTOCOMPLETE_SUCCESS](state, MOCK_AUTOCOMPLETE_OPTIONS_RES);
+
+ expect(state.loading).toBe(false);
+ expect(state.autocompleteOptions).toStrictEqual(MOCK_AUTOCOMPLETE_OPTIONS);
+ expect(state.autocompleteError).toBe(false);
+ });
+ });
+
+ describe('RECEIVE_AUTOCOMPLETE_ERROR', () => {
+ it('sets loading to false and empties autocompleteOptions array', () => {
+ mutations[types.RECEIVE_AUTOCOMPLETE_ERROR](state);
+
+ expect(state.loading).toBe(false);
+ expect(state.autocompleteOptions).toStrictEqual([]);
+ expect(state.autocompleteError).toBe(true);
+ });
+ });
+
+ describe('CLEAR_AUTOCOMPLETE', () => {
+ it('empties autocompleteOptions array', () => {
+ mutations[types.CLEAR_AUTOCOMPLETE](state);
+
+ expect(state.autocompleteOptions).toStrictEqual([]);
+ expect(state.autocompleteError).toBe(false);
+ });
+ });
+
+ describe('SET_SEARCH', () => {
+ it('sets search to value', () => {
+ mutations[types.SET_SEARCH](state, MOCK_SEARCH);
+
+ expect(state.search).toBe(MOCK_SEARCH);
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/groups_list_spec.js b/spec/frontend/super_sidebar/components/groups_list_spec.js
new file mode 100644
index 00000000000..6aee895f611
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/groups_list_spec.js
@@ -0,0 +1,87 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { s__ } from '~/locale';
+import GroupsList from '~/super_sidebar/components/groups_list.vue';
+import SearchResults from '~/super_sidebar/components/search_results.vue';
+import FrequentItemsList from '~/super_sidebar/components/frequent_items_list.vue';
+import NavItem from '~/super_sidebar/components/nav_item.vue';
+import { MAX_FREQUENT_GROUPS_COUNT } from '~/super_sidebar/constants';
+
+const username = 'root';
+const viewAllLink = '/path/to/groups';
+const storageKey = `${username}/frequent-groups`;
+
+describe('GroupsList component', () => {
+ let wrapper;
+
+ const findSearchResults = () => wrapper.findComponent(SearchResults);
+ const findFrequentItemsList = () => wrapper.findComponent(FrequentItemsList);
+ const findViewAllLink = () => wrapper.findComponent(NavItem);
+
+ const itRendersViewAllItem = () => {
+ it('renders the "View all..." item', () => {
+ expect(findViewAllLink().props('item')).toEqual({
+ icon: 'group',
+ link: viewAllLink,
+ title: s__('Navigation|View all groups'),
+ });
+ });
+ };
+
+ const createWrapper = (props = {}) => {
+ wrapper = shallowMountExtended(GroupsList, {
+ propsData: {
+ username,
+ viewAllLink,
+ ...props,
+ },
+ });
+ };
+
+ describe('when displaying search results', () => {
+ const searchResults = ['A search result'];
+
+ beforeEach(() => {
+ createWrapper({
+ isSearch: true,
+ searchResults,
+ });
+ });
+
+ it('renders the search results component', () => {
+ expect(findSearchResults().exists()).toBe(true);
+ expect(findFrequentItemsList().exists()).toBe(false);
+ });
+
+ it('passes the correct props to the search results component', () => {
+ expect(findSearchResults().props()).toEqual({
+ title: s__('Navigation|Groups'),
+ noResultsText: s__('Navigation|No group matches found'),
+ searchResults,
+ });
+ });
+
+ itRendersViewAllItem();
+ });
+
+ describe('when displaying frequent groups', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('renders the frequent items list', () => {
+ expect(findFrequentItemsList().exists()).toBe(true);
+ expect(findSearchResults().exists()).toBe(false);
+ });
+
+ it('passes the correct props to the frequent items list', () => {
+ expect(findFrequentItemsList().props()).toEqual({
+ title: s__('Navigation|Frequent groups'),
+ storageKey,
+ maxItems: MAX_FREQUENT_GROUPS_COUNT,
+ pristineText: s__('Navigation|Groups you visit often will appear here.'),
+ });
+ });
+
+ itRendersViewAllItem();
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/help_center_spec.js b/spec/frontend/super_sidebar/components/help_center_spec.js
index bc847a3e159..1d072c0ba3c 100644
--- a/spec/frontend/super_sidebar/components/help_center_spec.js
+++ b/spec/frontend/super_sidebar/components/help_center_spec.js
@@ -68,18 +68,23 @@ describe('HelpCenter component', () => {
});
describe('showKeyboardShortcuts', () => {
+ let button;
+
beforeEach(() => {
jest.spyOn(wrapper.vm.$refs.dropdown, 'close');
- window.toggleShortcutsHelp = jest.fn();
- findButton('Keyboard shortcuts ?').click();
+
+ button = findButton('Keyboard shortcuts ?');
});
it('closes the dropdown', () => {
+ button.click();
expect(wrapper.vm.$refs.dropdown.close).toHaveBeenCalled();
});
it('shows the keyboard shortcuts modal', () => {
- expect(window.toggleShortcutsHelp).toHaveBeenCalled();
+ // This relies on the event delegation set up by the Shortcuts class in
+ // ~/behaviors/shortcuts/shortcuts.js.
+ expect(button.classList.contains('js-shortcuts-modal-trigger')).toBe(true);
});
});
diff --git a/spec/frontend/super_sidebar/components/items_list_spec.js b/spec/frontend/super_sidebar/components/items_list_spec.js
new file mode 100644
index 00000000000..8e00984f500
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/items_list_spec.js
@@ -0,0 +1,63 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import ItemsList from '~/super_sidebar/components/items_list.vue';
+import NavItem from '~/super_sidebar/components/nav_item.vue';
+import { cachedFrequentProjects } from '../mock_data';
+
+const mockItems = JSON.parse(cachedFrequentProjects);
+const [firstMockedProject] = mockItems;
+
+describe('ItemsList component', () => {
+ let wrapper;
+
+ const findNavItems = () => wrapper.findAllComponents(NavItem);
+
+ const createWrapper = ({ props = {}, slots = {} } = {}) => {
+ wrapper = shallowMountExtended(ItemsList, {
+ propsData: {
+ ...props,
+ },
+ slots,
+ });
+ };
+
+ it('does not render nav items when there are no items', () => {
+ createWrapper();
+
+ expect(findNavItems().length).toBe(0);
+ });
+
+ it('renders one nav item per item', () => {
+ createWrapper({
+ props: {
+ items: mockItems,
+ },
+ });
+
+ expect(findNavItems().length).not.toBe(0);
+ expect(findNavItems().length).toBe(mockItems.length);
+ });
+
+ it('passes the correct props to the nav items', () => {
+ createWrapper({
+ props: {
+ items: mockItems,
+ },
+ });
+ const firstNavItem = findNavItems().at(0);
+
+ expect(firstNavItem.props('item')).toEqual(firstMockedProject);
+ });
+
+ it('renders the `view-all-items` slot', () => {
+ const testId = 'view-all-items';
+ createWrapper({
+ slots: {
+ 'view-all-items': {
+ template: `<div data-testid="${testId}" />`,
+ },
+ },
+ });
+
+ expect(wrapper.findByTestId(testId).exists()).toBe(true);
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/nav_item_spec.js b/spec/frontend/super_sidebar/components/nav_item_spec.js
new file mode 100644
index 00000000000..22989c1a5f9
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/nav_item_spec.js
@@ -0,0 +1,49 @@
+import { GlBadge } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import NavItem from '~/super_sidebar/components/nav_item.vue';
+
+describe('NavItem component', () => {
+ let wrapper;
+
+ const findLink = () => wrapper.findByTestId('nav-item-link');
+ const findPill = () => wrapper.findComponent(GlBadge);
+ const createWrapper = (item, props = {}) => {
+ wrapper = shallowMountExtended(NavItem, {
+ propsData: {
+ item,
+ ...props,
+ },
+ });
+ };
+
+ describe('pills', () => {
+ it.each([0, 5, 3.4, 'foo', '10%'])('item with pill_data `%p` renders a pill', (pillCount) => {
+ createWrapper({ title: 'Foo', pill_count: pillCount });
+
+ expect(findPill().text()).toEqual(pillCount.toString());
+ });
+
+ it.each([null, undefined, false, true, '', NaN, Number.POSITIVE_INFINITY])(
+ 'item with pill_data `%p` renders no pill',
+ (pillCount) => {
+ createWrapper({ title: 'Foo', pill_count: pillCount });
+
+ expect(findPill().exists()).toEqual(false);
+ },
+ );
+ });
+
+ it('applies custom link classes', () => {
+ const customClass = 'customClass';
+ createWrapper(
+ { title: 'Foo' },
+ {
+ linkClasses: {
+ [customClass]: true,
+ },
+ },
+ );
+
+ expect(findLink().attributes('class')).toContain(customClass);
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/projects_list_spec.js b/spec/frontend/super_sidebar/components/projects_list_spec.js
new file mode 100644
index 00000000000..cdc003b14e0
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/projects_list_spec.js
@@ -0,0 +1,82 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { s__ } from '~/locale';
+import ProjectsList from '~/super_sidebar/components/projects_list.vue';
+import SearchResults from '~/super_sidebar/components/search_results.vue';
+import FrequentItemsList from '~/super_sidebar/components/frequent_items_list.vue';
+import NavItem from '~/super_sidebar/components/nav_item.vue';
+import { MAX_FREQUENT_PROJECTS_COUNT } from '~/super_sidebar/constants';
+
+const username = 'root';
+const viewAllLink = '/path/to/projects';
+const storageKey = `${username}/frequent-projects`;
+
+describe('ProjectsList component', () => {
+ let wrapper;
+
+ const findSearchResults = () => wrapper.findComponent(SearchResults);
+ const findFrequentItemsList = () => wrapper.findComponent(FrequentItemsList);
+ const findViewAllLink = () => wrapper.findComponent(NavItem);
+
+ const itRendersViewAllItem = () => {
+ it('renders the "View all..." item', () => {
+ expect(findViewAllLink().props('item')).toEqual({
+ icon: 'project',
+ link: viewAllLink,
+ title: s__('Navigation|View all projects'),
+ });
+ });
+ };
+
+ const createWrapper = (props = {}) => {
+ wrapper = shallowMountExtended(ProjectsList, {
+ propsData: {
+ username,
+ viewAllLink,
+ ...props,
+ },
+ });
+ };
+
+ describe('when displaying search results', () => {
+ const searchResults = ['A search result'];
+
+ beforeEach(() => {
+ createWrapper({
+ isSearch: true,
+ searchResults,
+ });
+ });
+
+ it('renders the search results component', () => {
+ expect(findSearchResults().exists()).toBe(true);
+ expect(findFrequentItemsList().exists()).toBe(false);
+ });
+
+ it('passes the correct props to the search results component', () => {
+ expect(findSearchResults().props()).toEqual({
+ title: s__('Navigation|Projects'),
+ noResultsText: s__('Navigation|No project matches found'),
+ searchResults,
+ });
+ });
+
+ itRendersViewAllItem();
+ });
+
+ describe('when displaying frequent projects', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('passes the correct props to the frequent items list', () => {
+ expect(findFrequentItemsList().props()).toEqual({
+ title: s__('Navigation|Frequent projects'),
+ storageKey,
+ maxItems: MAX_FREQUENT_PROJECTS_COUNT,
+ pristineText: s__('Navigation|Projects you visit often will appear here.'),
+ });
+ });
+
+ itRendersViewAllItem();
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/search_results_spec.js b/spec/frontend/super_sidebar/components/search_results_spec.js
new file mode 100644
index 00000000000..dd48935c138
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/search_results_spec.js
@@ -0,0 +1,57 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { s__ } from '~/locale';
+import SearchResults from '~/super_sidebar/components/search_results.vue';
+import ItemsList from '~/super_sidebar/components/items_list.vue';
+
+const title = s__('Navigation|PROJECTS');
+const noResultsText = s__('Navigation|No project matches found');
+
+describe('SearchResults component', () => {
+ let wrapper;
+
+ const findListTitle = () => wrapper.findByTestId('list-title');
+ const findItemsList = () => wrapper.findComponent(ItemsList);
+ const findEmptyText = () => wrapper.findByTestId('empty-text');
+
+ const createWrapper = ({ props = {} } = {}) => {
+ wrapper = shallowMountExtended(SearchResults, {
+ propsData: {
+ title,
+ noResultsText,
+ ...props,
+ },
+ });
+ };
+
+ describe('default state', () => {
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it("renders the list's title", () => {
+ expect(findListTitle().text()).toBe(title);
+ });
+
+ it('renders the empty text', () => {
+ expect(findEmptyText().exists()).toBe(true);
+ expect(findEmptyText().text()).toBe(noResultsText);
+ });
+ });
+
+ describe('when displaying search results', () => {
+ it('shows search results', () => {
+ const searchResults = [{ id: 1 }];
+ createWrapper({ props: { isSearch: true, searchResults } });
+
+ expect(findItemsList().props('items')[0]).toEqual(searchResults[0]);
+ });
+
+ it('shows the no results text if search results are empty', () => {
+ const searchResults = [];
+ createWrapper({ props: { isSearch: true, searchResults } });
+
+ expect(findItemsList().props('items').length).toEqual(0);
+ expect(findEmptyText().text()).toBe(noResultsText);
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/sidebar_portal_spec.js b/spec/frontend/super_sidebar/components/sidebar_portal_spec.js
new file mode 100644
index 00000000000..3ef1cb7e692
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/sidebar_portal_spec.js
@@ -0,0 +1,68 @@
+import { nextTick } from 'vue';
+import { mount } from '@vue/test-utils';
+import SidebarPortal from '~/super_sidebar/components/sidebar_portal.vue';
+import SidebarPortalTarget from '~/super_sidebar/components/sidebar_portal_target.vue';
+
+describe('SidebarPortal', () => {
+ let targetWrapper;
+
+ const Target = {
+ components: { SidebarPortalTarget },
+ props: ['show'],
+ template: '<sidebar-portal-target v-if="show" />',
+ };
+
+ const Source = {
+ components: { SidebarPortal },
+ template: '<sidebar-portal><br data-testid="test"></sidebar-portal>',
+ };
+
+ const mountSource = () => {
+ mount(Source);
+ };
+
+ const mountTarget = ({ show = true } = {}) => {
+ targetWrapper = mount(Target, {
+ propsData: { show },
+ attachTo: document.body,
+ });
+ };
+
+ const findTestContent = () => targetWrapper.find('[data-testid="test"]');
+
+ it('renders content into the target', async () => {
+ mountTarget();
+ await nextTick();
+
+ mountSource();
+ await nextTick();
+
+ expect(findTestContent().exists()).toBe(true);
+ });
+
+ it('waits for target to be available before rendering', async () => {
+ mountSource();
+ await nextTick();
+
+ mountTarget();
+ await nextTick();
+
+ expect(findTestContent().exists()).toBe(true);
+ });
+
+ it('supports conditional rendering of target', async () => {
+ mountTarget({ show: false });
+ await nextTick();
+
+ mountSource();
+ await nextTick();
+
+ expect(findTestContent().exists()).toBe(false);
+
+ await targetWrapper.setProps({ show: true });
+ expect(findTestContent().exists()).toBe(true);
+
+ await targetWrapper.setProps({ show: false });
+ expect(findTestContent().exists()).toBe(false);
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/super_sidebar_spec.js b/spec/frontend/super_sidebar/components/super_sidebar_spec.js
index 45fc30c08f0..32921da23aa 100644
--- a/spec/frontend/super_sidebar/components/super_sidebar_spec.js
+++ b/spec/frontend/super_sidebar/components/super_sidebar_spec.js
@@ -2,13 +2,21 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import SuperSidebar from '~/super_sidebar/components/super_sidebar.vue';
import HelpCenter from '~/super_sidebar/components/help_center.vue';
import UserBar from '~/super_sidebar/components/user_bar.vue';
+import SidebarPortalTarget from '~/super_sidebar/components/sidebar_portal_target.vue';
+import { isCollapsed } from '~/super_sidebar/super_sidebar_collapsed_state_manager';
import { sidebarData } from '../mock_data';
+jest.mock('~/super_sidebar/super_sidebar_collapsed_state_manager', () => ({
+ isCollapsed: jest.fn(),
+}));
+
describe('SuperSidebar component', () => {
let wrapper;
+ const findSidebar = () => wrapper.find('.super-sidebar');
const findUserBar = () => wrapper.findComponent(UserBar);
const findHelpCenter = () => wrapper.findComponent(HelpCenter);
+ const findSidebarPortalTarget = () => wrapper.findComponent(SidebarPortalTarget);
const createWrapper = (props = {}) => {
wrapper = shallowMountExtended(SuperSidebar, {
@@ -20,16 +28,33 @@ describe('SuperSidebar component', () => {
};
describe('default', () => {
- beforeEach(() => {
+ it('add aria-hidden and inert attributes when collapsed', () => {
+ isCollapsed.mockReturnValue(true);
createWrapper();
+ expect(findSidebar().attributes('aria-hidden')).toBe('true');
+ expect(findSidebar().attributes('inert')).toBe('inert');
+ });
+
+ it('does not add aria-hidden and inert attributes when expanded', () => {
+ isCollapsed.mockReturnValue(false);
+ createWrapper();
+ expect(findSidebar().attributes('aria-hidden')).toBe('false');
+ expect(findSidebar().attributes('inert')).toBe(undefined);
});
it('renders UserBar with sidebarData', () => {
+ createWrapper();
expect(findUserBar().props('sidebarData')).toBe(sidebarData);
});
it('renders HelpCenter with sidebarData', () => {
+ createWrapper();
expect(findHelpCenter().props('sidebarData')).toBe(sidebarData);
});
+
+ it('renders SidebarPortalTarget', () => {
+ createWrapper();
+ expect(findSidebarPortalTarget().exists()).toBe(true);
+ });
});
});
diff --git a/spec/frontend/super_sidebar/components/user_bar_spec.js b/spec/frontend/super_sidebar/components/user_bar_spec.js
index eceb792c3db..ae15dd55644 100644
--- a/spec/frontend/super_sidebar/components/user_bar_spec.js
+++ b/spec/frontend/super_sidebar/components/user_bar_spec.js
@@ -1,3 +1,4 @@
+import { GlBadge } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { __ } from '~/locale';
import CreateMenu from '~/super_sidebar/components/create_menu.vue';
@@ -12,12 +13,12 @@ describe('UserBar component', () => {
const findCreateMenu = () => wrapper.findComponent(CreateMenu);
const findCounter = (at) => wrapper.findAllComponents(Counter).at(at);
const findMergeRequestMenu = () => wrapper.findComponent(MergeRequestMenu);
+ const findBrandLogo = () => wrapper.findByTestId('brand-header-custom-logo');
- const createWrapper = (props = {}) => {
+ const createWrapper = (extraSidebarData = {}) => {
wrapper = shallowMountExtended(UserBar, {
propsData: {
- sidebarData,
- ...props,
+ sidebarData: { ...sidebarData, ...extraSidebarData },
},
provide: {
rootPath: '/',
@@ -55,5 +56,29 @@ describe('UserBar component', () => {
expect(findCounter(2).props('href')).toBe('/dashboard/todos');
expect(findCounter(2).props('label')).toBe(__('To-Do list'));
});
+
+ it('renders branding logo', () => {
+ expect(findBrandLogo().exists()).toBe(true);
+ expect(findBrandLogo().attributes('src')).toBe(sidebarData.logo_url);
+ });
+ });
+
+ describe('GitLab Next badge', () => {
+ describe('when on canary', () => {
+ it('should render a badge to switch off GitLab Next', () => {
+ createWrapper({ gitlab_com_and_canary: true });
+ const badge = wrapper.findComponent(GlBadge);
+ expect(badge.text()).toBe('Next');
+ expect(badge.attributes('href')).toBe(sidebarData.canary_toggle_com_url);
+ });
+ });
+
+ describe('when not on canary', () => {
+ it('should not render the GitLab Next badge', () => {
+ createWrapper({ gitlab_com_and_canary: false });
+ const badge = wrapper.findComponent(GlBadge);
+ expect(badge.exists()).toBe(false);
+ });
+ });
});
});
diff --git a/spec/frontend/super_sidebar/components/user_menu_spec.js b/spec/frontend/super_sidebar/components/user_menu_spec.js
new file mode 100644
index 00000000000..b6231e03722
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/user_menu_spec.js
@@ -0,0 +1,375 @@
+import { GlAvatar, GlDisclosureDropdown } from '@gitlab/ui';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import UserMenu from '~/super_sidebar/components/user_menu.vue';
+import UserNameGroup from '~/super_sidebar/components/user_name_group.vue';
+import NewNavToggle from '~/nav/components/new_nav_toggle.vue';
+import invalidUrl from '~/lib/utils/invalid_url';
+import { mockTracking } from 'helpers/tracking_helper';
+import PersistentUserCallout from '~/persistent_user_callout';
+import { userMenuMockData, userMenuMockStatus, userMenuMockPipelineMinutes } from '../mock_data';
+
+describe('UserMenu component', () => {
+ let wrapper;
+ let trackingSpy;
+
+ const GlEmoji = { template: '<img/>' };
+ const toggleNewNavEndpoint = invalidUrl;
+ const showDropdown = () => wrapper.findComponent(GlDisclosureDropdown).vm.$emit('shown');
+
+ const createWrapper = (userDataChanges = {}) => {
+ wrapper = mountExtended(UserMenu, {
+ propsData: {
+ data: {
+ ...userMenuMockData,
+ ...userDataChanges,
+ },
+ },
+ stubs: {
+ GlEmoji,
+ GlAvatar: true,
+ },
+ provide: {
+ toggleNewNavEndpoint,
+ },
+ });
+
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ };
+
+ describe('Toggle button', () => {
+ let toggle;
+
+ beforeEach(() => {
+ createWrapper();
+ toggle = wrapper.findByTestId('base-dropdown-toggle');
+ });
+
+ it('renders User Avatar in a toggle', () => {
+ const avatar = toggle.findComponent(GlAvatar);
+ expect(avatar.exists()).toBe(true);
+ expect(avatar.props()).toMatchObject({
+ entityName: userMenuMockData.name,
+ src: userMenuMockData.avatar_url,
+ });
+ });
+
+ it('renders screen reader text', () => {
+ expect(toggle.find('.gl-sr-only').text()).toBe(`${userMenuMockData.name} user’s menu`);
+ });
+ });
+
+ describe('User Menu Group', () => {
+ it('renders and passes data to it', () => {
+ createWrapper();
+ const userNameGroup = wrapper.findComponent(UserNameGroup);
+ expect(userNameGroup.exists()).toBe(true);
+ expect(userNameGroup.props('user')).toEqual(userMenuMockData);
+ });
+ });
+
+ describe('User status item', () => {
+ let item;
+
+ const setItem = ({ can_update, busy, customized } = {}) => {
+ createWrapper({ status: { ...userMenuMockStatus, can_update, busy, customized } });
+ item = wrapper.findByTestId('status-item');
+ };
+
+ describe('When user cannot update the status', () => {
+ it('does not render the status menu item', () => {
+ setItem();
+ expect(item.exists()).toBe(false);
+ });
+ });
+
+ describe('When user can update the status', () => {
+ it('renders the status menu item', () => {
+ setItem({ can_update: true });
+ expect(item.exists()).toBe(true);
+ });
+
+ it('should set the CSS class for triggering status update modal', () => {
+ setItem({ can_update: true });
+ expect(item.find('.js-set-status-modal-trigger').exists()).toBe(true);
+ });
+
+ describe('renders correct label', () => {
+ it.each`
+ busy | customized | label
+ ${false} | ${false} | ${'Set status'}
+ ${false} | ${true} | ${'Edit status'}
+ ${true} | ${false} | ${'Edit status'}
+ ${true} | ${true} | ${'Edit status'}
+ `(
+ 'when busy is "$busy" and customized is "$customized" the label is "$label"',
+ ({ busy, customized, label }) => {
+ setItem({ can_update: true, busy, customized });
+ expect(item.text()).toBe(label);
+ },
+ );
+ });
+
+ describe('Status update modal wrapper', () => {
+ const findModalWrapper = () => wrapper.find('.js-set-status-modal-wrapper');
+
+ it('renders the modal wrapper', () => {
+ setItem({ can_update: true });
+ expect(findModalWrapper().exists()).toBe(true);
+ });
+
+ it('sets default data attributes when status is not customized', () => {
+ setItem({ can_update: true });
+ expect(findModalWrapper().attributes()).toMatchObject({
+ 'data-current-emoji': '',
+ 'data-current-message': '',
+ 'data-default-emoji': 'speech_balloon',
+ });
+ });
+
+ it('sets user status as data attributes when status is customized', () => {
+ setItem({ can_update: true, customized: true });
+ expect(findModalWrapper().attributes()).toMatchObject({
+ 'data-current-emoji': userMenuMockStatus.emoji,
+ 'data-current-message': userMenuMockStatus.message,
+ 'data-current-availability': userMenuMockStatus.availability,
+ 'data-current-clear-status-after': userMenuMockStatus.clear_after,
+ });
+ });
+ });
+ });
+ });
+
+ describe('Start Ultimate trial item', () => {
+ let item;
+
+ const setItem = ({ has_start_trial } = {}) => {
+ createWrapper({ trial: { has_start_trial } });
+ item = wrapper.findByTestId('start-trial-item');
+ };
+
+ describe('When Ultimate trial is not suggested for the user', () => {
+ it('does not render the start trial menu item', () => {
+ setItem();
+ expect(item.exists()).toBe(false);
+ });
+ });
+
+ describe('When Ultimate trial can be suggested for the user', () => {
+ it('does render the start trial menu item', () => {
+ setItem({ has_start_trial: true });
+ expect(item.exists()).toBe(true);
+ });
+ });
+ });
+
+ describe('Buy Pipeline Minutes item', () => {
+ let item;
+
+ const setItem = ({
+ show_buy_pipeline_minutes,
+ show_with_subtext,
+ show_notification_dot,
+ } = {}) => {
+ createWrapper({
+ pipeline_minutes: {
+ ...userMenuMockPipelineMinutes,
+ show_buy_pipeline_minutes,
+ show_with_subtext,
+ show_notification_dot,
+ },
+ });
+ item = wrapper.findByTestId('buy-pipeline-minutes-item');
+ };
+
+ describe('When does NOT meet the condition to buy CI minutes', () => {
+ beforeEach(() => {
+ setItem();
+ });
+
+ it('does NOT render the buy pipeline minutes item', () => {
+ expect(item.exists()).toBe(false);
+ });
+
+ it('does not track the Sentry event', () => {
+ showDropdown();
+ expect(trackingSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('When does meet the condition to buy CI minutes', () => {
+ it('does render the menu item', () => {
+ setItem({ show_buy_pipeline_minutes: true });
+ expect(item.exists()).toBe(true);
+ });
+
+ it('tracks the Sentry event', () => {
+ setItem({ show_buy_pipeline_minutes: true });
+ showDropdown();
+ expect(trackingSpy).toHaveBeenCalledWith(
+ undefined,
+ userMenuMockPipelineMinutes.tracking_attrs['track-action'],
+ {
+ label: userMenuMockPipelineMinutes.tracking_attrs['track-label'],
+ property: userMenuMockPipelineMinutes.tracking_attrs['track-property'],
+ },
+ );
+ });
+
+ describe('Callout & notification dot', () => {
+ let spyFactory;
+
+ beforeEach(() => {
+ spyFactory = jest.spyOn(PersistentUserCallout, 'factory');
+ });
+
+ describe('When `show_notification_dot` is `false`', () => {
+ beforeEach(() => {
+ setItem({ show_buy_pipeline_minutes: true, show_notification_dot: false });
+ showDropdown();
+ });
+
+ it('does not set callout attributes', () => {
+ expect(item.attributes()).not.toEqual(
+ expect.objectContaining({
+ 'data-feature-id': userMenuMockPipelineMinutes.callout_attrs.feature_id,
+ 'data-dismiss-endpoint': userMenuMockPipelineMinutes.callout_attrs.dismiss_endpoint,
+ }),
+ );
+ });
+
+ it('does not initialize the Persistent Callout', () => {
+ expect(spyFactory).not.toHaveBeenCalled();
+ });
+
+ it('does not render notification dot', () => {
+ expect(wrapper.findByTestId('buy-pipeline-minutes-notification-dot').exists()).toBe(
+ false,
+ );
+ });
+ });
+
+ describe('When `show_notification_dot` is `true`', () => {
+ beforeEach(() => {
+ setItem({ show_buy_pipeline_minutes: true, show_notification_dot: true });
+ showDropdown();
+ });
+
+ it('sets the callout data attributes', () => {
+ expect(item.attributes()).toEqual(
+ expect.objectContaining({
+ 'data-feature-id': userMenuMockPipelineMinutes.callout_attrs.feature_id,
+ 'data-dismiss-endpoint': userMenuMockPipelineMinutes.callout_attrs.dismiss_endpoint,
+ }),
+ );
+ });
+
+ it('initializes the Persistent Callout', () => {
+ expect(spyFactory).toHaveBeenCalled();
+ });
+
+ it('renders notification dot', () => {
+ expect(wrapper.findByTestId('buy-pipeline-minutes-notification-dot').exists()).toBe(
+ true,
+ );
+ });
+ });
+ });
+
+ describe('Warning message', () => {
+ it('does not display the warning message when `show_with_subtext` is `false`', () => {
+ setItem({ show_buy_pipeline_minutes: true });
+
+ expect(item.text()).not.toContain(UserMenu.i18n.oneOfGroupsRunningOutOfPipelineMinutes);
+ });
+
+ it('displays the text and warning message when `show_with_subtext` is true', () => {
+ setItem({ show_buy_pipeline_minutes: true, show_with_subtext: true });
+
+ expect(item.text()).toContain(UserMenu.i18n.oneOfGroupsRunningOutOfPipelineMinutes);
+ });
+ });
+ });
+ });
+
+ describe('Edit profile item', () => {
+ it('should render a link to the profile page', () => {
+ createWrapper();
+ const item = wrapper.findByTestId('edit-profile-item');
+ expect(item.text()).toBe(UserMenu.i18n.editProfile);
+ expect(item.find('a').attributes('href')).toBe(userMenuMockData.settings.profile_path);
+ });
+ });
+
+ describe('Preferences item', () => {
+ it('should render a link to the profile page', () => {
+ createWrapper();
+ const item = wrapper.findByTestId('preferences-item');
+ expect(item.text()).toBe(UserMenu.i18n.preferences);
+ expect(item.find('a').attributes('href')).toBe(
+ userMenuMockData.settings.profile_preferences_path,
+ );
+ });
+ });
+
+ describe('GitLab Next item', () => {
+ describe('on gitlab.com', () => {
+ it('should render a link to switch to GitLab Next', () => {
+ createWrapper({ gitlab_com_but_not_canary: true });
+ const item = wrapper.findByTestId('gitlab-next-item');
+ expect(item.text()).toBe(UserMenu.i18n.gitlabNext);
+ expect(item.find('a').attributes('href')).toBe(userMenuMockData.canary_toggle_com_url);
+ });
+ });
+
+ describe('anywhere else', () => {
+ it('should not render the GitLab Next link', () => {
+ createWrapper({ gitlab_com_but_not_canary: false });
+ const item = wrapper.findByTestId('gitlab-next-item');
+ expect(item.exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('New navigation toggle item', () => {
+ it('should render menu item with new navigation toggle', () => {
+ createWrapper();
+ const toggleItem = wrapper.findComponent(NewNavToggle);
+ expect(toggleItem.exists()).toBe(true);
+ expect(toggleItem.props('endpoint')).toBe(toggleNewNavEndpoint);
+ });
+ });
+
+ describe('Feedback item', () => {
+ it('should render feedback item with a link to a new GitLab issue', () => {
+ createWrapper();
+ const feedbackItem = wrapper.findByTestId('feedback-item');
+ expect(feedbackItem.find('a').attributes('href')).toBe(UserMenu.feedbackUrl);
+ });
+ });
+
+ describe('Sign out group', () => {
+ const findSignOutGroup = () => wrapper.findByTestId('sign-out-group');
+
+ it('should not render sign out group when user cannot sign out', () => {
+ createWrapper();
+ expect(findSignOutGroup().exists()).toBe(false);
+ });
+
+ describe('when user can sign out', () => {
+ beforeEach(() => {
+ createWrapper({ can_sign_out: true });
+ });
+
+ it('should render sign out group', () => {
+ expect(findSignOutGroup().exists()).toBe(true);
+ });
+
+ it('should render the menu item with a link to sign out and correct data attribute', () => {
+ expect(findSignOutGroup().find('a').attributes('href')).toBe(
+ userMenuMockData.sign_out_link,
+ );
+ expect(findSignOutGroup().find('a').attributes('data-method')).toBe('post');
+ });
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/components/user_name_group_spec.js b/spec/frontend/super_sidebar/components/user_name_group_spec.js
new file mode 100644
index 00000000000..c06c8c218d4
--- /dev/null
+++ b/spec/frontend/super_sidebar/components/user_name_group_spec.js
@@ -0,0 +1,100 @@
+import { GlDisclosureDropdownGroup, GlDisclosureDropdownItem, GlTooltip } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import UserNameGroup from '~/super_sidebar/components/user_name_group.vue';
+import { userMenuMockData, userMenuMockStatus } from '../mock_data';
+
+describe('UserNameGroup component', () => {
+ let wrapper;
+
+ const findGlDisclosureDropdownGroup = () => wrapper.findComponent(GlDisclosureDropdownGroup);
+ const findGlDisclosureDropdownItem = () => wrapper.findComponent(GlDisclosureDropdownItem);
+ const findGlTooltip = () => wrapper.findComponent(GlTooltip);
+ const findUserStatus = () => wrapper.findByTestId('user-menu-status');
+
+ const GlEmoji = { template: '<img/>' };
+
+ const createWrapper = (userDataChanges = {}) => {
+ wrapper = shallowMountExtended(UserNameGroup, {
+ propsData: {
+ user: {
+ ...userMenuMockData,
+ ...userDataChanges,
+ },
+ },
+ stubs: {
+ GlEmoji,
+ GlDisclosureDropdownItem,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ it('renders the menu item in a separate group', () => {
+ expect(findGlDisclosureDropdownGroup().exists()).toBe(true);
+ });
+
+ it('renders menu item', () => {
+ expect(findGlDisclosureDropdownItem().exists()).toBe(true);
+ });
+
+ it('passes the item to the disclosure dropdown item', () => {
+ expect(findGlDisclosureDropdownItem().props('item')).toEqual({
+ text: userMenuMockData.name,
+ href: userMenuMockData.link_to_profile,
+ });
+ });
+
+ it("renders user's name", () => {
+ expect(findGlDisclosureDropdownItem().text()).toContain(userMenuMockData.name);
+ });
+
+ it("renders user's username", () => {
+ expect(findGlDisclosureDropdownItem().text()).toContain(userMenuMockData.username);
+ });
+
+ describe('Busy status', () => {
+ it('should not render "Busy" when user is NOT busy', () => {
+ expect(findGlDisclosureDropdownItem().text()).not.toContain('Busy');
+ });
+ it('should render "Busy" when user is busy', () => {
+ createWrapper({ status: { customized: true, busy: true } });
+
+ expect(findGlDisclosureDropdownItem().text()).toContain('Busy');
+ });
+ });
+
+ describe('User status', () => {
+ describe('when not customized', () => {
+ it('should not render it', () => {
+ expect(findUserStatus().exists()).toBe(false);
+ });
+ });
+
+ describe('when customized', () => {
+ beforeEach(() => {
+ createWrapper({ status: { ...userMenuMockStatus, customized: true } });
+ });
+
+ it('should render it', () => {
+ expect(findUserStatus().exists()).toBe(true);
+ });
+
+ it('should render status emoji', () => {
+ expect(findUserStatus().findComponent(GlEmoji).attributes('data-name')).toBe(
+ userMenuMockData.status.emoji,
+ );
+ });
+
+ it('should render status message', () => {
+ expect(findUserStatus().text()).toContain(userMenuMockData.status.message);
+ });
+
+ it("sets the tooltip's target to the status container", () => {
+ expect(findGlTooltip().props('target')?.()).toBe(findUserStatus().element);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/super_sidebar/mock_data.js b/spec/frontend/super_sidebar/mock_data.js
index 91a2dc93a47..b540f85d9fe 100644
--- a/spec/frontend/super_sidebar/mock_data.js
+++ b/spec/frontend/super_sidebar/mock_data.js
@@ -1,3 +1,5 @@
+import invalidUrl from '~/lib/utils/invalid_url';
+
export const createNewMenuGroups = [
{
name: 'This group',
@@ -58,15 +60,23 @@ export const mergeRequestMenuGroup = [
];
export const sidebarData = {
+ current_menu_items: [],
+ current_context_header: {
+ title: 'Your Work',
+ icon: 'work',
+ },
name: 'Administrator',
username: 'root',
avatar_url: 'path/to/img_administrator',
+ logo_url: 'path/to/logo',
assigned_open_issues_count: 1,
todos_pending_count: 3,
issues_dashboard_path: 'path/to/issues',
total_merge_requests_count: 4,
create_new_menu_groups: createNewMenuGroups,
merge_request_menu: mergeRequestMenuGroup,
+ projects_path: 'path/to/projects',
+ groups_path: 'path/to/groups',
support_path: '/support',
display_whats_new: true,
whats_new_most_recent_release_items_count: 5,
@@ -74,4 +84,181 @@ export const sidebarData = {
show_version_check: false,
gitlab_version: { major: 16, minor: 0 },
gitlab_version_check: { severity: 'success' },
+ gitlab_com_and_canary: false,
+ canary_toggle_com_url: 'https://next.gitlab.com',
+};
+
+export const userMenuMockStatus = {
+ can_update: false,
+ busy: false,
+ customized: false,
+ emoji: 'art',
+ message: 'Working on user menu in super sidebar',
+ availability: 'busy',
+ clear_after: '2023-02-09 20:06:35 UTC',
+};
+
+export const userMenuMockPipelineMinutes = {
+ show_buy_pipeline_minutes: false,
+ show_notification_dot: false,
+ callout_attrs: {
+ feature_id: 'pipeline_minutes',
+ dismiss_endpoint: '/-/dismiss',
+ },
+ buy_pipeline_minutes_path: '/buy/pipeline_minutes',
+ tracking_attrs: {
+ 'track-action': 'trackAction',
+ 'track-label': 'label',
+ 'track-property': 'property',
+ },
+};
+
+export const userMenuMockData = {
+ name: 'Orange Fox',
+ username: 'thefox',
+ avatar_url: invalidUrl,
+ has_link_to_profile: true,
+ link_to_profile: '/thefox',
+ status: userMenuMockStatus,
+ trial: {
+ has_start_trial: false,
+ },
+ settings: {
+ profile_path: invalidUrl,
+ profile_preferences_path: invalidUrl,
+ },
+ pipeline_minutes: userMenuMockPipelineMinutes,
+ can_sign_out: false,
+ sign_out_link: invalidUrl,
+ gitlab_com_but_not_canary: true,
+ canary_toggle_com_url: 'https://next.gitlab.com',
+};
+
+export const cachedFrequentProjects = JSON.stringify([
+ {
+ id: 1,
+ name: 'Cached project 1',
+ namespace: 'Cached Namespace 1 / Cached project 1',
+ webUrl: '/cached-namespace-1/cached-project-1',
+ avatarUrl: '/uploads/-/avatar1.png',
+ lastAccessedOn: 1676325329054,
+ frequency: 10,
+ },
+ {
+ id: 2,
+ name: 'Cached project 2',
+ namespace: 'Cached Namespace 2 / Cached project 2',
+ webUrl: '/cached-namespace-2/cached-project-2',
+ avatarUrl: '/uploads/-/avatar2.png',
+ lastAccessedOn: 1674314684308,
+ frequency: 8,
+ },
+ {
+ id: 3,
+ name: 'Cached project 3',
+ namespace: 'Cached Namespace 3 / Cached project 3',
+ webUrl: '/cached-namespace-3/cached-project-3',
+ avatarUrl: '/uploads/-/avatar3.png',
+ lastAccessedOn: 1664977333191,
+ frequency: 12,
+ },
+ {
+ id: 4,
+ name: 'Cached project 4',
+ namespace: 'Cached Namespace 4 / Cached project 4',
+ webUrl: '/cached-namespace-4/cached-project-4',
+ avatarUrl: '/uploads/-/avatar4.png',
+ lastAccessedOn: 1674315407569,
+ frequency: 3,
+ },
+ {
+ id: 5,
+ name: 'Cached project 5',
+ namespace: 'Cached Namespace 5 / Cached project 5',
+ webUrl: '/cached-namespace-5/cached-project-5',
+ avatarUrl: '/uploads/-/avatar5.png',
+ lastAccessedOn: 1677084729436,
+ frequency: 21,
+ },
+ {
+ id: 6,
+ name: 'Cached project 6',
+ namespace: 'Cached Namespace 6 / Cached project 6',
+ webUrl: '/cached-namespace-6/cached-project-6',
+ avatarUrl: '/uploads/-/avatar6.png',
+ lastAccessedOn: 1676325329679,
+ frequency: 5,
+ },
+]);
+
+export const cachedFrequentGroups = JSON.stringify([
+ {
+ id: 1,
+ name: 'Cached group 1',
+ namespace: 'Cached Namespace 1',
+ webUrl: '/cached-namespace-1/cached-group-1',
+ avatarUrl: '/uploads/-/avatar1.png',
+ lastAccessedOn: 1676325329054,
+ frequency: 10,
+ },
+ {
+ id: 2,
+ name: 'Cached group 2',
+ namespace: 'Cached Namespace 2',
+ webUrl: '/cached-namespace-2/cached-group-2',
+ avatarUrl: '/uploads/-/avatar2.png',
+ lastAccessedOn: 1674314684308,
+ frequency: 8,
+ },
+ {
+ id: 3,
+ name: 'Cached group 3',
+ namespace: 'Cached Namespace 3',
+ webUrl: '/cached-namespace-3/cached-group-3',
+ avatarUrl: '/uploads/-/avatar3.png',
+ lastAccessedOn: 1664977333191,
+ frequency: 12,
+ },
+ {
+ id: 4,
+ name: 'Cached group 4',
+ namespace: 'Cached Namespace 4',
+ webUrl: '/cached-namespace-4/cached-group-4',
+ avatarUrl: '/uploads/-/avatar4.png',
+ lastAccessedOn: 1674315407569,
+ frequency: 3,
+ },
+]);
+
+export const searchUserProjectsAndGroupsResponseMock = {
+ data: {
+ projects: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Project/2',
+ name: 'Gitlab Shell',
+ namespace: 'Gitlab Org / Gitlab Shell',
+ webUrl: 'http://gdk.test:3000/gitlab-org/gitlab-shell',
+ avatarUrl: null,
+ __typename: 'Project',
+ },
+ ],
+ },
+
+ user: {
+ id: 'gid://gitlab/User/1',
+ groups: {
+ nodes: [
+ {
+ id: 'gid://gitlab/Group/60',
+ name: 'GitLab Instance',
+ namespace: 'gitlab-instance-2e4abb29',
+ webUrl: 'http://gdk.test:3000/groups/gitlab-instance-2e4abb29',
+ avatarUrl: null,
+ __typename: 'Group',
+ },
+ ],
+ },
+ },
+ },
};
diff --git a/spec/frontend/super_sidebar/super_sidebar_collapsed_state_manager_spec.js b/spec/frontend/super_sidebar/super_sidebar_collapsed_state_manager_spec.js
new file mode 100644
index 00000000000..3824965970b
--- /dev/null
+++ b/spec/frontend/super_sidebar/super_sidebar_collapsed_state_manager_spec.js
@@ -0,0 +1,157 @@
+import { GlBreakpointInstance as bp, breakpoints } from '@gitlab/ui/dist/utils';
+import { getCookie, setCookie } from '~/lib/utils/common_utils';
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
+import {
+ SIDEBAR_COLLAPSED_CLASS,
+ SIDEBAR_COLLAPSED_COOKIE,
+ SIDEBAR_COLLAPSED_COOKIE_EXPIRATION,
+ toggleSuperSidebarCollapsed,
+ initSuperSidebarCollapsedState,
+ bindSuperSidebarCollapsedEvents,
+ findPage,
+ findSidebar,
+ findToggles,
+} from '~/super_sidebar/super_sidebar_collapsed_state_manager';
+
+const { xl, sm } = breakpoints;
+
+jest.mock('~/lib/utils/common_utils', () => ({
+ getCookie: jest.fn(),
+ setCookie: jest.fn(),
+}));
+
+const pageHasCollapsedClass = (hasClass) => {
+ if (hasClass) {
+ expect(findPage().classList).toContain(SIDEBAR_COLLAPSED_CLASS);
+ } else {
+ expect(findPage().classList).not.toContain(SIDEBAR_COLLAPSED_CLASS);
+ }
+};
+
+describe('Super Sidebar Collapsed State Manager', () => {
+ beforeEach(() => {
+ setHTMLFixture(`
+ <div class="page-with-super-sidebar">
+ <aside class="super-sidebar"></aside>
+ <button class="js-super-sidebar-toggle"></button>
+ </div>
+ `);
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
+ describe('toggleSuperSidebarCollapsed', () => {
+ it.each`
+ collapsed | saveCookie | windowWidth | hasClass
+ ${true} | ${true} | ${xl} | ${true}
+ ${true} | ${false} | ${xl} | ${true}
+ ${true} | ${true} | ${sm} | ${true}
+ ${true} | ${false} | ${sm} | ${true}
+ ${false} | ${true} | ${xl} | ${false}
+ ${false} | ${false} | ${xl} | ${false}
+ ${false} | ${true} | ${sm} | ${false}
+ ${false} | ${false} | ${sm} | ${false}
+ `(
+ 'when collapsed is $collapsed, saveCookie is $saveCookie, and windowWidth is $windowWidth then page class contains `page-with-super-sidebar-collapsed` is $hasClass',
+ ({ collapsed, saveCookie, windowWidth, hasClass }) => {
+ jest.spyOn(bp, 'windowWidth').mockReturnValue(windowWidth);
+
+ toggleSuperSidebarCollapsed(collapsed, saveCookie);
+
+ pageHasCollapsedClass(hasClass);
+ expect(findSidebar().ariaHidden).toBe(collapsed);
+ expect(findSidebar().inert).toBe(collapsed);
+
+ if (saveCookie && windowWidth >= xl) {
+ expect(setCookie).toHaveBeenCalledWith(SIDEBAR_COLLAPSED_COOKIE, collapsed, {
+ expires: SIDEBAR_COLLAPSED_COOKIE_EXPIRATION,
+ });
+ } else {
+ expect(setCookie).not.toHaveBeenCalled();
+ }
+ },
+ );
+
+ describe('focus', () => {
+ it.each`
+ collapsed | isUserAction
+ ${false} | ${true}
+ ${false} | ${false}
+ ${true} | ${true}
+ ${true} | ${false}
+ `(
+ 'when collapsed is $collapsed, isUserAction is $isUserAction',
+ ({ collapsed, isUserAction }) => {
+ const sidebar = findSidebar();
+ jest.spyOn(sidebar, 'focus');
+ toggleSuperSidebarCollapsed(collapsed, false, isUserAction);
+
+ if (!collapsed && isUserAction) {
+ expect(sidebar.focus).toHaveBeenCalled();
+ } else {
+ expect(sidebar.focus).not.toHaveBeenCalled();
+ }
+ },
+ );
+ });
+ });
+
+ describe('initSuperSidebarCollapsedState', () => {
+ it.each`
+ windowWidth | cookie | hasClass
+ ${xl} | ${undefined} | ${false}
+ ${sm} | ${undefined} | ${true}
+ ${xl} | ${'true'} | ${true}
+ ${sm} | ${'true'} | ${true}
+ `(
+ 'sets page class to `page-with-super-sidebar-collapsed` when windowWidth is $windowWidth and cookie value is $cookie',
+ ({ windowWidth, cookie, hasClass }) => {
+ jest.spyOn(bp, 'windowWidth').mockReturnValue(windowWidth);
+ getCookie.mockReturnValue(cookie);
+
+ initSuperSidebarCollapsedState();
+
+ pageHasCollapsedClass(hasClass);
+ expect(setCookie).not.toHaveBeenCalled();
+ },
+ );
+ });
+
+ describe('bindSuperSidebarCollapsedEvents', () => {
+ it.each`
+ windowWidth | cookie | hasClass
+ ${xl} | ${undefined} | ${true}
+ ${sm} | ${undefined} | ${true}
+ ${xl} | ${'true'} | ${false}
+ ${sm} | ${'true'} | ${false}
+ `(
+ 'toggle click sets page class to `page-with-super-sidebar-collapsed` when windowWidth is $windowWidth and cookie value is $cookie',
+ ({ windowWidth, cookie, hasClass }) => {
+ setHTMLFixture(`
+ <div class="page-with-super-sidebar ${cookie ? SIDEBAR_COLLAPSED_CLASS : ''}">
+ <aside class="super-sidebar"></aside>
+ <button class="js-super-sidebar-toggle"></button>
+ </div>
+ `);
+ jest.spyOn(bp, 'windowWidth').mockReturnValue(windowWidth);
+ getCookie.mockReturnValue(cookie);
+
+ bindSuperSidebarCollapsedEvents();
+
+ findToggles()[0].click();
+
+ pageHasCollapsedClass(hasClass);
+
+ if (windowWidth >= xl) {
+ expect(setCookie).toHaveBeenCalledWith(SIDEBAR_COLLAPSED_COOKIE, !cookie, {
+ expires: SIDEBAR_COLLAPSED_COOKIE_EXPIRATION,
+ });
+ } else {
+ expect(setCookie).not.toHaveBeenCalled();
+ }
+ },
+ );
+ });
+});
diff --git a/spec/frontend/super_sidebar/utils_spec.js b/spec/frontend/super_sidebar/utils_spec.js
new file mode 100644
index 00000000000..1f236616e77
--- /dev/null
+++ b/spec/frontend/super_sidebar/utils_spec.js
@@ -0,0 +1,160 @@
+import {
+ getTopFrequentItems,
+ trackContextAccess,
+ formatContextSwitcherItems,
+} from '~/super_sidebar/utils';
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import AccessorUtilities from '~/lib/utils/accessor';
+import { FREQUENT_ITEMS, FIFTEEN_MINUTES_IN_MS } from '~/frequent_items/constants';
+import { unsortedFrequentItems, sortedFrequentItems } from '../frequent_items/mock_data';
+import { searchUserProjectsAndGroupsResponseMock } from './mock_data';
+
+describe('Super sidebar utils spec', () => {
+ describe('getTopFrequentItems', () => {
+ const maxItems = 3;
+
+ it('returns empty array if no items provided', () => {
+ const result = getTopFrequentItems();
+
+ expect(result.length).toBe(0);
+ });
+
+ it('returns the requested amount of items', () => {
+ const result = getTopFrequentItems(unsortedFrequentItems, maxItems);
+
+ expect(result.length).toBe(maxItems);
+ });
+
+ it('sorts frequent items in order of frequency and lastAccessedOn', () => {
+ const result = getTopFrequentItems(unsortedFrequentItems, maxItems);
+ const expectedResult = sortedFrequentItems.slice(0, maxItems);
+
+ expect(result).toEqual(expectedResult);
+ });
+ });
+
+ describe('trackContextAccess', () => {
+ useLocalStorageSpy();
+
+ const username = 'root';
+ const context = {
+ namespace: 'groups',
+ item: { id: 1 },
+ };
+ const storageKey = `${username}/frequent-${context.namespace}`;
+
+ it('returns `false` if local storage is not available', () => {
+ jest.spyOn(AccessorUtilities, 'canUseLocalStorage').mockReturnValue(false);
+
+ expect(trackContextAccess()).toBe(false);
+ });
+
+ it('creates a new item if it does not exist in the local storage', () => {
+ trackContextAccess(username, context);
+
+ expect(window.localStorage.setItem).toHaveBeenCalledWith(
+ storageKey,
+ JSON.stringify([
+ {
+ id: 1,
+ frequency: 1,
+ lastAccessedOn: Date.now(),
+ },
+ ]),
+ );
+ });
+
+ it('updates existing item if it was persisted to the local storage over 15 minutes ago', () => {
+ window.localStorage.setItem(
+ storageKey,
+ JSON.stringify([
+ {
+ id: 1,
+ frequency: 2,
+ lastAccessedOn: Date.now() - FIFTEEN_MINUTES_IN_MS - 1,
+ },
+ ]),
+ );
+ trackContextAccess(username, context);
+
+ expect(window.localStorage.setItem).toHaveBeenCalledWith(
+ storageKey,
+ JSON.stringify([
+ {
+ id: 1,
+ frequency: 3,
+ lastAccessedOn: Date.now(),
+ },
+ ]),
+ );
+ });
+
+ it('leaves item as is if it was persisted to the local storage under 15 minutes ago', () => {
+ const jsonString = JSON.stringify([
+ {
+ id: 1,
+ frequency: 2,
+ lastAccessedOn: Date.now() - FIFTEEN_MINUTES_IN_MS,
+ },
+ ]);
+ window.localStorage.setItem(storageKey, jsonString);
+
+ expect(window.localStorage.setItem).toHaveBeenCalledTimes(1);
+ expect(window.localStorage.setItem).toHaveBeenCalledWith(storageKey, jsonString);
+
+ trackContextAccess(username, context);
+
+ expect(window.localStorage.setItem).toHaveBeenCalledTimes(3);
+ expect(window.localStorage.setItem).toHaveBeenLastCalledWith(storageKey, jsonString);
+ });
+
+ it('replaces the least popular item in the local storage once the persisted items limit has been hit', () => {
+ // Add the maximum amount of items to the local storage, in increasing popularity
+ const storedItems = Array.from({ length: FREQUENT_ITEMS.MAX_COUNT }).map((_, i) => ({
+ id: i + 1,
+ frequency: i + 1,
+ lastAccessedOn: Date.now(),
+ }));
+ // The first item is considered the least popular one as it has the lowest frequency (1)
+ const [leastPopularItem] = storedItems;
+ // Persist the list to the local storage
+ const jsonString = JSON.stringify(storedItems);
+ window.localStorage.setItem(storageKey, jsonString);
+ // Track some new item that hasn't been visited yet
+ const newItem = {
+ id: FREQUENT_ITEMS.MAX_COUNT + 1,
+ };
+ trackContextAccess(username, {
+ namespace: 'groups',
+ item: newItem,
+ });
+ // Finally, retrieve the final data from the local storage
+ const finallyStoredItems = JSON.parse(window.localStorage.getItem(storageKey));
+
+ expect(finallyStoredItems).not.toEqual(expect.arrayContaining([leastPopularItem]));
+ expect(finallyStoredItems).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({
+ id: newItem.id,
+ frequency: 1,
+ }),
+ ]),
+ );
+ });
+ });
+
+ describe('formatContextSwitcherItems', () => {
+ it('returns the formatted items', () => {
+ const projects = searchUserProjectsAndGroupsResponseMock.data.projects.nodes;
+ expect(formatContextSwitcherItems(projects)).toEqual([
+ {
+ id: projects[0].id,
+ avatar: null,
+ title: projects[0].name,
+ subtitle: 'Gitlab Org',
+ link: projects[0].webUrl,
+ },
+ ]);
+ });
+ });
+});
diff --git a/spec/frontend/syntax_highlight_spec.js b/spec/frontend/syntax_highlight_spec.js
index 1be6c213350..a573c37ca44 100644
--- a/spec/frontend/syntax_highlight_spec.js
+++ b/spec/frontend/syntax_highlight_spec.js
@@ -1,14 +1,10 @@
-/* eslint-disable no-return-assign */
import $ from 'jquery';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import syntaxHighlight from '~/syntax_highlight';
describe('Syntax Highlighter', () => {
const stubUserColorScheme = (value) => {
- if (window.gon == null) {
- window.gon = {};
- }
- return (window.gon.user_color_scheme = value);
+ window.gon.user_color_scheme = value;
};
// We have to bind `document.querySelectorAll` to `document` to not mess up the fn's context
diff --git a/spec/frontend/tags/components/delete_tag_modal_spec.js b/spec/frontend/tags/components/delete_tag_modal_spec.js
index b1726a2c0ef..8438bdb7db0 100644
--- a/spec/frontend/tags/components/delete_tag_modal_spec.js
+++ b/spec/frontend/tags/components/delete_tag_modal_spec.js
@@ -44,10 +44,6 @@ const findFormInput = () => wrapper.findComponent(GlFormInput);
const findForm = () => wrapper.find('form');
describe('Delete tag modal', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Deleting a regular tag', () => {
const expectedTitle = 'Delete tag. Are you ABSOLUTELY SURE?';
const expectedMessage = "You're about to permanently delete the tag test-tag.";
diff --git a/spec/frontend/terms/components/app_spec.js b/spec/frontend/terms/components/app_spec.js
index 99f61a31dbd..c60c6c79f17 100644
--- a/spec/frontend/terms/components/app_spec.js
+++ b/spec/frontend/terms/components/app_spec.js
@@ -37,10 +37,6 @@ describe('TermsApp', () => {
isLoggedIn.mockReturnValue(true);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findFormWithAction = (path) => wrapper.find(`form[action="${path}"]`);
const findButton = (path) => findFormWithAction(path).find('button[type="submit"]');
const findScrollableViewport = () => wrapper.findByTestId('scrollable-viewport');
diff --git a/spec/frontend/terraform/components/empty_state_spec.js b/spec/frontend/terraform/components/empty_state_spec.js
index 21bfff5f1be..db3de556244 100644
--- a/spec/frontend/terraform/components/empty_state_spec.js
+++ b/spec/frontend/terraform/components/empty_state_spec.js
@@ -16,10 +16,6 @@ describe('EmptyStateComponent', () => {
wrapper = shallowMount(EmptyState, { propsData });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render content', () => {
expect(findEmptyState().props('title')).toBe(
"Your project doesn't have any Terraform state files",
diff --git a/spec/frontend/terraform/components/init_command_modal_spec.js b/spec/frontend/terraform/components/init_command_modal_spec.js
index 911bb8878da..ca9aa26a776 100644
--- a/spec/frontend/terraform/components/init_command_modal_spec.js
+++ b/spec/frontend/terraform/components/init_command_modal_spec.js
@@ -49,10 +49,6 @@ describe('InitCommandModal', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('on rendering', () => {
it('renders the explanatory text', () => {
expect(findExplanatoryText().text()).toContain('personal access token');
diff --git a/spec/frontend/terraform/components/states_table_actions_spec.js b/spec/frontend/terraform/components/states_table_actions_spec.js
index 40b7448d78d..31a644b39b4 100644
--- a/spec/frontend/terraform/components/states_table_actions_spec.js
+++ b/spec/frontend/terraform/components/states_table_actions_spec.js
@@ -1,4 +1,4 @@
-import { GlDropdown, GlModal, GlSprintf } from '@gitlab/ui';
+import { GlDropdown, GlModal, GlSprintf, GlFormInput } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
@@ -85,6 +85,7 @@ describe('StatesTableActions', () => {
const findDownloadBtn = () => wrapper.find('[data-testid="terraform-state-download"]');
const findRemoveBtn = () => wrapper.find('[data-testid="terraform-state-remove"]');
const findRemoveModal = () => wrapper.findComponent(GlModal);
+ const findFormInput = () => wrapper.findComponent(GlFormInput);
beforeEach(() => {
return createComponent();
@@ -96,7 +97,6 @@ describe('StatesTableActions', () => {
toast = null;
unlockResponse = null;
updateStateResponse = null;
- wrapper.destroy();
});
describe('when the state is loading', () => {
@@ -296,9 +296,7 @@ describe('StatesTableActions', () => {
describe('when state name is present', () => {
beforeEach(async () => {
- // 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({ removeConfirmText: defaultProps.state.name });
+ await findFormInput().vm.$emit('input', defaultProps.state.name);
findRemoveModal().vm.$emit('ok');
diff --git a/spec/frontend/terraform/components/states_table_spec.js b/spec/frontend/terraform/components/states_table_spec.js
index 0b3b169891b..7c783c9f717 100644
--- a/spec/frontend/terraform/components/states_table_spec.js
+++ b/spec/frontend/terraform/components/states_table_spec.js
@@ -127,7 +127,7 @@ describe('StatesTable', () => {
propsData,
provide: { projectPath: 'path/to/project' },
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
}),
);
@@ -140,11 +140,6 @@ describe('StatesTable', () => {
return createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it.each`
name | toolTipText | hasBadge | loading | lineNumber
${'state-1'} | ${'Locked by user-1 2 days ago'} | ${true} | ${false} | ${0}
diff --git a/spec/frontend/terraform/components/terraform_list_spec.js b/spec/frontend/terraform/components/terraform_list_spec.js
index 580951e799a..dc59e2769f6 100644
--- a/spec/frontend/terraform/components/terraform_list_spec.js
+++ b/spec/frontend/terraform/components/terraform_list_spec.js
@@ -63,11 +63,6 @@ describe('TerraformList', () => {
const findStatesTable = () => wrapper.findComponent(StatesTable);
const findTab = () => wrapper.findComponent(GlTab);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when the terraform query has succeeded', () => {
describe('when there is a list of terraform states', () => {
const states = [
diff --git a/spec/frontend/toggles/index_spec.js b/spec/frontend/toggles/index_spec.js
index f8c43e0ad0c..89e35991914 100644
--- a/spec/frontend/toggles/index_spec.js
+++ b/spec/frontend/toggles/index_spec.js
@@ -44,7 +44,6 @@ describe('toggles/index.js', () => {
afterEach(() => {
document.body.innerHTML = '';
instance = null;
- toggleWrapper = null;
});
describe('initToggle', () => {
diff --git a/spec/frontend/token_access/inbound_token_access_spec.js b/spec/frontend/token_access/inbound_token_access_spec.js
index fcd1a33fa68..1ca58053e68 100644
--- a/spec/frontend/token_access/inbound_token_access_spec.js
+++ b/spec/frontend/token_access/inbound_token_access_spec.js
@@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import InboundTokenAccess from '~/token_access/components/inbound_token_access.vue';
import inboundAddProjectCIJobTokenScopeMutation from '~/token_access/graphql/mutations/inbound_add_project_ci_job_token_scope.mutation.graphql';
import inboundRemoveProjectCIJobTokenScopeMutation from '~/token_access/graphql/mutations/inbound_remove_project_ci_job_token_scope.mutation.graphql';
@@ -26,7 +26,7 @@ const error = new Error(message);
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('TokenAccess component', () => {
let wrapper;
diff --git a/spec/frontend/token_access/opt_in_jwt_spec.js b/spec/frontend/token_access/opt_in_jwt_spec.js
index 3a68f247aa6..cdb385aa743 100644
--- a/spec/frontend/token_access/opt_in_jwt_spec.js
+++ b/spec/frontend/token_access/opt_in_jwt_spec.js
@@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { OPT_IN_JWT_HELP_LINK } from '~/token_access/constants';
import OptInJwt from '~/token_access/components/opt_in_jwt.vue';
import getOptInJwtSettingQuery from '~/token_access/graphql/queries/get_opt_in_jwt_setting.query.graphql';
@@ -16,7 +16,7 @@ const error = new Error(errorMessage);
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('OptInJwt component', () => {
let wrapper;
diff --git a/spec/frontend/token_access/outbound_token_access_spec.js b/spec/frontend/token_access/outbound_token_access_spec.js
index 893a021197f..347ea1178bc 100644
--- a/spec/frontend/token_access/outbound_token_access_spec.js
+++ b/spec/frontend/token_access/outbound_token_access_spec.js
@@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import OutboundTokenAccess from '~/token_access/components/outbound_token_access.vue';
import addProjectCIJobTokenScopeMutation from '~/token_access/graphql/mutations/add_project_ci_job_token_scope.mutation.graphql';
import removeProjectCIJobTokenScopeMutation from '~/token_access/graphql/mutations/remove_project_ci_job_token_scope.mutation.graphql';
@@ -26,7 +26,7 @@ const error = new Error(message);
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('TokenAccess component', () => {
let wrapper;
diff --git a/spec/frontend/token_access/token_access_app_spec.js b/spec/frontend/token_access/token_access_app_spec.js
index 7f269ee5fda..cff16fd125c 100644
--- a/spec/frontend/token_access/token_access_app_spec.js
+++ b/spec/frontend/token_access/token_access_app_spec.js
@@ -11,12 +11,8 @@ describe('TokenAccessApp component', () => {
const findInboundTokenAccess = () => wrapper.findComponent(InboundTokenAccess);
const findOptInJwt = () => wrapper.findComponent(OptInJwt);
- const createComponent = (flagState = false) => {
- wrapper = shallowMount(TokenAccessApp, {
- provide: {
- glFeatures: { ciInboundJobTokenScope: flagState },
- },
- });
+ const createComponent = () => {
+ wrapper = shallowMount(TokenAccessApp);
};
describe('default', () => {
@@ -32,12 +28,6 @@ describe('TokenAccessApp component', () => {
expect(findOutboundTokenAccess().exists()).toBe(true);
});
- it('does not render the inbound token access component', () => {
- expect(findInboundTokenAccess().exists()).toBe(false);
- });
- });
-
- describe('with feature flag enabled', () => {
it('renders the inbound token access component', () => {
createComponent(true);
diff --git a/spec/frontend/token_access/token_projects_table_spec.js b/spec/frontend/token_access/token_projects_table_spec.js
index b51d8b3ccea..7654aa09b0a 100644
--- a/spec/frontend/token_access/token_projects_table_spec.js
+++ b/spec/frontend/token_access/token_projects_table_spec.js
@@ -6,14 +6,19 @@ import { mockProjects, mockFields } from './mock_data';
describe('Token projects table', () => {
let wrapper;
- const createComponent = () => {
+ const defaultProps = {
+ tableFields: mockFields,
+ projects: mockProjects,
+ };
+
+ const createComponent = (props) => {
wrapper = mountExtended(TokenProjectsTable, {
provide: {
fullPath: 'root/ci-project',
},
propsData: {
- tableFields: mockFields,
- projects: mockProjects,
+ ...defaultProps,
+ ...props,
},
});
};
@@ -25,31 +30,52 @@ describe('Token projects table', () => {
const findProjectNameCell = () => wrapper.findByTestId('token-access-project-name');
const findProjectNamespaceCell = () => wrapper.findByTestId('token-access-project-namespace');
- beforeEach(() => {
+ it('displays a table', () => {
createComponent();
- });
- it('displays a table', () => {
expect(findTable().exists()).toBe(true);
});
it('displays the correct amount of table rows', () => {
+ createComponent();
+
expect(findAllTableRows()).toHaveLength(mockProjects.length);
});
it('delete project button emits event with correct project to delete', async () => {
+ createComponent();
+
await findDeleteProjectBtn().trigger('click');
expect(wrapper.emitted('removeProject')).toEqual([[mockProjects[0].fullPath]]);
});
it('does not show the remove icon if the project is locked', () => {
+ createComponent();
+
// currently two mock projects with one being a locked project
expect(findAllDeleteProjectBtn()).toHaveLength(1);
});
it('displays project and namespace cells', () => {
+ createComponent();
+
expect(findProjectNameCell().text()).toBe('merge-train-stuff');
expect(findProjectNamespaceCell().text()).toBe('root');
});
+
+ it('displays empty string for namespace when namespace is null', () => {
+ const nullNamespace = {
+ id: '1',
+ name: 'merge-train-stuff',
+ namespace: null,
+ fullPath: 'root/merge-train-stuff',
+ isLocked: false,
+ __typename: 'Project',
+ };
+
+ createComponent({ projects: [nullNamespace] });
+
+ expect(findProjectNamespaceCell().text()).toBe('');
+ });
});
diff --git a/spec/frontend/tooltips/components/tooltips_spec.js b/spec/frontend/tooltips/components/tooltips_spec.js
index d5a63a99601..e473091f405 100644
--- a/spec/frontend/tooltips/components/tooltips_spec.js
+++ b/spec/frontend/tooltips/components/tooltips_spec.js
@@ -30,11 +30,6 @@ describe('tooltips/components/tooltips.vue', () => {
const allTooltips = () => wrapper.findAllComponents(GlTooltip);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('addTooltips', () => {
let target;
diff --git a/spec/frontend/usage_quotas/components/usage_quotas_app_spec.js b/spec/frontend/usage_quotas/components/usage_quotas_app_spec.js
index cb70ea4e72d..3508bf7cfde 100644
--- a/spec/frontend/usage_quotas/components/usage_quotas_app_spec.js
+++ b/spec/frontend/usage_quotas/components/usage_quotas_app_spec.js
@@ -23,10 +23,6 @@ describe('UsageQuotasApp', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findSubTitle = () => wrapper.findByTestId('usage-quotas-page-subtitle');
it('renders the view title', () => {
diff --git a/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js b/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js
index 3379af3f41c..1a200090805 100644
--- a/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js
+++ b/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js
@@ -53,10 +53,6 @@ describe('ProjectStorageApp', () => {
const findUsageQuotasHelpLink = () => wrapper.findByTestId('usage-quotas-help-link');
const findUsageGraph = () => wrapper.findComponent(UsageGraph);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with apollo fetching successful', () => {
let mockApollo;
diff --git a/spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js b/spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js
index ce489f69cad..6065ec9e4bf 100644
--- a/spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js
+++ b/spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js
@@ -54,9 +54,6 @@ describe('ProjectStorageDetail', () => {
beforeEach(() => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
describe('with storage types', () => {
it.each(storageTypes)(
diff --git a/spec/frontend/usage_quotas/storage/components/storage_type_icon_spec.js b/spec/frontend/usage_quotas/storage/components/storage_type_icon_spec.js
index 1eb3386bfb8..364cf1e587b 100644
--- a/spec/frontend/usage_quotas/storage/components/storage_type_icon_spec.js
+++ b/spec/frontend/usage_quotas/storage/components/storage_type_icon_spec.js
@@ -16,10 +16,6 @@ describe('StorageTypeIcon', () => {
const findGlIcon = () => wrapper.findComponent(GlIcon);
describe('rendering icon', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
expected | provided
${'doc-image'} | ${'lfsObjectsSize'}
diff --git a/spec/frontend/usage_quotas/storage/components/usage_graph_spec.js b/spec/frontend/usage_quotas/storage/components/usage_graph_spec.js
index 75b970d937a..02268e1c9d8 100644
--- a/spec/frontend/usage_quotas/storage/components/usage_graph_spec.js
+++ b/spec/frontend/usage_quotas/storage/components/usage_graph_spec.js
@@ -39,10 +39,6 @@ describe('Storage Counter usage graph component', () => {
mountComponent(data);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the legend in order', () => {
const types = wrapper.findAll('[data-testid="storage-type-legend"]');
diff --git a/spec/frontend/user_lists/components/user_lists_spec.js b/spec/frontend/user_lists/components/user_lists_spec.js
index 161eb036361..603289ac11e 100644
--- a/spec/frontend/user_lists/components/user_lists_spec.js
+++ b/spec/frontend/user_lists/components/user_lists_spec.js
@@ -39,11 +39,6 @@ describe('~/user_lists/components/user_lists.vue', () => {
const newButton = () => within(wrapper.element).queryAllByText('New user list');
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('without permissions', () => {
const provideData = {
...mockProvide,
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 3324b040b86..96e9705f02b 100644
--- a/spec/frontend/user_lists/components/user_lists_table_spec.js
+++ b/spec/frontend/user_lists/components/user_lists_table_spec.js
@@ -22,10 +22,6 @@ describe('User Lists Table', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should display the details of a user list', () => {
expect(wrapper.find('[data-testid="ffUserListName"]').text()).toBe(userList.name);
expect(wrapper.find('[data-testid="ffUserListIds"]').text()).toBe(
diff --git a/spec/frontend/user_popovers_spec.js b/spec/frontend/user_popovers_spec.js
index 8ce071c075f..3808cc8b0fc 100644
--- a/spec/frontend/user_popovers_spec.js
+++ b/spec/frontend/user_popovers_spec.js
@@ -10,8 +10,6 @@ jest.mock('~/api/user_api', () => ({
}));
describe('User Popovers', () => {
- let origGon;
-
const fixtureTemplate = 'merge_requests/merge_request_with_mentions.html';
const selector = '.js-user-link[data-user], .js-user-link[data-user-id]';
@@ -60,15 +58,6 @@ describe('User Popovers', () => {
});
};
- beforeEach(() => {
- origGon = window.gon;
- window.gon = {};
- });
-
- afterEach(() => {
- window.gon = origGon;
- });
-
describe('when signed out', () => {
beforeEach(() => {
setupTestSubject();
diff --git a/spec/frontend/validators/length_validator_spec.js b/spec/frontend/validators/length_validator_spec.js
new file mode 100644
index 00000000000..ece8238b3e3
--- /dev/null
+++ b/spec/frontend/validators/length_validator_spec.js
@@ -0,0 +1,91 @@
+import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
+import LengthValidator, { isAboveMaxLength, isBelowMinLength } from '~/validators/length_validator';
+
+describe('length_validator', () => {
+ describe('isAboveMaxLength', () => {
+ it('should return true if the string is longer than the maximum length', () => {
+ expect(isAboveMaxLength('123456', '5')).toBe(true);
+ });
+
+ it('should return false if the string is shorter than the maximum length', () => {
+ expect(isAboveMaxLength('1234', '5')).toBe(false);
+ });
+ });
+
+ describe('isBelowMinLength', () => {
+ it('should return true if the string is shorter than the minimum length and not empty', () => {
+ expect(isBelowMinLength('1234', '5', 'false')).toBe(true);
+ });
+
+ it('should return false if the string is longer than the minimum length', () => {
+ expect(isBelowMinLength('123456', '5', 'false')).toBe(false);
+ });
+
+ it('should return false if the string is empty and allowed to be empty', () => {
+ expect(isBelowMinLength('', '5', 'true')).toBe(false);
+ });
+
+ it('should return true if the string is empty and not allowed to be empty', () => {
+ expect(isBelowMinLength('', '5', 'false')).toBe(true);
+ });
+ });
+
+ describe('LengthValidator', () => {
+ let input;
+ let validator;
+
+ beforeEach(() => {
+ setHTMLFixture(
+ '<div class="container"><input class="js-validate-length" /><span class="gl-field-error"></span></div>',
+ );
+ input = document.querySelector('input');
+ input.dataset.minLength = '3';
+ input.dataset.maxLength = '5';
+ input.dataset.minLengthMessage = 'Too short';
+ input.dataset.maxLengthMessage = 'Too long';
+ validator = new LengthValidator({ container: '.container' });
+ });
+
+ afterEach(() => {
+ resetHTMLFixture();
+ });
+
+ it('sets error message for input with value longer than max length', () => {
+ input.value = '123456';
+ input.dispatchEvent(new Event('input'));
+ expect(validator.errorMessage).toBe('Too long');
+ });
+
+ it('sets error message for input with value shorter than min length', () => {
+ input.value = '12';
+ input.dispatchEvent(new Event('input'));
+ expect(validator.errorMessage).toBe('Too short');
+ });
+
+ it('does not set error message for input with valid length', () => {
+ input.value = '123';
+ input.dispatchEvent(new Event('input'));
+ expect(validator.errorMessage).toBeNull();
+ });
+
+ it('does not set error message for empty input if allowEmpty is true', () => {
+ input.dataset.allowEmpty = 'true';
+ input.value = '';
+ input.dispatchEvent(new Event('input'));
+ expect(validator.errorMessage).toBeNull();
+ });
+
+ it('sets error message for empty input if allowEmpty is false', () => {
+ input.dataset.allowEmpty = 'false';
+ input.value = '';
+ input.dispatchEvent(new Event('input'));
+ expect(validator.errorMessage).toBe('Too short');
+ });
+
+ it('sets error message for empty input if allowEmpty is not defined', () => {
+ input.value = '';
+ input.dispatchEvent(new Event('input'));
+ expect(validator.errorMessage).toBe('Too short');
+ });
+ });
+});
diff --git a/spec/frontend/vue_compat_test_setup.js b/spec/frontend/vue_compat_test_setup.js
new file mode 100644
index 00000000000..b6234c51535
--- /dev/null
+++ b/spec/frontend/vue_compat_test_setup.js
@@ -0,0 +1,60 @@
+/* eslint-disable import/no-commonjs */
+const Vue = require('vue');
+const VTU = require('@vue/test-utils');
+const { installCompat: installVTUCompat, fullCompatConfig } = require('vue-test-utils-compat');
+
+if (global.document) {
+ const compatConfig = {
+ MODE: 2,
+
+ GLOBAL_MOUNT: 'suppress-warning',
+ GLOBAL_EXTEND: 'suppress-warning',
+ GLOBAL_PROTOTYPE: 'suppress-warning',
+ RENDER_FUNCTION: 'suppress-warning',
+
+ INSTANCE_DESTROY: 'suppress-warning',
+ INSTANCE_DELETE: 'suppress-warning',
+
+ INSTANCE_ATTRS_CLASS_STYLE: 'suppress-warning',
+ INSTANCE_CHILDREN: 'suppress-warning',
+ INSTANCE_SCOPED_SLOTS: 'suppress-warning',
+ INSTANCE_LISTENERS: 'suppress-warning',
+ INSTANCE_EVENT_EMITTER: 'suppress-warning',
+ INSTANCE_EVENT_HOOKS: 'suppress-warning',
+ INSTANCE_SET: 'suppress-warning',
+ GLOBAL_OBSERVABLE: 'suppress-warning',
+ GLOBAL_SET: 'suppress-warning',
+ COMPONENT_FUNCTIONAL: 'suppress-warning',
+ COMPONENT_V_MODEL: 'suppress-warning',
+ CUSTOM_DIR: 'suppress-warning',
+ OPTIONS_BEFORE_DESTROY: 'suppress-warning',
+ OPTIONS_DATA_MERGE: 'suppress-warning',
+ OPTIONS_DATA_FN: 'suppress-warning',
+ OPTIONS_DESTROYED: 'suppress-warning',
+ ATTR_FALSE_VALUE: 'suppress-warning',
+
+ COMPILER_V_ON_NATIVE: 'suppress-warning',
+ COMPILER_V_BIND_OBJECT_ORDER: 'suppress-warning',
+
+ CONFIG_WHITESPACE: 'suppress-warning',
+ CONFIG_OPTION_MERGE_STRATS: 'suppress-warning',
+ PRIVATE_APIS: 'suppress-warning',
+ WATCH_ARRAY: 'suppress-warning',
+ };
+
+ let compatH;
+ Vue.config.compilerOptions.whitespace = 'condense';
+ Vue.createApp({
+ compatConfig: {
+ MODE: 3,
+ RENDER_FUNCTION: 'suppress-warning',
+ },
+ render(h) {
+ compatH = h;
+ },
+ }).mount(document.createElement('div'));
+
+ Vue.configureCompat(compatConfig);
+ installVTUCompat(VTU, fullCompatConfig, compatH);
+ VTU.config.global.renderStubDefaultSlot = true;
+}
diff --git a/spec/frontend/vue_merge_request_widget/components/action_buttons.js b/spec/frontend/vue_merge_request_widget/components/action_buttons.js
index 6d714aeaf18..7334f061dc9 100644
--- a/spec/frontend/vue_merge_request_widget/components/action_buttons.js
+++ b/spec/frontend/vue_merge_request_widget/components/action_buttons.js
@@ -11,10 +11,6 @@ function factory(propsData = {}) {
}
describe('MR widget extension actions', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('tertiaryButtons', () => {
it('renders buttons', () => {
factory({
diff --git a/spec/frontend/vue_merge_request_widget/components/added_commit_message_spec.js b/spec/frontend/vue_merge_request_widget/components/added_commit_message_spec.js
index 063425454d7..4164a7df482 100644
--- a/spec/frontend/vue_merge_request_widget/components/added_commit_message_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/added_commit_message_spec.js
@@ -14,10 +14,6 @@ function factory(propsData) {
}
describe('Widget added commit message', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays changes where not merged when state is closed', () => {
factory({ state: 'closed' });
diff --git a/spec/frontend/vue_merge_request_widget/components/approvals/approvals_spec.js b/spec/frontend/vue_merge_request_widget/components/approvals/approvals_spec.js
index bf208f16d18..e78e1be7882 100644
--- a/spec/frontend/vue_merge_request_widget/components/approvals/approvals_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/approvals/approvals_spec.js
@@ -1,26 +1,32 @@
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
import { GlButton, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { createAlert } from '~/flash';
+import approvedByCurrentUser from 'test_fixtures/graphql/merge_requests/approvals/approvals.query.graphql.json';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { createAlert } from '~/alert';
import Approvals from '~/vue_merge_request_widget/components/approvals/approvals.vue';
import ApprovalsSummary from '~/vue_merge_request_widget/components/approvals/approvals_summary.vue';
import ApprovalsSummaryOptional from '~/vue_merge_request_widget/components/approvals/approvals_summary_optional.vue';
import {
- FETCH_LOADING,
- FETCH_ERROR,
APPROVE_ERROR,
UNAPPROVE_ERROR,
} from '~/vue_merge_request_widget/components/approvals/messages';
import eventHub from '~/vue_merge_request_widget/event_hub';
+import approvedByQuery from 'ee_else_ce/vue_merge_request_widget/components/approvals/queries/approvals.query.graphql';
+import { createCanApproveResponse } from 'jest/approvals/mock_data';
+
+Vue.use(VueApollo);
const mockAlertDismiss = jest.fn();
-jest.mock('~/flash', () => ({
+jest.mock('~/alert', () => ({
createAlert: jest.fn().mockImplementation(() => ({
dismiss: mockAlertDismiss,
})),
}));
-const RULE_NAME = 'first_rule';
const TEST_HELP_PATH = 'help/path';
const testApprovedBy = () => [1, 7, 10].map((id) => ({ id }));
const testApprovals = () => ({
@@ -34,15 +40,18 @@ const testApprovals = () => ({
require_password_to_approve: false,
invalid_approvers_rules: [],
});
-const testApprovalRulesResponse = () => ({ rules: [{ id: 2 }] });
describe('MRWidget approvals', () => {
let wrapper;
let service;
let mr;
- const createComponent = (props = {}) => {
+ const createComponent = (props = {}, response = approvedByCurrentUser) => {
+ const requestHandlers = [[approvedByQuery, jest.fn().mockResolvedValue(response)]];
+ const apolloProvider = createMockApollo(requestHandlers);
+
wrapper = shallowMount(Approvals, {
+ apolloProvider,
propsData: {
mr,
service,
@@ -68,15 +77,10 @@ describe('MRWidget approvals', () => {
};
const findSummary = () => wrapper.findComponent(ApprovalsSummary);
const findOptionalSummary = () => wrapper.findComponent(ApprovalsSummaryOptional);
- const findInvalidRules = () => wrapper.find('[data-testid="invalid-rules"]');
beforeEach(() => {
service = {
...{
- fetchApprovals: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
- fetchApprovalSettings: jest
- .fn()
- .mockReturnValue(Promise.resolve(testApprovalRulesResponse())),
approveMergeRequest: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
unapproveMergeRequest: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
approveMergeRequestWithAuth: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
@@ -97,55 +101,21 @@ describe('MRWidget approvals', () => {
};
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- });
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- describe('when created', () => {
- it('shows loading message', async () => {
- service = {
- fetchApprovals: jest.fn().mockReturnValue(new Promise(() => {})),
- };
-
- createComponent();
- await nextTick();
- expect(wrapper.text()).toContain(FETCH_LOADING);
- });
-
- it('fetches approvals', () => {
- createComponent();
- expect(service.fetchApprovals).toHaveBeenCalled();
- });
- });
-
- describe('when fetch approvals error', () => {
- beforeEach(() => {
- jest.spyOn(service, 'fetchApprovals').mockReturnValue(Promise.reject());
- createComponent();
- return nextTick();
- });
-
- it('still shows loading message', () => {
- expect(wrapper.text()).toContain(FETCH_LOADING);
- });
-
- it('flashes error', () => {
- expect(createAlert).toHaveBeenCalledWith({ message: FETCH_ERROR });
- });
+ gon.current_user_id = getIdFromGraphQLId(
+ approvedByCurrentUser.data.project.mergeRequest.approvedBy.nodes[0].id,
+ );
});
describe('action button', () => {
describe('when mr is closed', () => {
- beforeEach(() => {
+ beforeEach(async () => {
+ const response = createCanApproveResponse();
+
mr.isOpen = false;
- mr.approvals.user_has_approved = false;
- mr.approvals.user_can_approve = true;
- createComponent();
- return nextTick();
+ createComponent({}, response);
+ await waitForPromises();
});
it('action is not rendered', () => {
@@ -154,12 +124,12 @@ describe('MRWidget approvals', () => {
});
describe('when user cannot approve', () => {
- beforeEach(() => {
- mr.approvals.user_has_approved = false;
- mr.approvals.user_can_approve = false;
+ beforeEach(async () => {
+ const response = JSON.parse(JSON.stringify(approvedByCurrentUser));
+ response.data.project.mergeRequest.approvedBy.nodes = [];
- createComponent();
- return nextTick();
+ createComponent({}, response);
+ await waitForPromises();
});
it('action is not rendered', () => {
@@ -168,15 +138,16 @@ describe('MRWidget approvals', () => {
});
describe('when user can approve', () => {
+ let canApproveResponse;
+
beforeEach(() => {
- mr.approvals.user_has_approved = false;
- mr.approvals.user_can_approve = true;
+ canApproveResponse = createCanApproveResponse();
});
describe('and MR is unapproved', () => {
- beforeEach(() => {
- createComponent();
- return nextTick();
+ beforeEach(async () => {
+ createComponent({}, canApproveResponse);
+ await waitForPromises();
});
it('approve action is rendered', () => {
@@ -190,30 +161,33 @@ describe('MRWidget approvals', () => {
describe('and MR is approved', () => {
beforeEach(() => {
- mr.approvals.approved = true;
+ canApproveResponse.data.project.mergeRequest.approved = true;
});
describe('with no approvers', () => {
- beforeEach(() => {
- mr.approvals.approved_by = [];
- createComponent();
- return nextTick();
+ beforeEach(async () => {
+ canApproveResponse.data.project.mergeRequest.approvedBy.nodes = [];
+ createComponent({}, canApproveResponse);
+ await nextTick();
});
- it('approve action (with inverted style) is rendered', () => {
- expect(findActionData()).toEqual({
+ it('approve action is rendered', () => {
+ expect(findActionData()).toMatchObject({
variant: 'confirm',
text: 'Approve',
- category: 'secondary',
});
});
});
describe('with approvers', () => {
- beforeEach(() => {
- mr.approvals.approved_by = [{ user: { id: 7 } }];
- createComponent();
- return nextTick();
+ beforeEach(async () => {
+ canApproveResponse.data.project.mergeRequest.approvedBy.nodes =
+ approvedByCurrentUser.data.project.mergeRequest.approvedBy.nodes;
+
+ canApproveResponse.data.project.mergeRequest.approvedBy.nodes[0].id = 2;
+
+ createComponent({}, canApproveResponse);
+ await waitForPromises();
});
it('approve additionally action is rendered', () => {
@@ -227,9 +201,9 @@ describe('MRWidget approvals', () => {
});
describe('when approve action is clicked', () => {
- beforeEach(() => {
- createComponent();
- return nextTick();
+ beforeEach(async () => {
+ createComponent({}, canApproveResponse);
+ await waitForPromises();
});
it('shows loading icon', () => {
@@ -258,10 +232,6 @@ describe('MRWidget approvals', () => {
it('emits to eventHub', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
});
-
- it('calls store setApprovals', () => {
- expect(mr.setApprovals).toHaveBeenCalledWith(testApprovals());
- });
});
describe('and error', () => {
@@ -286,12 +256,12 @@ describe('MRWidget approvals', () => {
});
describe('when user has approved', () => {
- beforeEach(() => {
- mr.approvals.user_has_approved = true;
- mr.approvals.user_can_approve = false;
+ beforeEach(async () => {
+ const response = JSON.parse(JSON.stringify(approvedByCurrentUser));
- createComponent();
- return nextTick();
+ createComponent({}, response);
+
+ await waitForPromises();
});
it('revoke action is rendered', () => {
@@ -316,10 +286,6 @@ describe('MRWidget approvals', () => {
it('emits to eventHub', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
});
-
- it('calls store setApprovals', () => {
- expect(mr.setApprovals).toHaveBeenCalledWith(testApprovals());
- });
});
describe('and error', () => {
@@ -329,7 +295,7 @@ describe('MRWidget approvals', () => {
return nextTick();
});
- it('flashes error message', () => {
+ it('alerts error message', () => {
expect(createAlert).toHaveBeenCalledWith({ message: UNAPPROVE_ERROR });
});
});
@@ -338,19 +304,24 @@ describe('MRWidget approvals', () => {
});
describe('approvals optional summary', () => {
+ let optionalApprovalsResponse;
+
+ beforeEach(() => {
+ optionalApprovalsResponse = JSON.parse(JSON.stringify(approvedByCurrentUser));
+ });
+
describe('when no approvals required and no approvers', () => {
beforeEach(() => {
- mr.approvals.approved_by = [];
- mr.approvals.approvals_required = 0;
- mr.approvals.user_has_approved = false;
+ optionalApprovalsResponse.data.project.mergeRequest.approvedBy.nodes = [];
+ optionalApprovalsResponse.data.project.mergeRequest.approvalsRequired = 0;
});
describe('and can approve', () => {
- beforeEach(() => {
- mr.approvals.user_can_approve = true;
+ beforeEach(async () => {
+ optionalApprovalsResponse.data.project.mergeRequest.userPermissions.canApprove = true;
- createComponent();
- return nextTick();
+ createComponent({}, optionalApprovalsResponse);
+ await waitForPromises();
});
it('is shown', () => {
@@ -363,11 +334,9 @@ describe('MRWidget approvals', () => {
});
describe('and cannot approve', () => {
- beforeEach(() => {
- mr.approvals.user_can_approve = false;
-
- createComponent();
- return nextTick();
+ beforeEach(async () => {
+ createComponent({}, optionalApprovalsResponse);
+ await nextTick();
});
it('is shown', () => {
@@ -382,9 +351,9 @@ describe('MRWidget approvals', () => {
});
describe('approvals summary', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createComponent();
- return nextTick();
+ await nextTick();
});
it('is rendered with props', () => {
@@ -393,41 +362,7 @@ describe('MRWidget approvals', () => {
expect(findOptionalSummary().exists()).toBe(false);
expect(summary.exists()).toBe(true);
expect(summary.props()).toMatchObject({
- projectPath: 'gitlab-org/gitlab',
- iid: '1',
- updatedCount: 0,
- });
- });
- });
-
- describe('invalid rules', () => {
- beforeEach(() => {
- mr.approvals.merge_request_approvers_available = true;
- createComponent();
- });
-
- it('does not render related components', () => {
- expect(findInvalidRules().exists()).toBe(false);
- });
-
- describe('when invalid rules are present', () => {
- beforeEach(() => {
- mr.approvals.invalid_approvers_rules = [{ name: RULE_NAME }];
- createComponent();
- });
-
- it('renders related components', () => {
- const invalidRules = findInvalidRules();
-
- expect(invalidRules.exists()).toBe(true);
-
- const invalidRulesText = invalidRules.text();
-
- expect(invalidRulesText).toContain(RULE_NAME);
- expect(invalidRulesText).toContain(
- 'GitLab has approved this rule automatically to unblock the merge request.',
- );
- expect(invalidRulesText).toContain('Learn more.');
+ approvalState: approvedByCurrentUser.data.project.mergeRequest,
});
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_optional_spec.js b/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_optional_spec.js
index e6fb0495947..bf3df70d423 100644
--- a/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_optional_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_optional_spec.js
@@ -13,11 +13,6 @@ describe('MRWidget approvals summary optional', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findHelpLink = () => wrapper.findComponent(GlLink);
describe('when can approve', () => {
diff --git a/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js b/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js
index e75ce7c60c9..8c6b3cc464c 100644
--- a/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js
@@ -1,11 +1,10 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { mount } from '@vue/test-utils';
-import approvedByMultipleUsers from 'test_fixtures/graphql/merge_requests/approvals/approved_by.query.graphql_multiple_users.json';
-import noApprovalsResponse from 'test_fixtures/graphql/merge_requests/approvals/approved_by.query.graphql_no_approvals.json';
-import approvedByCurrentUser from 'test_fixtures/graphql/merge_requests/approvals/approved_by.query.graphql.json';
+import approvedByMultipleUsers from 'test_fixtures/graphql/merge_requests/approvals/approvals.query.graphql_multiple_users.json';
+import noApprovalsResponse from 'test_fixtures/graphql/merge_requests/approvals/approvals.query.graphql_no_approvals.json';
+import approvedByCurrentUser from 'test_fixtures/graphql/merge_requests/approvals/approvals.query.graphql.json';
import waitForPromises from 'helpers/wait_for_promises';
-import createMockApollo from 'helpers/mock_apollo_helper';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import ApprovalsSummary from '~/vue_merge_request_widget/components/approvals/approvals_summary.vue';
import {
@@ -14,32 +13,22 @@ import {
APPROVED_BY_YOU_AND_OTHERS,
} from '~/vue_merge_request_widget/components/approvals/messages';
import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue';
-import approvedByQuery from 'ee_else_ce/vue_merge_request_widget/components/approvals/queries/approved_by.query.graphql';
Vue.use(VueApollo);
describe('MRWidget approvals summary', () => {
- const originalUserId = gon.current_user_id;
let wrapper;
- const createComponent = (response = approvedByCurrentUser) => {
+ const createComponent = (data = approvedByCurrentUser) => {
wrapper = mount(ApprovalsSummary, {
propsData: {
- projectPath: 'gitlab-org/gitlab',
- iid: '1',
+ approvalState: data.data.project.mergeRequest,
},
- apolloProvider: createMockApollo([[approvedByQuery, jest.fn().mockResolvedValue(response)]]),
});
};
const findAvatars = () => wrapper.findComponent(UserAvatarList);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- gon.current_user_id = originalUserId;
- });
-
describe('when approved', () => {
beforeEach(async () => {
createComponent();
diff --git a/spec/frontend/vue_merge_request_widget/components/artifacts_list_app_spec.js b/spec/frontend/vue_merge_request_widget/components/artifacts_list_app_spec.js
index 52e2393bf05..332f14a1721 100644
--- a/spec/frontend/vue_merge_request_widget/components/artifacts_list_app_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/artifacts_list_app_spec.js
@@ -26,7 +26,6 @@ describe('Merge Requests Artifacts list app', () => {
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
});
diff --git a/spec/frontend/vue_merge_request_widget/components/artifacts_list_spec.js b/spec/frontend/vue_merge_request_widget/components/artifacts_list_spec.js
index b7bf72cd215..bb049a5d52f 100644
--- a/spec/frontend/vue_merge_request_widget/components/artifacts_list_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/artifacts_list_spec.js
@@ -18,10 +18,6 @@ describe('Artifacts List', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
beforeEach(() => {
mountComponent(data);
});
diff --git a/spec/frontend/vue_merge_request_widget/components/extensions/child_content_spec.js b/spec/frontend/vue_merge_request_widget/components/extensions/child_content_spec.js
index 198a4c2823a..3a621db7b44 100644
--- a/spec/frontend/vue_merge_request_widget/components/extensions/child_content_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/extensions/child_content_spec.js
@@ -20,11 +20,6 @@ function factory(propsData) {
}
describe('MR widget extension child content', () => {
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders child components', () => {
factory({
data: {
diff --git a/spec/frontend/vue_merge_request_widget/components/extensions/status_icon_spec.js b/spec/frontend/vue_merge_request_widget/components/extensions/status_icon_spec.js
index f3aa5bb774f..ffa6b5538d3 100644
--- a/spec/frontend/vue_merge_request_widget/components/extensions/status_icon_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/extensions/status_icon_spec.js
@@ -11,10 +11,6 @@ function factory(propsData = {}) {
}
describe('MR widget extensions status icon', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders loading icon', () => {
factory({ name: 'test', isLoading: true, iconName: 'failed' });
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_collapsible_extension_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_collapsible_extension_spec.js
index 81f266d8070..6b22c2e26ac 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_collapsible_extension_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_collapsible_extension_spec.js
@@ -25,10 +25,6 @@ describe('Merge Request Collapsible Extension', () => {
const findErrorMessage = () => wrapper.find('.js-error-state');
const findIcon = () => wrapper.findComponent(GlIcon);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('while collapsed', () => {
beforeEach(() => {
mountComponent(data);
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_alert_message_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_alert_message_spec.js
index 5d923d0383f..01178dab9bb 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_alert_message_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_alert_message_spec.js
@@ -11,10 +11,6 @@ function createComponent(propsData = {}) {
}
describe('MrWidgetAlertMessage', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render a GlAert', () => {
createComponent({ type: 'danger' });
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_author_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_author_spec.js
index 8a42e2e2ce7..7eafccae083 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_author_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_author_spec.js
@@ -29,7 +29,6 @@ describe('MrWidgetAuthor', () => {
});
afterEach(() => {
- wrapper.destroy();
window.gl = oldWindowGl;
});
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_author_time_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_author_time_spec.js
index 90a29d15488..534b745aed2 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_author_time_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_author_time_spec.js
@@ -23,10 +23,6 @@ describe('MrWidgetAuthorTime', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders provided action text', () => {
expect(wrapper.text()).toContain('Merged by');
});
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_container_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_container_spec.js
index 8dadb0c65d0..25de76ba33c 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_container_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_container_spec.js
@@ -13,10 +13,6 @@ describe('MrWidgetContainer', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('has layout', () => {
factory();
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_icon_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_icon_spec.js
index 6a9b019fb4f..090a96d576c 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_icon_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_icon_spec.js
@@ -15,10 +15,6 @@ describe('MrWidgetIcon', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders icon and container', () => {
expect(wrapper.element.className).toContain('circle-icon-container');
expect(wrapper.findComponent(GlIcon).props('name')).toEqual(TEST_ICON);
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_container_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_container_spec.js
index 13beb43e10b..18842e996de 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_container_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_container_spec.js
@@ -29,10 +29,6 @@ describe('MrWidgetPipelineContainer', () => {
mock.onGet().reply(HTTP_STATUS_OK, {});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findDeploymentList = () => wrapper.findComponent(DeploymentList);
const findCIErrorMessage = () => wrapper.findByTestId('ci-error-message');
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js
index ec047fe0714..f284ec98a73 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_rebase_spec.js
@@ -1,3 +1,4 @@
+import { GlModal } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import WidgetRebase from '~/vue_merge_request_widget/components/states/mr_widget_rebase.vue';
@@ -8,8 +9,11 @@ jest.mock('~/vue_shared/plugins/global_toast');
let wrapper;
-function createWrapper(propsData) {
+function createWrapper(propsData, provideData) {
wrapper = mount(WidgetRebase, {
+ provide: {
+ ...provideData,
+ },
propsData,
data() {
return {
@@ -19,6 +23,7 @@ function createWrapper(propsData) {
userPermissions: {
pushToSourceBranch: propsData.mr.canPushToSourceBranch,
},
+ pipelines: propsData.mr.pipelines,
},
};
},
@@ -37,11 +42,8 @@ describe('Merge request widget rebase component', () => {
const findRebaseMessageText = () => findRebaseMessage().text();
const findStandardRebaseButton = () => wrapper.find('[data-testid="standard-rebase-button"]');
const findRebaseWithoutCiButton = () => wrapper.find('[data-testid="rebase-without-ci-button"]');
+ const findModal = () => wrapper.findComponent(GlModal);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
describe('while rebasing', () => {
it('should show progress message', () => {
createWrapper({
@@ -199,6 +201,72 @@ describe('Merge request widget rebase component', () => {
expect(rebaseMock).toHaveBeenCalledWith({ skipCi: true });
});
});
+
+ describe('security modal', () => {
+ it('displays modal and rebases after confirming', () => {
+ createWrapper(
+ {
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: true,
+ sourceProjectFullPath: 'user/forked',
+ targetProjectFullPath: 'root/original',
+ pipelines: {
+ nodes: [
+ {
+ id: '1',
+ project: {
+ id: '2',
+ fullPath: 'user/forked',
+ },
+ },
+ ],
+ },
+ },
+ service: {
+ rebase: rebaseMock,
+ poll: pollMock,
+ },
+ },
+ { canCreatePipelineInTargetProject: true },
+ );
+
+ findModal().vm.show = jest.fn();
+
+ findStandardRebaseButton().vm.$emit('click');
+
+ expect(findModal().vm.show).toHaveBeenCalled();
+
+ findModal().vm.$emit('primary');
+
+ expect(rebaseMock).toHaveBeenCalled();
+ });
+
+ it('does not display modal', () => {
+ createWrapper(
+ {
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: true,
+ sourceProjectFullPath: 'user/forked',
+ targetProjectFullPath: 'root/original',
+ },
+ service: {
+ rebase: rebaseMock,
+ poll: pollMock,
+ },
+ },
+ { canCreatePipelineInTargetProject: false },
+ );
+
+ findModal().vm.show = jest.fn();
+
+ findStandardRebaseButton().vm.$emit('click');
+
+ expect(findModal().vm.show).not.toHaveBeenCalled();
+ expect(rebaseMock).toHaveBeenCalled();
+ });
+ });
});
describe('without permissions', () => {
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_related_links_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_related_links_spec.js
index 15522f7ac1d..42a16090510 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_related_links_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_related_links_spec.js
@@ -9,10 +9,6 @@ describe('MRWidgetRelatedLinks', () => {
wrapper = shallowMount(RelatedLinks, { propsData });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('computed', () => {
describe('closesText', () => {
it('returns Closes text for open merge request', () => {
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js
index 530549b7b9c..b210327aa31 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_status_icon_spec.js
@@ -17,11 +17,6 @@ describe('MR widget status icon component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('while loading', () => {
it('renders loading icon', () => {
createWrapper({ status: 'loading' });
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_suggest_pipeline_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_suggest_pipeline_spec.js
index 73358edee78..70c76687a79 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_suggest_pipeline_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_suggest_pipeline_spec.js
@@ -18,10 +18,6 @@ describe('MRWidgetSuggestPipeline', () => {
describe('template', () => {
let wrapper;
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('core functionality', () => {
const findOkBtn = () => wrapper.find('[data-testid="ok"]');
let trackingSpy;
diff --git a/spec/frontend/vue_merge_request_widget/components/review_app_link_spec.js b/spec/frontend/vue_merge_request_widget/components/review_app_link_spec.js
index e393b56034d..48484551d59 100644
--- a/spec/frontend/vue_merge_request_widget/components/review_app_link_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/review_app_link_spec.js
@@ -17,10 +17,6 @@ describe('review app link', () => {
wrapper = shallowMount(ReviewAppLink, { propsData: props });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders provided link as href attribute', () => {
expect(wrapper.attributes('href')).toBe(props.link);
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/commit_edit_spec.js b/spec/frontend/vue_merge_request_widget/components/states/commit_edit_spec.js
index c0add94e6ed..f520c6a4f78 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/commit_edit_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/commit_edit_spec.js
@@ -26,10 +26,6 @@ describe('Commits edit component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findTextarea = () => wrapper.find('.form-control');
it('has a correct label', () => {
diff --git a/spec/frontend/vue_merge_request_widget/components/states/merge_checks_failed_spec.js b/spec/frontend/vue_merge_request_widget/components/states/merge_checks_failed_spec.js
index e4448346685..c2ab0e384e8 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/merge_checks_failed_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/merge_checks_failed_spec.js
@@ -12,10 +12,6 @@ function factory(propsData = {}) {
}
describe('Merge request widget merge checks failed state component', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
mrState | displayText
${{ approvals: true, isApproved: false }} | ${'approvalNeeded'}
diff --git a/spec/frontend/vue_merge_request_widget/components/states/merge_failed_pipeline_confirmation_dialog_spec.js b/spec/frontend/vue_merge_request_widget/components/states/merge_failed_pipeline_confirmation_dialog_spec.js
index c9aca01083d..7d471b91c37 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/merge_failed_pipeline_confirmation_dialog_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/merge_failed_pipeline_confirmation_dialog_spec.js
@@ -37,10 +37,6 @@ describe('MergeFailedPipelineConfirmationDialog', () => {
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?',
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js
index 08700e834d7..3e18ee75125 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_archived_spec.js
@@ -10,11 +10,6 @@ describe('MRWidgetArchived', () => {
wrapper = shallowMount(archivedComponent, { propsData: { mr: {} } });
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders error icon', () => {
expect(wrapper.findComponent(StateContainer).exists()).toBe(true);
expect(wrapper.findComponent(StateContainer).props().status).toBe('failed');
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js
index fef5fee5f19..65d170cae8b 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled_spec.js
@@ -83,8 +83,6 @@ describe('MRWidgetAutoMergeEnabled', () => {
afterEach(() => {
window.gl = oldWindowGl;
- wrapper.destroy();
- wrapper = null;
});
describe('computed', () => {
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed_spec.js
index 826f708069c..9b043bda72d 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed_spec.js
@@ -18,10 +18,6 @@ describe('MRWidgetAutoMergeFailed', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
beforeEach(() => {
createComponent({
mr: { mergeError },
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_checking_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_checking_spec.js
index ac18ccf9e26..6c3b7f76fe6 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_checking_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_checking_spec.js
@@ -9,11 +9,6 @@ describe('MRWidgetChecking', () => {
wrapper = shallowMount(CheckingComponent, { propsData: { mr: {} } });
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders loading icon', () => {
expect(wrapper.findComponent(StateContainer).exists()).toBe(true);
expect(wrapper.findComponent(StateContainer).props().status).toBe('loading');
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commit_message_dropdown_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commit_message_dropdown_spec.js
index 5d2d1fdd6f1..e4febda1daa 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commit_message_dropdown_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commit_message_dropdown_spec.js
@@ -36,10 +36,6 @@ describe('Commits message dropdown component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findDropdownElements = () => wrapper.findAllComponents(GlDropdownItem);
const findFirstDropdownElement = () => findDropdownElements().at(0);
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commits_header_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commits_header_spec.js
index a6d3a6286a7..b3843b066df 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commits_header_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_commits_header_spec.js
@@ -21,10 +21,6 @@ describe('Commits header component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findHeaderWrapper = () => wrapper.find('.js-mr-widget-commits-count');
const findCommitToggle = () => wrapper.find('.commit-edit-toggle');
const findTargetBranchMessage = () => wrapper.find('.label-branch');
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js
index 2ca9dc61745..7f0a171d712 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_conflicts_spec.js
@@ -50,10 +50,6 @@ describe('MRWidgetConflicts', () => {
await nextTick();
}
- afterEach(() => {
- wrapper.destroy();
- });
-
// There are two permissions we need to consider:
//
// 1. Is the user allowed to merge to the target branch?
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_failed_to_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_failed_to_merge_spec.js
index 833fa27d453..38e5422325a 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_failed_to_merge_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_failed_to_merge_spec.js
@@ -28,10 +28,6 @@ describe('MRWidgetFailedToMerge', () => {
jest.spyOn(window, 'clearInterval').mockImplementation();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('interval', () => {
it('sets interval to refresh', () => {
createComponent();
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merged_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merged_spec.js
index a3aa563b516..e44e2834a0e 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merged_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merged_spec.js
@@ -62,10 +62,6 @@ describe('MRWidgetMerged', () => {
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findButtonByText = (text) =>
wrapper.findAll('button').wrappers.find((w) => w.text() === text);
const findRemoveSourceBranchButton = () => findButtonByText('Delete source branch');
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js
index 5408f731b34..ca75ca11e5b 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_merging_spec.js
@@ -29,10 +29,6 @@ describe('MRWidgetMerging', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders information about merge request being merged', () => {
const message = wrapper.findComponent(BoldText).props('message');
expect(message).toContain('Merging!');
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_missing_branch_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_missing_branch_spec.js
index f29cf55f7ce..fca25b8bb94 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_missing_branch_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_missing_branch_spec.js
@@ -15,10 +15,6 @@ function factory(sourceBranchRemoved) {
}
describe('MRWidgetMissingBranch', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
sourceBranchRemoved | branchName
${true} | ${'source'}
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js
index 42515c597c5..40b053282de 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_not_allowed_spec.js
@@ -10,11 +10,6 @@ describe('MRWidgetNotAllowed', () => {
wrapper = shallowMount(notAllowedComponent);
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders success icon', () => {
expect(wrapper.findComponent(StatusIcon).exists()).toBe(true);
expect(wrapper.findComponent(StatusIcon).props().status).toBe('success');
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge_spec.js
index 6de0c06c33d..c8fa1399dcb 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_nothing_to_merge_spec.js
@@ -1,28 +1,58 @@
-import Vue, { nextTick } from 'vue';
+import { GlSprintf } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import NothingToMerge from '~/vue_merge_request_widget/components/states/nothing_to_merge.vue';
describe('NothingToMerge', () => {
- describe('template', () => {
- const Component = Vue.extend(NothingToMerge);
- const newBlobPath = '/foo';
- const vm = new Component({
- el: document.createElement('div'),
+ let wrapper;
+ const newBlobPath = '/foo';
+
+ const defaultProps = {
+ mr: {
+ newBlobPath,
+ },
+ };
+
+ const createComponent = (props = defaultProps) => {
+ wrapper = shallowMountExtended(NothingToMerge, {
propsData: {
- mr: { newBlobPath },
+ ...props,
+ },
+ stubs: {
+ GlSprintf,
},
});
+ };
+
+ const findCreateButton = () => wrapper.findByTestId('createFileButton');
+ const findNothingToMergeTextBody = () => wrapper.findByTestId('nothing-to-merge-body');
+
+ describe('With Blob link', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('shows the component with the correct text and highlights', () => {
+ expect(wrapper.text()).toContain('This merge request contains no changes.');
+ expect(findNothingToMergeTextBody().text()).toContain(
+ 'Use merge requests to propose changes to your project and discuss them with your team. To make changes, push a commit or edit this merge request to use a different branch.',
+ );
+ });
+
+ it('shows the Create file button with the correct attributes', () => {
+ const createButton = findCreateButton();
+
+ expect(createButton.exists()).toBe(true);
+ expect(createButton.attributes('href')).toBe(newBlobPath);
+ });
+ });
- it('should have correct elements', () => {
- expect(vm.$el.classList.contains('mr-widget-body')).toBe(true);
- expect(vm.$el.querySelector('[data-testid="createFileButton"]').href).toContain(newBlobPath);
- expect(vm.$el.innerText).toContain('Use merge requests to propose changes to your project');
+ describe('Without Blob link', () => {
+ beforeEach(() => {
+ createComponent({ mr: { newBlobPath: '' } });
});
- it('should not show new blob link if there is no link available', () => {
- vm.mr.newBlobPath = null;
- nextTick(() => {
- expect(vm.$el.querySelector('[data-testid="createFileButton"]')).toEqual(null);
- });
+ it('does not show the Create file button', () => {
+ expect(findCreateButton().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js
index c0197b5e20a..d99106df0a2 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked_spec.js
@@ -10,11 +10,6 @@ describe('MRWidgetPipelineBlocked', () => {
wrapper = shallowMount(PipelineBlockedComponent);
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders error icon', () => {
expect(wrapper.findComponent(StatusIcon).exists()).toBe(true);
expect(wrapper.findComponent(StatusIcon).props().status).toBe('failed');
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js
index 8bae2b62ed1..ea93463f3ab 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_pipeline_failed_spec.js
@@ -19,11 +19,6 @@ describe('PipelineFailed', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should render error status icon', () => {
createComponent();
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_sha_mismatch_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_sha_mismatch_spec.js
index aaa4591d67d..02b71ebf183 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_sha_mismatch_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_sha_mismatch_spec.js
@@ -20,10 +20,6 @@ describe('ShaMismatch', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render warning message', () => {
expect(wrapper.text()).toContain('Merge blocked: new changes were just added.');
});
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_squash_before_merge_spec.js
index c839fa17fe5..97f8e695df9 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_squash_before_merge_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_squash_before_merge_spec.js
@@ -14,10 +14,6 @@ describe('Squash before merge component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findCheckbox = () => wrapper.findComponent(GlFormCheckbox);
describe('checkbox', () => {
diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions_spec.js
index c97b42f61ac..58b9f162815 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions_spec.js
@@ -21,10 +21,6 @@ describe('UnresolvedDiscussions', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('triggers the correct notes event when the jump to first unresolved discussion button is clicked', () => {
jest.spyOn(notesEventHub, '$emit');
@@ -38,10 +34,6 @@ describe('UnresolvedDiscussions', () => {
wrapper = createComponent({ path: TEST_HOST });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should have correct elements', () => {
const text = removeBreakLine(wrapper.text()).trim();
expect(text).toContain('Merge blocked:');
diff --git a/spec/frontend/vue_merge_request_widget/components/states/new_ready_to_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/new_ready_to_merge_spec.js
index 5ec9654a4af..20d06a7aaee 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/new_ready_to_merge_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/new_ready_to_merge_spec.js
@@ -15,10 +15,6 @@ function factory({ canMerge }) {
}
describe('New ready to merge state component', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
canMerge
${true}
diff --git a/spec/frontend/vue_merge_request_widget/components/states/work_in_progress_spec.js b/spec/frontend/vue_merge_request_widget/components/states/work_in_progress_spec.js
index e610ceb2122..43ce1769ff3 100644
--- a/spec/frontend/vue_merge_request_widget/components/states/work_in_progress_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/states/work_in_progress_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import getStateQueryResponse from 'test_fixtures/graphql/merge_requests/get_state.query.graphql.json';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import WorkInProgress, {
MSG_SOMETHING_WENT_WRONG,
MSG_MARK_READY,
@@ -22,7 +22,7 @@ const TEST_MR_IID = '23';
const TEST_MR_TITLE = 'Test MR Title';
const TEST_PROJECT_PATH = 'lorem/ipsum';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/merge_request');
describe('~/vue_merge_request_widget/components/states/work_in_progress.vue', () => {
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/action_buttons_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/action_buttons_spec.js
index 366ea113162..adefce9060c 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/action_buttons_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/widget/action_buttons_spec.js
@@ -11,10 +11,6 @@ function factory(propsData = {}) {
}
describe('~/vue_merge_request_widget/components/widget/action_buttons.vue', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('tertiaryButtons', () => {
it('renders buttons', () => {
factory({
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js
index 973866176c2..5887670a58d 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/widget/widget_spec.js
@@ -50,10 +50,6 @@ describe('~/vue_merge_request_widget/components/widget/widget.vue', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('on mount', () => {
it('fetches collapsed', async () => {
const fetchCollapsedData = jest
diff --git a/spec/frontend/vue_merge_request_widget/deployment/deployment_action_button_spec.js b/spec/frontend/vue_merge_request_widget/deployment/deployment_action_button_spec.js
index 1bad5dacefa..785515ae846 100644
--- a/spec/frontend/vue_merge_request_widget/deployment/deployment_action_button_spec.js
+++ b/spec/frontend/vue_merge_request_widget/deployment/deployment_action_button_spec.js
@@ -25,10 +25,6 @@ describe('Deployment action button', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when passed only icon via props', () => {
beforeEach(() => {
factory({
diff --git a/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js b/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js
index 41df485b0de..1fdbbadf8b0 100644
--- a/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js
+++ b/spec/frontend/vue_merge_request_widget/deployment/deployment_actions_spec.js
@@ -1,6 +1,6 @@
import { mount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import { visitUrl } from '~/lib/utils/url_utility';
import {
@@ -21,7 +21,7 @@ import {
retryDetails,
} from './deployment_mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility');
jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal');
@@ -54,7 +54,6 @@ describe('DeploymentAction component', () => {
});
afterEach(() => {
- wrapper.destroy();
confirmAction.mockReset();
});
diff --git a/spec/frontend/vue_merge_request_widget/deployment/deployment_list_spec.js b/spec/frontend/vue_merge_request_widget/deployment/deployment_list_spec.js
index 948d7ebab5e..77dac4204db 100644
--- a/spec/frontend/vue_merge_request_widget/deployment/deployment_list_spec.js
+++ b/spec/frontend/vue_merge_request_widget/deployment/deployment_list_spec.js
@@ -28,7 +28,6 @@ describe('~/vue_merge_request_widget/components/deployment/deployment_list.vue',
afterEach(() => {
wrapper?.destroy?.();
- wrapper = null;
});
describe('with few deployments', () => {
diff --git a/spec/frontend/vue_merge_request_widget/deployment/deployment_spec.js b/spec/frontend/vue_merge_request_widget/deployment/deployment_spec.js
index f310f7669a9..74122f47ad3 100644
--- a/spec/frontend/vue_merge_request_widget/deployment/deployment_spec.js
+++ b/spec/frontend/vue_merge_request_widget/deployment/deployment_spec.js
@@ -32,10 +32,6 @@ describe('Deployment component', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('always renders DeploymentInfo', () => {
expect(wrapper.findComponent(DeploymentInfo).exists()).toBe(true);
});
diff --git a/spec/frontend/vue_merge_request_widget/deployment/deployment_view_button_spec.js b/spec/frontend/vue_merge_request_widget/deployment/deployment_view_button_spec.js
index 8994fa522d0..7a151c26934 100644
--- a/spec/frontend/vue_merge_request_widget/deployment/deployment_view_button_spec.js
+++ b/spec/frontend/vue_merge_request_widget/deployment/deployment_view_button_spec.js
@@ -28,10 +28,6 @@ describe('Deployment View App button', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findReviewAppLink = () => wrapper.findComponent(ReviewAppLink);
const findMrWigdetDeploymentDropdown = () => wrapper.findComponent(GlDropdown);
const findMrWigdetDeploymentDropdownIcon = () =>
diff --git a/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js b/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js
index 548b68bc103..d2d622d0534 100644
--- a/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js
+++ b/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js
@@ -73,7 +73,6 @@ describe('Test report extension', () => {
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
});
diff --git a/spec/frontend/vue_merge_request_widget/extentions/accessibility/index_spec.js b/spec/frontend/vue_merge_request_widget/extentions/accessibility/index_spec.js
index 01049e54a7f..40158917f52 100644
--- a/spec/frontend/vue_merge_request_widget/extentions/accessibility/index_spec.js
+++ b/spec/frontend/vue_merge_request_widget/extentions/accessibility/index_spec.js
@@ -39,7 +39,6 @@ describe('Accessibility extension', () => {
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
});
diff --git a/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js b/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js
index 67b327217ef..4b7870842bd 100644
--- a/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js
+++ b/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js
@@ -61,7 +61,6 @@ describe('Code Quality extension', () => {
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
});
diff --git a/spec/frontend/vue_merge_request_widget/extentions/terraform/index_spec.js b/spec/frontend/vue_merge_request_widget/extentions/terraform/index_spec.js
index 13384e1efca..52a244107bd 100644
--- a/spec/frontend/vue_merge_request_widget/extentions/terraform/index_spec.js
+++ b/spec/frontend/vue_merge_request_widget/extentions/terraform/index_spec.js
@@ -48,7 +48,6 @@ describe('Terraform extension', () => {
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
});
diff --git a/spec/frontend/vue_merge_request_widget/mr_widget_how_to_merge_modal_spec.js b/spec/frontend/vue_merge_request_widget/mr_widget_how_to_merge_modal_spec.js
index 015d394312a..20f1796008a 100644
--- a/spec/frontend/vue_merge_request_widget/mr_widget_how_to_merge_modal_spec.js
+++ b/spec/frontend/vue_merge_request_widget/mr_widget_how_to_merge_modal_spec.js
@@ -15,11 +15,6 @@ describe('MRWidgetHowToMerge', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
beforeEach(() => {
mountComponent();
});
diff --git a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
index f37276ad594..fad501ee7f5 100644
--- a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
@@ -1,9 +1,10 @@
import { GlBadge, GlLink, GlIcon, GlButton, GlDropdown } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
+import { mount, shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import * as Sentry from '@sentry/browser';
+import approvedByCurrentUser from 'test_fixtures/graphql/merge_requests/approvals/approvals.query.graphql.json';
import getStateQueryResponse from 'test_fixtures/graphql/merge_requests/get_state.query.graphql.json';
import readyToMergeResponse from 'test_fixtures/graphql/merge_requests/states/ready_to_merge.query.graphql.json';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -20,6 +21,7 @@ import {
registerExtension,
registeredExtensions,
} from '~/vue_merge_request_widget/components/extensions';
+import { STATE_QUERY_POLLING_INTERVAL_BACKOFF } from '~/vue_merge_request_widget/constants';
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
import eventHub from '~/vue_merge_request_widget/event_hub';
import MrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
@@ -28,6 +30,7 @@ import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_
import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql';
import getStateQuery from '~/vue_merge_request_widget/queries/get_state.query.graphql';
import readyToMergeQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql';
+import approvalsQuery from 'ee_else_ce/vue_merge_request_widget/components/approvals/queries/approvals.query.graphql';
import userPermissionsQuery from '~/vue_merge_request_widget/queries/permissions.query.graphql';
import conflictsStateQuery from '~/vue_merge_request_widget/queries/states/conflicts.query.graphql';
import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data';
@@ -60,6 +63,8 @@ jest.mock('@sentry/browser', () => ({
Vue.use(VueApollo);
describe('MrWidgetOptions', () => {
+ let stateQueryHandler;
+ let queryResponse;
let wrapper;
let mock;
@@ -83,37 +88,41 @@ describe('MrWidgetOptions', () => {
afterEach(() => {
mock.restore();
+ // eslint-disable-next-line @gitlab/vtu-no-explicit-wrapper-destroy
wrapper.destroy();
-
gl.mrWidgetData = {};
- gon.features = {};
});
- const createComponent = (mrData = mockData, options = {}) => {
- wrapper = mount(MrWidgetOptions, {
+ const createComponent = (mrData = mockData, options = {}, data = {}, fullMount = true) => {
+ const mounting = fullMount ? mount : shallowMount;
+
+ queryResponse = {
+ data: {
+ project: {
+ ...getStateQueryResponse.data.project,
+ mergeRequest: {
+ ...getStateQueryResponse.data.project.mergeRequest,
+ mergeError: mrData.mergeError || null,
+ },
+ },
+ },
+ };
+ stateQueryHandler = jest.fn().mockResolvedValue(queryResponse);
+ wrapper = mounting(MrWidgetOptions, {
propsData: {
mrData: { ...mrData },
},
data() {
- return { loading: false };
+ return {
+ loading: false,
+ ...data,
+ };
},
...options,
apolloProvider: createMockApollo([
- [
- getStateQuery,
- jest.fn().mockResolvedValue({
- data: {
- project: {
- ...getStateQueryResponse.data.project,
- mergeRequest: {
- ...getStateQueryResponse.data.project.mergeRequest,
- mergeError: mrData.mergeError || null,
- },
- },
- },
- }),
- ],
+ [approvalsQuery, jest.fn().mockResolvedValue(approvedByCurrentUser)],
+ [getStateQuery, stateQueryHandler],
[readyToMergeQuery, jest.fn().mockResolvedValue(readyToMergeResponse)],
[
userPermissionsQuery,
@@ -351,18 +360,6 @@ describe('MrWidgetOptions', () => {
});
});
- describe('initPolling', () => {
- it('should call SmartInterval', () => {
- wrapper.vm.initPolling();
-
- expect(SmartInterval).toHaveBeenCalledWith(
- expect.objectContaining({
- callback: wrapper.vm.checkStatus,
- }),
- );
- });
- });
-
describe('initDeploymentsPolling', () => {
it('should call SmartInterval', () => {
wrapper.vm.initDeploymentsPolling();
@@ -529,23 +526,64 @@ describe('MrWidgetOptions', () => {
});
});
- describe('resumePolling', () => {
- it('should call stopTimer on pollingInterval', () => {
- jest.spyOn(wrapper.vm.pollingInterval, 'resume').mockImplementation(() => {});
+ describe('Apollo query', () => {
+ const interval = 5;
+ const data = 'foo';
+ const mockCheckStatus = jest.fn().mockResolvedValue({ data });
+ const mockSetGraphqlData = jest.fn();
+ const mockSetData = jest.fn();
- wrapper.vm.resumePolling();
+ beforeEach(() => {
+ wrapper.destroy();
+
+ return createComponent(
+ mockData,
+ {},
+ {
+ pollInterval: interval,
+ startingPollInterval: interval,
+ mr: {
+ setData: mockSetData,
+ setGraphqlData: mockSetGraphqlData,
+ },
+ service: {
+ checkStatus: mockCheckStatus,
+ },
+ },
+ false,
+ );
+ });
- expect(wrapper.vm.pollingInterval.resume).toHaveBeenCalled();
+ describe('normal polling behavior', () => {
+ it('responds to the GraphQL query finishing', () => {
+ expect(mockSetGraphqlData).toHaveBeenCalledWith(queryResponse.data.project);
+ expect(mockCheckStatus).toHaveBeenCalled();
+ expect(mockSetData).toHaveBeenCalledWith(data, undefined);
+ expect(stateQueryHandler).toHaveBeenCalledTimes(1);
+ });
});
- });
- describe('stopPolling', () => {
- it('should call stopTimer on pollingInterval', () => {
- jest.spyOn(wrapper.vm.pollingInterval, 'stopTimer').mockImplementation(() => {});
+ describe('external event control', () => {
+ describe('enablePolling', () => {
+ it('enables the Apollo query polling using the event hub', () => {
+ eventHub.$emit('EnablePolling');
+
+ expect(stateQueryHandler).toHaveBeenCalled();
+ jest.advanceTimersByTime(interval * STATE_QUERY_POLLING_INTERVAL_BACKOFF);
+ expect(stateQueryHandler).toHaveBeenCalledTimes(2);
+ });
+ });
+
+ describe('disablePolling', () => {
+ it('disables the Apollo query polling using the event hub', () => {
+ expect(stateQueryHandler).toHaveBeenCalledTimes(1);
- wrapper.vm.stopPolling();
+ eventHub.$emit('DisablePolling');
+ jest.advanceTimersByTime(interval * STATE_QUERY_POLLING_INTERVAL_BACKOFF);
- expect(wrapper.vm.pollingInterval.stopTimer).toHaveBeenCalled();
+ expect(stateQueryHandler).toHaveBeenCalledTimes(1); // no additional polling after a real interval timeout
+ });
+ });
});
});
});
@@ -890,11 +928,7 @@ describe('MrWidgetOptions', () => {
});
describe('mock extension', () => {
- let pollRequest;
-
beforeEach(() => {
- pollRequest = jest.spyOn(Poll.prototype, 'makeRequest');
-
registerExtension(workingExtension());
createComponent();
@@ -945,10 +979,6 @@ describe('MrWidgetOptions', () => {
expect(collapsedSection.findComponent(GlButton).exists()).toBe(true);
expect(collapsedSection.findComponent(GlButton).text()).toBe('Full report');
});
-
- it('extension polling is not called if enablePolling flag is not passed', () => {
- expect(pollRequest).toHaveBeenCalledTimes(0);
- });
});
describe('expansion', () => {
@@ -1235,10 +1265,6 @@ describe('MrWidgetOptions', () => {
});
describe('widget container', () => {
- afterEach(() => {
- delete window.gon.features.refactorSecurityExtension;
- });
-
it('should not be displayed when the refactor_security_extension feature flag is turned off', () => {
createComponent();
expect(findWidgetContainer().exists()).toBe(false);
diff --git a/spec/frontend/vue_merge_request_widget/stores/get_state_key_spec.js b/spec/frontend/vue_merge_request_widget/stores/get_state_key_spec.js
index 88d9d0b4cff..a6288b9c725 100644
--- a/spec/frontend/vue_merge_request_widget/stores/get_state_key_spec.js
+++ b/spec/frontend/vue_merge_request_widget/stores/get_state_key_spec.js
@@ -20,7 +20,7 @@ describe('getStateKey', () => {
};
const bound = getStateKey.bind(context);
- expect(bound()).toEqual(null);
+ expect(bound()).toEqual('checking');
context.detailedMergeStatus = 'MERGEABLE';
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 12c5c190e26..217103ab25c 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
@@ -32,10 +32,6 @@ describe('Alert Details Sidebar To Do', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
const findToDoButton = () => wrapper.find('[data-testid="alert-todo-button"]');
describe('updating the alert to do', () => {
diff --git a/spec/frontend/vue_shared/components/__snapshots__/file_row_header_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/file_row_header_spec.js.snap
deleted file mode 100644
index ca9d4488870..00000000000
--- a/spec/frontend/vue_shared/components/__snapshots__/file_row_header_spec.js.snap
+++ /dev/null
@@ -1,40 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`File row header component adds multiple ellipsises after 40 characters 1`] = `
-<div
- class="file-row-header bg-white sticky-top p-2 js-file-row-header"
- title="app/assets/javascripts/merge_requests/widget/diffs/notes"
->
- <gl-truncate-stub
- class="bold"
- position="middle"
- text="app/assets/javascripts/merge_requests/widget/diffs/notes"
- />
-</div>
-`;
-
-exports[`File row header component renders file path 1`] = `
-<div
- class="file-row-header bg-white sticky-top p-2 js-file-row-header"
- title="app/assets"
->
- <gl-truncate-stub
- class="bold"
- position="middle"
- text="app/assets"
- />
-</div>
-`;
-
-exports[`File row header component trucates path after 40 characters 1`] = `
-<div
- class="file-row-header bg-white sticky-top p-2 js-file-row-header"
- title="app/assets/javascripts/merge_requests"
->
- <gl-truncate-stub
- class="bold"
- position="middle"
- text="app/assets/javascripts/merge_requests"
- />
-</div>
-`;
diff --git a/spec/frontend/vue_shared/components/actions_button_spec.js b/spec/frontend/vue_shared/components/actions_button_spec.js
index f3fb840b270..8c2f2b52f8e 100644
--- a/spec/frontend/vue_shared/components/actions_button_spec.js
+++ b/spec/frontend/vue_shared/components/actions_button_spec.js
@@ -34,10 +34,6 @@ describe('Actions button component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
const findButton = () => wrapper.findComponent(GlButton);
const findTooltip = () => wrapper.findComponent(GlTooltip);
const findDropdown = () => wrapper.findComponent(GlDropdown);
diff --git a/spec/frontend/vue_shared/components/alert_details_table_spec.js b/spec/frontend/vue_shared/components/alert_details_table_spec.js
index 8a9ee4699bd..8e7a10c4d77 100644
--- a/spec/frontend/vue_shared/components/alert_details_table_spec.js
+++ b/spec/frontend/vue_shared/components/alert_details_table_spec.js
@@ -41,11 +41,6 @@ describe('AlertDetails', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findTableComponent = () => wrapper.findComponent(GlTable);
const findTableKeys = () => findTableComponent().findAll('tbody td:first-child');
const findTableFieldValueByKey = (fieldKey) =>
diff --git a/spec/frontend/vue_shared/components/awards_list_spec.js b/spec/frontend/vue_shared/components/awards_list_spec.js
index c7f9d8fd8d5..da5516f8db1 100644
--- a/spec/frontend/vue_shared/components/awards_list_spec.js
+++ b/spec/frontend/vue_shared/components/awards_list_spec.js
@@ -64,16 +64,7 @@ const REACTION_CONTROL_CLASSES = [
describe('vue_shared/components/awards_list', () => {
let wrapper;
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const createComponent = (props = {}) => {
- if (wrapper) {
- throw new Error('There should only be one wrapper created per test');
- }
-
wrapper = mount(AwardsList, { propsData: props });
};
const matchingEmojiTag = (name) => expect.stringMatching(`gl-emoji data-name="${name}"`);
@@ -98,7 +89,6 @@ describe('vue_shared/components/awards_list', () => {
addButtonClass: TEST_ADD_BUTTON_CLASS,
});
});
-
it('shows awards in correct order', () => {
expect(findAwardsData()).toEqual([
{
diff --git a/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js b/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js
index ce7fd40937f..6acd1f51a86 100644
--- a/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js
@@ -24,10 +24,6 @@ describe('Blob Rich Viewer component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the passed content without transformations', () => {
expect(wrapper.html()).toContain(content);
});
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 4b44311b253..a480e0869e8 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
@@ -23,10 +23,6 @@ describe('Blob Simple Viewer component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('does not fail if content is empty', () => {
const spy = jest.spyOn(window.console, 'error');
createComponent('');
diff --git a/spec/frontend/vue_shared/components/changed_file_icon_spec.js b/spec/frontend/vue_shared/components/changed_file_icon_spec.js
index ea708b6f3fe..d1b1e58f5d7 100644
--- a/spec/frontend/vue_shared/components/changed_file_icon_spec.js
+++ b/spec/frontend/vue_shared/components/changed_file_icon_spec.js
@@ -21,10 +21,6 @@ describe('Changed file icon', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findIcon = () => wrapper.findComponent(GlIcon);
const findIconName = () => findIcon().props('name');
const findIconClasses = () => findIcon().classes();
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 6932a812287..2a40511affb 100644
--- a/spec/frontend/vue_shared/components/chronic_duration_input_spec.js
+++ b/spec/frontend/vue_shared/components/chronic_duration_input_spec.js
@@ -10,8 +10,6 @@ describe('vue_shared/components/chronic_duration_input', () => {
let hiddenElement;
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
textElement = null;
hiddenElement = null;
});
@@ -22,10 +20,6 @@ describe('vue_shared/components/chronic_duration_input', () => {
};
const createComponent = (props = {}) => {
- if (wrapper) {
- throw new Error('There should only be one wrapper created per test');
- }
-
wrapper = mount(ChronicDurationInput, { propsData: props });
findComponents();
};
diff --git a/spec/frontend/vue_shared/components/ci_badge_link_spec.js b/spec/frontend/vue_shared/components/ci_badge_link_spec.js
index 4f24ec2d015..afb509b9fe6 100644
--- a/spec/frontend/vue_shared/components/ci_badge_link_spec.js
+++ b/spec/frontend/vue_shared/components/ci_badge_link_spec.js
@@ -82,10 +82,6 @@ describe('CI Badge Link Component', () => {
wrapper = shallowMount(CiBadgeLink, { propsData });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each(Object.keys(statuses))('should render badge for status: %s', (status) => {
createComponent({ status: statuses[status] });
diff --git a/spec/frontend/vue_shared/components/ci_icon_spec.js b/spec/frontend/vue_shared/components/ci_icon_spec.js
index 2064bee9673..31d63654168 100644
--- a/spec/frontend/vue_shared/components/ci_icon_spec.js
+++ b/spec/frontend/vue_shared/components/ci_icon_spec.js
@@ -7,11 +7,6 @@ describe('CI Icon component', () => {
const findIconWrapper = () => wrapper.find('[data-testid="ci-icon-wrapper"]');
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should render a span element with an svg', () => {
wrapper = shallowMount(ciIcon, {
propsData: {
diff --git a/spec/frontend/vue_shared/components/clipboard_button_spec.js b/spec/frontend/vue_shared/components/clipboard_button_spec.js
index b18b00e70bb..08a9c2a42d8 100644
--- a/spec/frontend/vue_shared/components/clipboard_button_spec.js
+++ b/spec/frontend/vue_shared/components/clipboard_button_spec.js
@@ -59,11 +59,6 @@ describe('clipboard button', () => {
expect(wrapper.vm.$root.$emit).toHaveBeenCalledWith('bv::hide::tooltip', 'clipboard-button-1');
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('without gfm', () => {
beforeEach(() => {
createWrapper({
diff --git a/spec/frontend/vue_shared/components/clone_dropdown_spec.js b/spec/frontend/vue_shared/components/clone_dropdown_spec.js
index 31c08260dd0..584e29d94c4 100644
--- a/spec/frontend/vue_shared/components/clone_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/clone_dropdown_spec.js
@@ -21,11 +21,6 @@ describe('Clone Dropdown Button', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('rendering', () => {
it('matches the snapshot', () => {
createComponent();
diff --git a/spec/frontend/vue_shared/components/code_block_highlighted_spec.js b/spec/frontend/vue_shared/components/code_block_highlighted_spec.js
index 181692e61b5..25283eb1211 100644
--- a/spec/frontend/vue_shared/components/code_block_highlighted_spec.js
+++ b/spec/frontend/vue_shared/components/code_block_highlighted_spec.js
@@ -11,10 +11,6 @@ describe('Code Block Highlighted', () => {
wrapper = shallowMount(CodeBlock, { propsData });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders highlighted code if language is supported', async () => {
createComponent({ code, language: 'javascript' });
diff --git a/spec/frontend/vue_shared/components/code_block_spec.js b/spec/frontend/vue_shared/components/code_block_spec.js
index 9a4dbcc47ff..0fdfb96cb23 100644
--- a/spec/frontend/vue_shared/components/code_block_spec.js
+++ b/spec/frontend/vue_shared/components/code_block_spec.js
@@ -13,10 +13,6 @@ describe('Code Block', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('overwrites the default slot', () => {
createComponent({}, { default: 'DEFAULT SLOT' });
diff --git a/spec/frontend/vue_shared/components/color_picker/color_picker_spec.js b/spec/frontend/vue_shared/components/color_picker/color_picker_spec.js
index 060048c4bbd..a839af3b709 100644
--- a/spec/frontend/vue_shared/components/color_picker/color_picker_spec.js
+++ b/spec/frontend/vue_shared/components/color_picker/color_picker_spec.js
@@ -34,10 +34,6 @@ describe('ColorPicker', () => {
};
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('label', () => {
it('hides the label if the label is not passed', () => {
createComponent(shallowMount);
diff --git a/spec/frontend/vue_shared/components/color_select_dropdown/color_item_spec.js b/spec/frontend/vue_shared/components/color_select_dropdown/color_item_spec.js
index fe614f03119..700556edfd5 100644
--- a/spec/frontend/vue_shared/components/color_select_dropdown/color_item_spec.js
+++ b/spec/frontend/vue_shared/components/color_select_dropdown/color_item_spec.js
@@ -20,10 +20,6 @@ describe('ColorItem', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the correct title', () => {
expect(wrapper.text()).toBe(propsData.title);
});
diff --git a/spec/frontend/vue_shared/components/color_select_dropdown/color_select_root_spec.js b/spec/frontend/vue_shared/components/color_select_dropdown/color_select_root_spec.js
index 5b0772f6e34..61a9ab3225e 100644
--- a/spec/frontend/vue_shared/components/color_select_dropdown/color_select_root_spec.js
+++ b/spec/frontend/vue_shared/components/color_select_dropdown/color_select_root_spec.js
@@ -3,7 +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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
import DropdownContents from '~/vue_shared/components/color_select_dropdown/dropdown_contents.vue';
import DropdownValue from '~/vue_shared/components/color_select_dropdown/dropdown_value.vue';
@@ -13,7 +13,7 @@ import ColorSelectRoot from '~/vue_shared/components/color_select_dropdown/color
import { DROPDOWN_VARIANT } from '~/vue_shared/components/color_select_dropdown/constants';
import { colorQueryResponse, updateColorMutationResponse, color } from './mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(VueApollo);
@@ -60,10 +60,6 @@ describe('LabelsSelectRoot', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
const defaultClasses = ['labels-select-wrapper', 'gl-relative'];
@@ -145,7 +141,7 @@ describe('LabelsSelectRoot', () => {
await waitForPromises();
});
- it('creates flash with error message', () => {
+ it('creates alert with error message', () => {
expect(createAlert).toHaveBeenCalledWith({
captureError: true,
message: 'Error fetching epic color.',
diff --git a/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_color_view_spec.js b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_color_view_spec.js
index 303824c77b3..914dfdbaab0 100644
--- a/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_color_view_spec.js
+++ b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_color_view_spec.js
@@ -22,10 +22,6 @@ describe('DropdownContentsColorView', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findColors = () => wrapper.findAllComponents(ColorItem);
const findColorList = () => wrapper.findComponent(GlDropdownForm);
diff --git a/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js
index ee4d3a2630a..c07faab20d0 100644
--- a/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js
+++ b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js
@@ -28,10 +28,6 @@ describe('DropdownContent', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findColorView = () => wrapper.findComponent(DropdownContentsColorView);
const findDropdownHeader = () => wrapper.findComponent(DropdownHeader);
const findDropdown = () => wrapper.findComponent(GlDropdown);
diff --git a/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_header_spec.js b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_header_spec.js
index d203d78477f..6c8aabe1c7f 100644
--- a/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_header_spec.js
+++ b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_header_spec.js
@@ -15,10 +15,6 @@ describe('DropdownHeader', () => {
const findButton = () => wrapper.findComponent(GlButton);
- afterEach(() => {
- wrapper.destroy();
- });
-
beforeEach(() => {
createComponent();
});
diff --git a/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_value_spec.js b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_value_spec.js
index 5bbdb136353..825f37c97e0 100644
--- a/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_value_spec.js
+++ b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_value_spec.js
@@ -22,10 +22,6 @@ describe('DropdownValue', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when there is a color set', () => {
it('renders the color', () => {
expect(findColorItems()).toHaveLength(2);
diff --git a/spec/frontend/vue_shared/components/commit_spec.js b/spec/frontend/vue_shared/components/commit_spec.js
index 1893e127f6f..62a2738d8df 100644
--- a/spec/frontend/vue_shared/components/commit_spec.js
+++ b/spec/frontend/vue_shared/components/commit_spec.js
@@ -24,10 +24,6 @@ describe('Commit component', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render a fork icon if it does not represent a tag', () => {
createComponent({
tag: false,
diff --git a/spec/frontend/vue_shared/components/confidentiality_badge_spec.js b/spec/frontend/vue_shared/components/confidentiality_badge_spec.js
index 3f7ec156c19..92cd7597637 100644
--- a/spec/frontend/vue_shared/components/confidentiality_badge_spec.js
+++ b/spec/frontend/vue_shared/components/confidentiality_badge_spec.js
@@ -1,14 +1,11 @@
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
-import { WorkspaceType, TYPE_ISSUE, TYPE_EPIC } from '~/issues/constants';
+import { TYPE_ISSUE, TYPE_EPIC, WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
import ConfidentialityBadge from '~/vue_shared/components/confidentiality_badge.vue';
-const createComponent = ({
- workspaceType = WorkspaceType.project,
- issuableType = TYPE_ISSUE,
-} = {}) =>
+const createComponent = ({ workspaceType = WORKSPACE_PROJECT, issuableType = TYPE_ISSUE } = {}) =>
shallowMount(ConfidentialityBadge, {
propsData: {
workspaceType,
@@ -23,14 +20,10 @@ describe('ConfidentialityBadge', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
- workspaceType | issuableType | expectedTooltip
- ${WorkspaceType.project} | ${TYPE_ISSUE} | ${'Only project members with at least the Reporter role, the author, and assignees can view or be notified about this issue.'}
- ${WorkspaceType.group} | ${TYPE_EPIC} | ${'Only group members with at least the Reporter role can view or be notified about this epic.'}
+ workspaceType | issuableType | expectedTooltip
+ ${WORKSPACE_PROJECT} | ${TYPE_ISSUE} | ${'Only project members with at least the Reporter role, the author, and assignees can view or be notified about this issue.'}
+ ${WORKSPACE_GROUP} | ${TYPE_EPIC} | ${'Only group members with at least the Reporter role can view or be notified about this epic.'}
`(
'should render gl-badge with correct tooltip when workspaceType is $workspaceType and issuableType is $issuableType',
({ workspaceType, issuableType, expectedTooltip }) => {
diff --git a/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js b/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js
index a660643d74f..d7f94c00d09 100644
--- a/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js
+++ b/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js
@@ -24,7 +24,7 @@ describe('Confirm Danger Modal', () => {
const findAdditionalMessage = () => wrapper.findByTestId('confirm-danger-message');
const findPrimaryAction = () => findModal().props('actionPrimary');
const findCancelAction = () => findModal().props('actionCancel');
- const findPrimaryActionAttributes = (attr) => findPrimaryAction().attributes[0][attr];
+ const findPrimaryActionAttributes = (attr) => findPrimaryAction().attributes[attr];
const createComponent = ({ provide = {} } = {}) =>
shallowMountExtended(ConfirmDangerModal, {
@@ -42,10 +42,6 @@ describe('Confirm Danger Modal', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the default warning message', () => {
expect(findDefaultWarning().text()).toBe(CONFIRM_DANGER_WARNING);
});
diff --git a/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_spec.js b/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_spec.js
index a179afccae0..379b5cde4d5 100644
--- a/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_spec.js
+++ b/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_spec.js
@@ -32,10 +32,6 @@ describe('Confirm Danger Modal', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the button', () => {
expect(wrapper.html()).toContain(buttonText);
});
diff --git a/spec/frontend/vue_shared/components/confirm_fork_modal_spec.js b/spec/frontend/vue_shared/components/confirm_fork_modal_spec.js
index 1cde92cf522..fbfef5cbe46 100644
--- a/spec/frontend/vue_shared/components/confirm_fork_modal_spec.js
+++ b/spec/frontend/vue_shared/components/confirm_fork_modal_spec.js
@@ -21,10 +21,6 @@ describe('vue_shared/components/confirm_fork_modal', () => {
},
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('visible = false', () => {
beforeEach(() => {
wrapper = createComponent();
diff --git a/spec/frontend/vue_shared/components/confirm_modal_spec.js b/spec/frontend/vue_shared/components/confirm_modal_spec.js
index c1e682a1aae..283ef52cee7 100644
--- a/spec/frontend/vue_shared/components/confirm_modal_spec.js
+++ b/spec/frontend/vue_shared/components/confirm_modal_spec.js
@@ -47,10 +47,6 @@ describe('vue_shared/components/confirm_modal', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findModal = () => wrapper.findComponent(GlModalStub);
const findForm = () => wrapper.find('form');
const findFormData = () =>
diff --git a/spec/frontend/vue_shared/components/content_transition_spec.js b/spec/frontend/vue_shared/components/content_transition_spec.js
index 8bb6d31cce7..5f2b1f096f3 100644
--- a/spec/frontend/vue_shared/components/content_transition_spec.js
+++ b/spec/frontend/vue_shared/components/content_transition_spec.js
@@ -13,11 +13,6 @@ const TEST_SLOTS = [
describe('~/vue_shared/components/content_transition.vue', () => {
let wrapper;
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const createComponent = (props = {}, slots = {}) => {
wrapper = shallowMount(ContentTransition, {
propsData: {
diff --git a/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_input_spec.js b/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_input_spec.js
index c1495e8264a..a3e5f187f9b 100644
--- a/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_input_spec.js
+++ b/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_input_spec.js
@@ -18,10 +18,6 @@ describe('DateTimePickerInput', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders label above the input', () => {
createComponent({
label: inputLabel,
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 aa41df438d2..5620b569409 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
@@ -26,10 +26,6 @@ describe('DateTimePicker', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders dropdown toggle button with selected text', async () => {
createComponent();
await nextTick();
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 79001b9282f..dde2540e121 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
@@ -17,10 +17,6 @@ describe('Deploy Board Instance', () => {
});
describe('as a non-canary deployment', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render a div with the correct css status and tooltip data', () => {
wrapper = createComponent({
tooltipText: 'This is a pod',
@@ -43,10 +39,6 @@ describe('Deploy Board Instance', () => {
});
describe('as a canary deployment', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render a div with canary class when stable prop is provided as false', async () => {
wrapper = createComponent({
stable: false,
@@ -58,10 +50,6 @@ describe('Deploy Board Instance', () => {
});
describe('as a legend item', () => {
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should not have a tooltip', () => {
wrapper = createComponent();
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 353d493add9..ca9c2b7d381 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
@@ -16,10 +16,6 @@ describe('Design note pin component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should match the snapshot of note without index', () => {
createComponent();
expect(wrapper.element).toMatchSnapshot();
diff --git a/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js b/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js
index 99c973bdd26..930d2fc8cfe 100644
--- a/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/diff_stats_dropdown_spec.js
@@ -110,10 +110,6 @@ describe('Diff Stats Dropdown', () => {
createComponent({ changed, added, deleted });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it(`dropdown header should be '${expectedDropdownHeader}'`, () => {
expect(findChanged().props('text')).toBe(expectedDropdownHeader);
});
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 6e0717c29d7..694c69fbe9f 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
@@ -18,10 +18,6 @@ describe('DiffViewer', () => {
wrapper = mount(DiffViewer, { propsData });
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders image diff', () => {
window.gon = {
relative_url_root: '',
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 16f924b44d8..7863ef45817 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
@@ -42,10 +42,6 @@ describe('ImageDiffViewer component', () => {
triggerEvent('mouseup', doc.body);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders image diff for replaced', () => {
createComponent(allProps);
const metaInfoElements = wrapper.findAll('.image-info');
diff --git a/spec/frontend/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js b/spec/frontend/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js
index c4358f0d9cb..661db19ff0e 100644
--- a/spec/frontend/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js
+++ b/spec/frontend/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js
@@ -13,10 +13,6 @@ describe('Diff viewer mode changed component', () => {
});
});
- afterEach(() => {
- vm.destroy();
- });
-
it('renders aMode & bMode', () => {
expect(vm.text()).toContain('File mode changed from 123 to 321');
});
diff --git a/spec/frontend/vue_shared/components/dismissible_alert_spec.js b/spec/frontend/vue_shared/components/dismissible_alert_spec.js
index 8b1189f25d5..53e7d9fc7fc 100644
--- a/spec/frontend/vue_shared/components/dismissible_alert_spec.js
+++ b/spec/frontend/vue_shared/components/dismissible_alert_spec.js
@@ -16,10 +16,6 @@ describe('vue_shared/components/dismissible_alert', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findAlert = () => wrapper.findComponent(GlAlert);
describe('default', () => {
diff --git a/spec/frontend/vue_shared/components/dismissible_container_spec.js b/spec/frontend/vue_shared/components/dismissible_container_spec.js
index 7d8581e11e9..6d179434d1d 100644
--- a/spec/frontend/vue_shared/components/dismissible_container_spec.js
+++ b/spec/frontend/vue_shared/components/dismissible_container_spec.js
@@ -11,10 +11,6 @@ describe('DismissibleContainer', () => {
featureId: 'some-feature-id',
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
const findBtn = () => wrapper.find('[data-testid="close"]');
let mockAxios;
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 4b32fbffebe..b184ec4ac54 100644
--- a/spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js
+++ b/spec/frontend/vue_shared/components/dismissible_feedback_alert_spec.js
@@ -23,11 +23,6 @@ describe('Dismissible Feedback Alert', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const createFullComponent = () => createComponent({ mountFn: mount });
const findAlert = () => wrapper.findComponent(GlAlert);
diff --git a/spec/frontend/vue_shared/components/dom_element_listener_spec.js b/spec/frontend/vue_shared/components/dom_element_listener_spec.js
index a848c34b7ce..d31e9b867e4 100644
--- a/spec/frontend/vue_shared/components/dom_element_listener_spec.js
+++ b/spec/frontend/vue_shared/components/dom_element_listener_spec.js
@@ -42,10 +42,6 @@ describe('~/vue_shared/components/dom_element_listener.vue', () => {
};
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/vue_shared/components/dropdown/dropdown_button_spec.js b/spec/frontend/vue_shared/components/dropdown/dropdown_button_spec.js
index e34ed31b4bf..82130500458 100644
--- a/spec/frontend/vue_shared/components/dropdown/dropdown_button_spec.js
+++ b/spec/frontend/vue_shared/components/dropdown/dropdown_button_spec.js
@@ -11,10 +11,6 @@ describe('DropdownButton component', () => {
wrapper = mount(DropdownButton, { propsData: props, slots });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('computed', () => {
describe('dropdownToggleText', () => {
it('returns default toggle text', () => {
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 dd3e55c82bb..4dfee20764c 100644
--- a/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js
+++ b/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js
@@ -31,10 +31,6 @@ describe('DropdownWidget component', () => {
},
});
- // We need to mock out `showDropdown` which
- // invokes `show` method of BDropdown used inside GlDropdown.
- // Context: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54895#note_524281679
- jest.spyOn(wrapper.vm, 'showDropdown').mockImplementation();
jest.spyOn(findDropdown().vm, 'hide').mockImplementation();
};
@@ -42,11 +38,6 @@ describe('DropdownWidget component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('passes default selectText prop to dropdown', () => {
expect(findDropdown().props('text')).toBe('Select');
});
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 119d6448507..ef42c17984a 100644
--- a/spec/frontend/vue_shared/components/dropdown_keyboard_navigation_spec.js
+++ b/spec/frontend/vue_shared/components/dropdown_keyboard_navigation_spec.js
@@ -39,10 +39,6 @@ describe('DropdownKeyboardNavigation', () => {
},
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('onInit', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/vue_shared/components/ensure_data_spec.js b/spec/frontend/vue_shared/components/ensure_data_spec.js
index eef8b452f5f..217e795bc64 100644
--- a/spec/frontend/vue_shared/components/ensure_data_spec.js
+++ b/spec/frontend/vue_shared/components/ensure_data_spec.js
@@ -59,7 +59,6 @@ describe('EnsureData', () => {
});
afterEach(() => {
- wrapper.destroy();
Sentry.captureException.mockClear();
});
diff --git a/spec/frontend/vue_shared/components/entity_select/project_select_spec.js b/spec/frontend/vue_shared/components/entity_select/project_select_spec.js
index 57dce032d30..32ce2155494 100644
--- a/spec/frontend/vue_shared/components/entity_select/project_select_spec.js
+++ b/spec/frontend/vue_shared/components/entity_select/project_select_spec.js
@@ -63,11 +63,8 @@ describe('ProjectSelect', () => {
};
const openListbox = () => findListbox().vm.$emit('shown');
- beforeAll(() => {
- gon.api_version = apiVersion;
- });
-
beforeEach(() => {
+ gon.api_version = apiVersion;
mock = new MockAdapter(axios);
});
diff --git a/spec/frontend/vue_shared/components/expand_button_spec.js b/spec/frontend/vue_shared/components/expand_button_spec.js
index 170c947e520..ad2a57d90eb 100644
--- a/spec/frontend/vue_shared/components/expand_button_spec.js
+++ b/spec/frontend/vue_shared/components/expand_button_spec.js
@@ -27,10 +27,6 @@ describe('Expand button', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the prepended collapse button', () => {
expect(expanderPrependEl().isVisible()).toBe(true);
expect(expanderAppendEl().isVisible()).toBe(false);
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 f0998b1b5c6..dce6c85b5b3 100644
--- a/spec/frontend/vue_shared/components/file_finder/item_spec.js
+++ b/spec/frontend/vue_shared/components/file_finder/item_spec.js
@@ -22,10 +22,6 @@ describe('File finder item spec', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders file name & path', () => {
createComponent();
diff --git a/spec/frontend/vue_shared/components/file_icon_spec.js b/spec/frontend/vue_shared/components/file_icon_spec.js
index 0fcc0678c13..d95773f2218 100644
--- a/spec/frontend/vue_shared/components/file_icon_spec.js
+++ b/spec/frontend/vue_shared/components/file_icon_spec.js
@@ -16,10 +16,6 @@ describe('File Icon component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render a span element and an icon', () => {
createComponent({
fileName: 'test.js',
diff --git a/spec/frontend/vue_shared/components/file_row_header_spec.js b/spec/frontend/vue_shared/components/file_row_header_spec.js
index 80f4c275dcc..885a80f73b5 100644
--- a/spec/frontend/vue_shared/components/file_row_header_spec.js
+++ b/spec/frontend/vue_shared/components/file_row_header_spec.js
@@ -1,36 +1,24 @@
import { shallowMount } from '@vue/test-utils';
+import { GlTruncate } from '@gitlab/ui';
import FileRowHeader from '~/vue_shared/components/file_row_header.vue';
describe('File row header component', () => {
- let vm;
+ let wrapper;
function createComponent(path) {
- vm = shallowMount(FileRowHeader, {
+ wrapper = shallowMount(FileRowHeader, {
propsData: {
path,
},
});
}
- afterEach(() => {
- vm.destroy();
- });
-
it('renders file path', () => {
- createComponent('app/assets');
-
- expect(vm.element).toMatchSnapshot();
- });
-
- it('trucates path after 40 characters', () => {
- createComponent('app/assets/javascripts/merge_requests');
-
- expect(vm.element).toMatchSnapshot();
- });
-
- it('adds multiple ellipsises after 40 characters', () => {
- createComponent('app/assets/javascripts/merge_requests/widget/diffs/notes');
+ const path = 'app/assets';
+ createComponent(path);
- expect(vm.element).toMatchSnapshot();
+ const truncate = wrapper.findComponent(GlTruncate);
+ expect(truncate.exists()).toBe(true);
+ expect(truncate.props('text')).toBe(path);
});
});
diff --git a/spec/frontend/vue_shared/components/file_row_spec.js b/spec/frontend/vue_shared/components/file_row_spec.js
index b70d4565f56..976866af27c 100644
--- a/spec/frontend/vue_shared/components/file_row_spec.js
+++ b/spec/frontend/vue_shared/components/file_row_spec.js
@@ -6,6 +6,9 @@ import FileIcon from '~/vue_shared/components/file_icon.vue';
import FileRow from '~/vue_shared/components/file_row.vue';
import FileHeader from '~/vue_shared/components/file_row_header.vue';
+const scrollIntoViewMock = jest.fn();
+HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
+
describe('File row component', () => {
let wrapper;
@@ -18,10 +21,6 @@ describe('File row component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders name', () => {
const fileName = 't4';
createComponent({
@@ -72,11 +71,10 @@ describe('File row component', () => {
},
level: 0,
});
- jest.spyOn(wrapper.vm, '$emit');
wrapper.element.click();
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', fileName);
+ expect(wrapper.emitted('toggleTreeOpen')[0][0]).toEqual(fileName);
});
it('calls scrollIntoView if made active', () => {
@@ -89,14 +87,12 @@ describe('File row component', () => {
level: 0,
});
- jest.spyOn(wrapper.vm, 'scrollIntoView');
-
wrapper.setProps({
file: { ...wrapper.props('file'), active: true },
});
return nextTick().then(() => {
- expect(wrapper.vm.scrollIntoView).toHaveBeenCalled();
+ expect(scrollIntoViewMock).toHaveBeenCalled();
});
});
diff --git a/spec/frontend/vue_shared/components/file_tree_spec.js b/spec/frontend/vue_shared/components/file_tree_spec.js
index e8818e09dc0..9d2fa369910 100644
--- a/spec/frontend/vue_shared/components/file_tree_spec.js
+++ b/spec/frontend/vue_shared/components/file_tree_spec.js
@@ -33,10 +33,6 @@ describe('File Tree component', () => {
...pick(x.attributes(), Object.keys(TEST_EXTA_ARGS)),
}));
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('file row component', () => {
beforeEach(() => {
createComponent({ file: {} });
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 b0e393bbf5e..123714353e2 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
@@ -82,10 +82,6 @@ describe('FilteredSearchBarRoot', () => {
wrapper = createComponent({ sortOptions: mockSortOptions });
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('data', () => {
it('initializes `filterValue`, `selectedSortOption` and `selectedSortDirection` data props and displays the sort dropdown', () => {
expect(wrapper.vm.filterValue).toEqual([]);
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js
index 63c22aff3d5..dd0ec65c871 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/store/modules/filters/actions_spec.js
@@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import { mockBranches } from 'jest/vue_shared/components/filtered_search_bar/mock_data';
import Api from '~/api';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { HTTP_STATUS_OK, HTTP_STATUS_SERVICE_UNAVAILABLE } from '~/lib/utils/http_status';
import * as actions from '~/vue_shared/components/filtered_search_bar/store/modules/filters/actions';
import * as types from '~/vue_shared/components/filtered_search_bar/store/modules/filters/mutation_types';
@@ -15,7 +15,7 @@ const labelsEndpoint = 'fake_labels_endpoint';
const groupEndpoint = 'fake_group_endpoint';
const projectEndpoint = 'fake_project_endpoint';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Filters actions', () => {
let state;
@@ -165,16 +165,10 @@ describe('Filters actions', () => {
});
describe('fetchAuthors', () => {
- let restoreVersion;
beforeEach(() => {
- restoreVersion = gon.api_version;
gon.api_version = 'v1';
});
- afterEach(() => {
- gon.api_version = restoreVersion;
- });
-
describe('success', () => {
beforeEach(() => {
mock.onAny().replyOnce(HTTP_STATUS_OK, filterUsers);
@@ -305,17 +299,11 @@ describe('Filters actions', () => {
describe('fetchAssignees', () => {
describe('success', () => {
- let restoreVersion;
beforeEach(() => {
mock.onAny().replyOnce(HTTP_STATUS_OK, filterUsers);
- restoreVersion = gon.api_version;
gon.api_version = 'v1';
});
- afterEach(() => {
- gon.api_version = restoreVersion;
- });
-
it('dispatches RECEIVE_ASSIGNEES_SUCCESS with received data and groupEndpoint set', () => {
return testAction(
actions.fetchAssignees,
@@ -350,17 +338,11 @@ describe('Filters actions', () => {
});
describe('error', () => {
- let restoreVersion;
beforeEach(() => {
mock.onAny().replyOnce(HTTP_STATUS_SERVICE_UNAVAILABLE);
- restoreVersion = gon.api_version;
gon.api_version = 'v1';
});
- afterEach(() => {
- gon.api_version = restoreVersion;
- });
-
it('dispatches RECEIVE_ASSIGNEES_ERROR and groupEndpoint set', () => {
return testAction(
actions.fetchAssignees,
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 164235e4bb9..9941abbfaea 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
@@ -120,10 +120,6 @@ describe('BaseToken', () => {
const getMockSuggestionListSuggestions = () =>
JSON.parse(findMockSuggestionList().attributes('data-suggestions'));
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('data', () => {
it('calls `getRecentlyUsedSuggestions` to populate `recentSuggestions` when `recentSuggestionsStorageKey` is defined', () => {
wrapper = createComponent();
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 311d5a13280..a6bb32736db 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
@@ -9,14 +9,15 @@ import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { OPTIONS_NONE_ANY } from '~/vue_shared/components/filtered_search_bar/constants';
import BranchToken from '~/vue_shared/components/filtered_search_bar/tokens/branch_token.vue';
+import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
import { mockBranches, mockBranchToken } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
const defaultStubs = {
Portal: true,
GlFilteredSearchSuggestionList: {
@@ -54,58 +55,83 @@ describe('BranchToken', () => {
let mock;
let wrapper;
+ const findBaseToken = () => wrapper.findComponent(BaseToken);
+ const triggerFetchBranches = (searchTerm = null) => {
+ findBaseToken().vm.$emit('fetch-suggestions', searchTerm);
+ return waitForPromises();
+ };
+
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
describe('methods', () => {
- beforeEach(() => {
- wrapper = createComponent();
- });
-
describe('fetchBranches', () => {
- it('calls `config.fetchBranches` with provided searchTerm param', () => {
- jest.spyOn(wrapper.vm.config, 'fetchBranches');
-
- wrapper.vm.fetchBranches('foo');
+ it('sets loading state', async () => {
+ wrapper = createComponent({
+ config: {
+ fetchBranches: jest.fn().mockResolvedValue(new Promise(() => {})),
+ },
+ });
+ await nextTick();
- expect(wrapper.vm.config.fetchBranches).toHaveBeenCalledWith('foo');
+ expect(findBaseToken().props('suggestionsLoading')).toBe(true);
});
- it('sets response to `branches` when request is succesful', () => {
- jest.spyOn(wrapper.vm.config, 'fetchBranches').mockResolvedValue({ data: mockBranches });
+ describe('when request is successful', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ config: {
+ fetchBranches: jest.fn().mockResolvedValue({ data: mockBranches }),
+ },
+ });
+ });
+
+ it('calls `config.fetchBranches` with provided searchTerm param', async () => {
+ const searchTerm = 'foo';
+ await triggerFetchBranches(searchTerm);
- wrapper.vm.fetchBranches('foo');
+ expect(findBaseToken().props('config').fetchBranches).toHaveBeenCalledWith(searchTerm);
+ });
+
+ it('sets response to `branches`', async () => {
+ await triggerFetchBranches();
- return waitForPromises().then(() => {
- expect(wrapper.vm.branches).toEqual(mockBranches);
+ expect(findBaseToken().props('suggestions')).toEqual(mockBranches);
+ });
+
+ it('sets `loading` to false when request completes', async () => {
+ await triggerFetchBranches();
+
+ expect(findBaseToken().props('suggestionsLoading')).toBe(false);
});
});
- it('calls `createAlert` with flash error message when request fails', () => {
- jest.spyOn(wrapper.vm.config, 'fetchBranches').mockRejectedValue({});
+ describe('when request fails', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ config: {
+ fetchBranches: jest.fn().mockRejectedValue({}),
+ },
+ });
+ });
- wrapper.vm.fetchBranches('foo');
+ it('calls `createAlert` with alert error message when request fails', async () => {
+ await triggerFetchBranches();
- return waitForPromises().then(() => {
expect(createAlert).toHaveBeenCalledWith({
message: 'There was a problem fetching branches.',
});
});
- });
-
- it('sets `loading` to false when request completes', () => {
- jest.spyOn(wrapper.vm.config, 'fetchBranches').mockRejectedValue({});
- wrapper.vm.fetchBranches('foo');
+ it('sets `loading` to false when request completes', async () => {
+ await triggerFetchBranches();
- return waitForPromises().then(() => {
- expect(wrapper.vm.loading).toBe(false);
+ expect(findBaseToken().props('suggestionsLoading')).toBe(false);
});
});
});
@@ -120,16 +146,13 @@ describe('BranchToken', () => {
await nextTick();
}
- beforeEach(async () => {
- wrapper = createComponent({ value: { data: mockBranches[0].name } });
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- branches: mockBranches,
+ beforeEach(() => {
+ wrapper = createComponent({
+ value: { data: mockBranches[0].name },
+ config: {
+ initialBranches: mockBranches,
+ },
});
-
- await nextTick();
});
it('renders gl-filtered-search-token component', () => {
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_contact_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_contact_token_spec.js
index 7be7035a0f2..ce134f7d24e 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_contact_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_contact_token_spec.js
@@ -8,7 +8,7 @@ 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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { OPTIONS_NONE_ANY } from '~/vue_shared/components/filtered_search_bar/constants';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
@@ -22,7 +22,7 @@ import {
mockProjectCrmContactsQueryResponse,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
const defaultStubs = {
Portal: true,
@@ -79,7 +79,6 @@ describe('CrmContactToken', () => {
};
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
@@ -159,7 +158,7 @@ describe('CrmContactToken', () => {
});
});
- it('calls `createAlert` with flash error message when request fails', async () => {
+ it('calls `createAlert` with alert error message when request fails', async () => {
mountComponent();
jest.spyOn(wrapper.vm.$apollo, 'query').mockRejectedValue({});
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_organization_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_organization_token_spec.js
index ecd3e8a04f1..8526631c63d 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_organization_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_organization_token_spec.js
@@ -8,7 +8,7 @@ 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 { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { OPTIONS_NONE_ANY } from '~/vue_shared/components/filtered_search_bar/constants';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
@@ -22,7 +22,7 @@ import {
mockProjectCrmOrganizationsQueryResponse,
} from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
const defaultStubs = {
Portal: true,
@@ -78,7 +78,6 @@ describe('CrmOrganizationToken', () => {
};
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
@@ -158,7 +157,7 @@ describe('CrmOrganizationToken', () => {
});
});
- it('calls `createAlert` with flash error message when request fails', async () => {
+ it('calls `createAlert` with alert error message when request fails', async () => {
mountComponent();
jest.spyOn(wrapper.vm.$apollo, 'query').mockRejectedValue({});
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 773df01ada7..4e00b6837a3 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
@@ -8,7 +8,7 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import {
@@ -17,10 +17,11 @@ import {
OPTIONS_NONE_ANY,
} from '~/vue_shared/components/filtered_search_bar/constants';
import EmojiToken from '~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue';
+import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
import { mockReactionEmojiToken, mockEmojis } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
const GlEmoji = { template: '<img/>' };
const defaultStubs = {
Portal: true,
@@ -60,58 +61,72 @@ describe('EmojiToken', () => {
let mock;
let wrapper;
+ const findBaseToken = () => wrapper.findComponent(BaseToken);
+ const triggerFetchEmojis = (searchTerm = null) => {
+ findBaseToken().vm.$emit('fetch-suggestions', searchTerm);
+ return waitForPromises();
+ };
+
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
describe('methods', () => {
- beforeEach(() => {
- wrapper = createComponent();
- });
-
describe('fetchEmojis', () => {
- it('calls `config.fetchEmojis` with provided searchTerm param', () => {
- jest.spyOn(wrapper.vm.config, 'fetchEmojis');
-
- wrapper.vm.fetchEmojis('foo');
+ it('sets loading state', async () => {
+ wrapper = createComponent({
+ config: {
+ fetchEmojis: jest.fn().mockResolvedValue(new Promise(() => {})),
+ },
+ });
+ await nextTick();
- expect(wrapper.vm.config.fetchEmojis).toHaveBeenCalledWith('foo');
+ expect(findBaseToken().props('suggestionsLoading')).toBe(true);
});
- it('sets response to `emojis` when request is successful', () => {
- jest.spyOn(wrapper.vm.config, 'fetchEmojis').mockResolvedValue(mockEmojis);
+ describe('when request is successful', () => {
+ const searchTerm = 'foo';
- wrapper.vm.fetchEmojis('foo');
+ beforeEach(async () => {
+ wrapper = createComponent({
+ config: {
+ fetchEmojis: jest.fn().mockResolvedValue({ data: mockEmojis }),
+ },
+ });
+ return triggerFetchEmojis(searchTerm);
+ });
- return waitForPromises().then(() => {
- expect(wrapper.vm.emojis).toEqual(mockEmojis);
+ it('calls `config.fetchEmojis` with provided searchTerm param', () => {
+ expect(findBaseToken().props('config').fetchEmojis).toHaveBeenCalledWith(searchTerm);
});
- });
- it('calls `createAlert` with flash error message when request fails', () => {
- jest.spyOn(wrapper.vm.config, 'fetchEmojis').mockRejectedValue({});
+ it('sets response to `emojis`', () => {
+ expect(findBaseToken().props('suggestions')).toEqual(mockEmojis);
+ });
+ });
- wrapper.vm.fetchEmojis('foo');
+ describe('when request fails', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ config: {
+ fetchEmojis: jest.fn().mockRejectedValue({}),
+ },
+ });
+ return triggerFetchEmojis();
+ });
- return waitForPromises().then(() => {
+ it('calls `createAlert` with alert error message', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'There was a problem fetching emojis.',
});
});
- });
-
- it('sets `loading` to false when request completes', () => {
- jest.spyOn(wrapper.vm.config, 'fetchEmojis').mockRejectedValue({});
-
- wrapper.vm.fetchEmojis('foo');
- return waitForPromises().then(() => {
- expect(wrapper.vm.loading).toBe(false);
+ it('sets `loading` to false when request completes', () => {
+ expect(findBaseToken().props('suggestionsLoading')).toBe(false);
});
});
});
@@ -123,15 +138,10 @@ describe('EmojiToken', () => {
beforeEach(async () => {
wrapper = createComponent({
value: { data: `"${mockEmojis[0].name}"` },
+ config: {
+ initialEmojis: mockEmojis,
+ },
});
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- emojis: mockEmojis,
- });
-
- await nextTick();
});
it('renders gl-filtered-search-token component', () => {
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 9d96123c17f..b9275409125 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
@@ -11,7 +11,7 @@ import {
mockRegularLabel,
mockLabels,
} from 'jest/sidebar/components/labels/labels_select_vue/mock_data';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { OPTIONS_NONE_ANY } from '~/vue_shared/components/filtered_search_bar/constants';
@@ -20,7 +20,7 @@ import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label
import { mockLabelToken } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
const defaultStubs = {
Portal: true,
BaseToken,
@@ -60,103 +60,125 @@ function createComponent(options = {}) {
describe('LabelToken', () => {
let mock;
let wrapper;
+ const defaultLabels = OPTIONS_NONE_ANY;
beforeEach(() => {
mock = new MockAdapter(axios);
});
+ const findBaseToken = () => wrapper.findComponent(BaseToken);
+ const findSuggestions = () => wrapper.findAllComponents(GlFilteredSearchSuggestion);
+ const findTokenSegments = () => wrapper.findAllComponents(GlFilteredSearchTokenSegment);
+ const triggerFetchLabels = (searchTerm = null) => {
+ findBaseToken().vm.$emit('fetch-suggestions', searchTerm);
+ return waitForPromises();
+ };
+
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
describe('methods', () => {
- beforeEach(() => {
- wrapper = createComponent();
- });
-
describe('getActiveLabel', () => {
it('returns label object from labels array based on provided `currentValue` param', () => {
- expect(wrapper.vm.getActiveLabel(mockLabels, 'Foo Label')).toEqual(mockRegularLabel);
+ wrapper = createComponent();
+
+ expect(findBaseToken().props('getActiveTokenValue')(mockLabels, 'Foo Label')).toEqual(
+ mockRegularLabel,
+ );
});
});
describe('getLabelName', () => {
- it('returns value of `name` or `title` property present in provided label param', () => {
- let mockLabel = {
- title: 'foo',
- };
+ it('returns value of `name` or `title` property present in provided label param', async () => {
+ const customMockLabels = [
+ { title: 'Title with no name label' },
+ { name: 'Name Label', title: 'Title with name label' },
+ ];
+
+ wrapper = createComponent({
+ active: true,
+ config: {
+ ...mockLabelToken,
+ fetchLabels: jest.fn().mockResolvedValue({ data: customMockLabels }),
+ },
+ stubs: { Portal: true },
+ });
- expect(wrapper.vm.getLabelName(mockLabel)).toBe(mockLabel.title);
+ await waitForPromises();
- mockLabel = {
- name: 'foo',
- };
+ const suggestions = findSuggestions();
+ const indexWithTitle = defaultLabels.length;
+ const indexWithName = defaultLabels.length + 1;
- expect(wrapper.vm.getLabelName(mockLabel)).toBe(mockLabel.name);
+ expect(suggestions.at(indexWithTitle).text()).toBe(customMockLabels[0].title);
+ expect(suggestions.at(indexWithName).text()).toBe(customMockLabels[1].name);
});
});
describe('fetchLabels', () => {
- it('calls `config.fetchLabels` with provided searchTerm param', () => {
- jest.spyOn(wrapper.vm.config, 'fetchLabels');
-
- wrapper.vm.fetchLabels('foo');
-
- expect(wrapper.vm.config.fetchLabels).toHaveBeenCalledWith('foo');
- });
+ describe('when request is successful', () => {
+ const searchTerm = 'foo';
+
+ beforeEach(async () => {
+ wrapper = createComponent({
+ config: {
+ fetchLabels: jest.fn().mockResolvedValue({ data: mockLabels }),
+ },
+ });
+ await triggerFetchLabels(searchTerm);
+ });
- it('sets response to `labels` when request is succesful', () => {
- jest.spyOn(wrapper.vm.config, 'fetchLabels').mockResolvedValue(mockLabels);
+ it('calls `config.fetchLabels` with provided searchTerm param', () => {
+ expect(findBaseToken().props('config').fetchLabels).toHaveBeenCalledWith(searchTerm);
+ });
- wrapper.vm.fetchLabels('foo');
+ it('sets response to `labels`', () => {
+ expect(findBaseToken().props('suggestions')).toEqual(mockLabels);
+ });
- return waitForPromises().then(() => {
- expect(wrapper.vm.labels).toEqual(mockLabels);
+ it('sets `loading` to false when request completes', () => {
+ expect(findBaseToken().props('suggestionsLoading')).toBe(false);
});
});
- it('calls `createAlert` with flash error message when request fails', () => {
- jest.spyOn(wrapper.vm.config, 'fetchLabels').mockRejectedValue({});
-
- wrapper.vm.fetchLabels('foo');
+ describe('when request fails', () => {
+ beforeEach(async () => {
+ wrapper = createComponent({
+ config: {
+ fetchLabels: jest.fn().mockRejectedValue({}),
+ },
+ });
+ await triggerFetchLabels();
+ });
- return waitForPromises().then(() => {
+ it('calls `createAlert` with alert error message', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'There was a problem fetching labels.',
});
});
- });
-
- it('sets `loading` to false when request completes', () => {
- jest.spyOn(wrapper.vm.config, 'fetchLabels').mockRejectedValue({});
-
- wrapper.vm.fetchLabels('foo');
- return waitForPromises().then(() => {
- expect(wrapper.vm.loading).toBe(false);
+ it('sets `loading` to false when request completes', () => {
+ expect(findBaseToken().props('suggestionsLoading')).toBe(false);
});
});
});
});
describe('template', () => {
- const defaultLabels = OPTIONS_NONE_ANY;
-
beforeEach(async () => {
- wrapper = createComponent({ value: { data: `"${mockRegularLabel.title}"` } });
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- labels: mockLabels,
+ wrapper = createComponent({
+ value: { data: `"${mockRegularLabel.title}"` },
+ config: {
+ initialLabels: mockLabels,
+ },
});
await nextTick();
});
it('renders base-token component', () => {
- const baseTokenEl = wrapper.findComponent(BaseToken);
+ const baseTokenEl = findBaseToken();
expect(baseTokenEl.exists()).toBe(true);
expect(baseTokenEl.props()).toMatchObject({
@@ -166,7 +188,7 @@ describe('LabelToken', () => {
});
it('renders token item when value is selected', () => {
- const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment);
+ const tokenSegments = findTokenSegments();
expect(tokenSegments).toHaveLength(3); // Label, =, "Foo Label"
expect(tokenSegments.at(2).text()).toBe(`~${mockRegularLabel.title}`); // "Foo Label"
@@ -181,12 +203,12 @@ describe('LabelToken', () => {
config: { ...mockLabelToken, defaultLabels },
stubs: { Portal: true },
});
- const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment);
+ const tokenSegments = findTokenSegments();
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
await nextTick();
- const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion);
+ const suggestions = findSuggestions();
expect(suggestions).toHaveLength(defaultLabels.length);
defaultLabels.forEach((label, index) => {
@@ -200,7 +222,7 @@ describe('LabelToken', () => {
config: { ...mockLabelToken, defaultLabels: [] },
stubs: { Portal: true },
});
- const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment);
+ const tokenSegments = findTokenSegments();
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
await nextTick();
@@ -215,11 +237,10 @@ describe('LabelToken', () => {
config: { ...mockLabelToken },
stubs: { Portal: true },
});
- const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment);
+ const tokenSegments = findTokenSegments();
const suggestionsSegment = tokenSegments.at(2);
suggestionsSegment.vm.$emit('activate');
-
- const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion);
+ const suggestions = findSuggestions();
expect(suggestions).toHaveLength(OPTIONS_NONE_ANY.length);
OPTIONS_NONE_ANY.forEach((label, index) => {
@@ -234,7 +255,7 @@ describe('LabelToken', () => {
input: mockInput,
},
});
- wrapper.findComponent(BaseToken).vm.$emit('input', [{ data: 'mockData', operator: '=' }]);
+ findBaseToken().vm.$emit('input', [{ data: 'mockData', operator: '=' }]);
expect(mockInput).toHaveBeenLastCalledWith([{ data: 'mockData', operator: '=' }]);
});
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 589697fe542..fea1496a80b 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
@@ -8,16 +8,17 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { sortMilestonesByDueDate } from '~/milestones/utils';
import { DEFAULT_MILESTONES } from '~/vue_shared/components/filtered_search_bar/constants';
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
+import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
import { mockMilestoneToken, mockMilestones, mockRegularMilestone } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/milestones/utils');
const defaultStubs = {
@@ -57,6 +58,12 @@ describe('MilestoneToken', () => {
let mock;
let wrapper;
+ const findBaseToken = () => wrapper.findComponent(BaseToken);
+ const triggerFetchMilestones = (searchTerm = null) => {
+ findBaseToken().vm.$emit('fetch-suggestions', searchTerm);
+ return waitForPromises();
+ };
+
beforeEach(() => {
mock = new MockAdapter(axios);
wrapper = createComponent();
@@ -64,73 +71,77 @@ describe('MilestoneToken', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
describe('methods', () => {
describe('fetchMilestones', () => {
- describe('when config.shouldSkipSort is true', () => {
- beforeEach(() => {
- wrapper.vm.config.shouldSkipSort = true;
+ it('sets loading state', async () => {
+ wrapper = createComponent({
+ config: {
+ fetchMilestones: jest.fn().mockResolvedValue(new Promise(() => {})),
+ },
});
+ await nextTick();
- afterEach(() => {
- wrapper.vm.config.shouldSkipSort = false;
- });
+ expect(findBaseToken().props('suggestionsLoading')).toBe(true);
+ });
+
+ describe('when config.shouldSkipSort is true', () => {
it('does not call sortMilestonesByDueDate', async () => {
- jest.spyOn(wrapper.vm.config, 'fetchMilestones').mockResolvedValue({
- data: mockMilestones,
+ wrapper = createComponent({
+ config: {
+ shouldSkipSort: true,
+ fetchMilestones: jest.fn().mockResolvedValue({ data: mockMilestones }),
+ },
});
- wrapper.vm.fetchMilestones();
-
- await waitForPromises();
+ await triggerFetchMilestones();
expect(sortMilestonesByDueDate).toHaveBeenCalledTimes(0);
});
});
- it('calls `config.fetchMilestones` with provided searchTerm param', () => {
- jest.spyOn(wrapper.vm.config, 'fetchMilestones');
-
- wrapper.vm.fetchMilestones('foo');
+ describe('when request is successful', () => {
+ const searchTerm = 'foo';
- expect(wrapper.vm.config.fetchMilestones).toHaveBeenCalledWith('foo');
- });
-
- it('sets response to `milestones` when request is successful', () => {
- wrapper.vm.config.shouldSkipSort = false;
+ beforeEach(() => {
+ wrapper = createComponent({
+ config: {
+ shouldSkipSort: false,
+ fetchMilestones: jest.fn().mockResolvedValue({ data: mockMilestones }),
+ },
+ });
+ return triggerFetchMilestones(searchTerm);
+ });
- jest.spyOn(wrapper.vm.config, 'fetchMilestones').mockResolvedValue({
- data: mockMilestones,
+ it('calls `config.fetchMilestones` with provided searchTerm param', () => {
+ expect(findBaseToken().props('config').fetchMilestones).toHaveBeenCalledWith(searchTerm);
});
- wrapper.vm.fetchMilestones();
- return waitForPromises().then(() => {
- expect(wrapper.vm.milestones).toEqual(mockMilestones);
+ it('sets response to `milestones`', () => {
expect(sortMilestonesByDueDate).toHaveBeenCalled();
+ expect(findBaseToken().props('suggestions')).toEqual(mockMilestones);
});
});
- it('calls `createAlert` with flash error message when request fails', () => {
- jest.spyOn(wrapper.vm.config, 'fetchMilestones').mockRejectedValue({});
-
- wrapper.vm.fetchMilestones('foo');
+ describe('when request fails', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ config: {
+ fetchMilestones: jest.fn().mockRejectedValue({}),
+ },
+ });
+ return triggerFetchMilestones();
+ });
- return waitForPromises().then(() => {
+ it('calls `createAlert` with alert error message', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'There was a problem fetching milestones.',
});
});
- });
- it('sets `loading` to false when request completes', () => {
- jest.spyOn(wrapper.vm.config, 'fetchMilestones').mockRejectedValue({});
-
- wrapper.vm.fetchMilestones('foo');
-
- return waitForPromises().then(() => {
- expect(wrapper.vm.loading).toBe(false);
+ it('sets `loading` to false when request completes', () => {
+ expect(findBaseToken().props('suggestionsLoading')).toBe(false);
});
});
});
@@ -143,15 +154,12 @@ describe('MilestoneToken', () => {
];
beforeEach(async () => {
- wrapper = createComponent({ value: { data: `"${mockRegularMilestone.title}"` } });
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- milestones: mockMilestones,
+ wrapper = createComponent({
+ value: { data: `"${mockRegularMilestone.title}"` },
+ config: {
+ initialMilestones: mockMilestones,
+ },
});
-
- await nextTick();
});
it('renders gl-filtered-search-token component', () => {
@@ -228,7 +236,7 @@ describe('MilestoneToken', () => {
it('finds the correct value from the activeToken', () => {
DEFAULT_MILESTONES.forEach(({ value, title }) => {
- const activeToken = wrapper.vm.getActiveMilestone([], value);
+ const activeToken = findBaseToken().props('getActiveTokenValue')([], value);
expect(activeToken.title).toEqual(title);
});
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 0e5fa0f66d4..5190ab919b1 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
@@ -2,11 +2,11 @@ import { GlFilteredSearchToken, GlFilteredSearchTokenSegment } from '@gitlab/ui'
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import ReleaseToken from '~/vue_shared/components/filtered_search_bar/tokens/release_token.vue';
import { mockReleaseToken } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('ReleaseToken', () => {
const id = '123';
@@ -27,10 +27,6 @@ describe('ReleaseToken', () => {
},
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders release value', async () => {
wrapper = createComponent({ value: { data: id } });
await nextTick();
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js
index 32cb74d5f80..89003296854 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/user_token_spec.js
@@ -8,7 +8,7 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { OPTIONS_NONE_ANY } from '~/vue_shared/components/filtered_search_bar/constants';
@@ -17,7 +17,7 @@ import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_t
import { mockAuthorToken, mockUsers } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
const defaultStubs = {
Portal: true,
GlFilteredSearchSuggestionList: {
@@ -67,99 +67,82 @@ function createComponent(options = {}) {
}
describe('UserToken', () => {
- const originalGon = window.gon;
const currentUserLength = 1;
let mock;
let wrapper;
- const getBaseToken = () => wrapper.findComponent(BaseToken);
+ const findBaseToken = () => wrapper.findComponent(BaseToken);
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
- window.gon = originalGon;
mock.restore();
- wrapper.destroy();
});
describe('methods', () => {
describe('fetchUsers', () => {
+ const triggerFetchUsers = (searchTerm = null) => {
+ findBaseToken().vm.$emit('fetch-suggestions', searchTerm);
+ return waitForPromises();
+ };
+
beforeEach(() => {
wrapper = createComponent();
});
- it('calls `config.fetchUsers` with provided searchTerm param', () => {
- jest.spyOn(wrapper.vm.config, 'fetchUsers');
-
- getBaseToken().vm.$emit('fetch-suggestions', mockUsers[0].username);
-
- expect(wrapper.vm.config.fetchUsers).toHaveBeenCalledWith(
- mockAuthorToken.fetchPath,
- mockUsers[0].username,
- );
- });
-
- it('sets response to `users` when request is successful', () => {
- jest.spyOn(wrapper.vm.config, 'fetchUsers').mockResolvedValue(mockUsers);
-
- getBaseToken().vm.$emit('fetch-suggestions', 'root');
-
- return waitForPromises().then(() => {
- expect(getBaseToken().props('suggestions')).toEqual(mockUsers);
+ it('sets loading state', async () => {
+ wrapper = createComponent({
+ config: {
+ fetchUsers: jest.fn().mockResolvedValue(new Promise(() => {})),
+ },
});
+ await nextTick();
+
+ expect(findBaseToken().props('suggestionsLoading')).toBe(true);
});
- // TODO: rm when completed https://gitlab.com/gitlab-org/gitlab/-/issues/345756
- describe('when there are null users presents', () => {
- const mockUsersWithNullUser = mockUsers.concat([null]);
+ describe('when request is successful', () => {
+ const searchTerm = 'foo';
beforeEach(() => {
- jest
- .spyOn(wrapper.vm.config, 'fetchUsers')
- .mockResolvedValue({ data: mockUsersWithNullUser });
-
- getBaseToken().vm.$emit('fetch-suggestions', 'root');
+ wrapper = createComponent({
+ config: {
+ fetchUsers: jest.fn().mockResolvedValue({ data: mockUsers }),
+ },
+ });
+ return triggerFetchUsers(searchTerm);
});
- describe('when res.data is present', () => {
- it('filters the successful response when null values are present', () => {
- return waitForPromises().then(() => {
- expect(getBaseToken().props('suggestions')).toEqual(mockUsers);
- });
- });
+ it('calls `config.fetchUsers` with provided searchTerm param', () => {
+ expect(findBaseToken().props('config').fetchUsers).toHaveBeenCalledWith(searchTerm);
});
- describe('when response is an array', () => {
- it('filters the successful response when null values are present', () => {
- return waitForPromises().then(() => {
- expect(getBaseToken().props('suggestions')).toEqual(mockUsers);
- });
- });
+ it('sets response to `users` when request is successful', () => {
+ expect(findBaseToken().props('suggestions')).toEqual(mockUsers);
});
});
- it('calls `createAlert` with flash error message when request fails', () => {
- jest.spyOn(wrapper.vm.config, 'fetchUsers').mockRejectedValue({});
-
- getBaseToken().vm.$emit('fetch-suggestions', 'root');
+ describe('when request fails', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ config: {
+ fetchUsers: jest.fn().mockRejectedValue({}),
+ },
+ });
+ return triggerFetchUsers();
+ });
- return waitForPromises().then(() => {
+ it('calls `createAlert` with alert error message', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'There was a problem fetching users.',
});
});
- });
-
- it('sets `loading` to false when request completes', async () => {
- jest.spyOn(wrapper.vm.config, 'fetchUsers').mockRejectedValue({});
-
- getBaseToken().vm.$emit('fetch-suggestions', 'root');
- await waitForPromises();
-
- expect(getBaseToken().props('suggestionsLoading')).toBe(false);
+ it('sets `loading` to false when request completes', async () => {
+ expect(findBaseToken().props('suggestionsLoading')).toBe(false);
+ });
});
});
});
@@ -178,12 +161,12 @@ describe('UserToken', () => {
data: { users: mockUsers },
});
- const baseTokenEl = getBaseToken();
+ const baseTokenEl = findBaseToken();
expect(baseTokenEl.exists()).toBe(true);
expect(baseTokenEl.props()).toMatchObject({
suggestions: mockUsers,
- getActiveTokenValue: wrapper.vm.getActiveUser,
+ getActiveTokenValue: baseTokenEl.props('getActiveTokenValue'),
});
});
@@ -191,7 +174,6 @@ describe('UserToken', () => {
wrapper = createComponent({
value: { data: mockUsers[0].username },
data: { users: mockUsers },
- stubs: { Portal: true },
});
await nextTick();
@@ -215,30 +197,13 @@ describe('UserToken', () => {
users: [
{
...mockUsers[0],
+ avatarUrl: mockUsers[0].avatar_url,
+ avatar_url: undefined,
},
],
},
- stubs: { Portal: true },
});
- await nextTick();
-
- expect(getAvatarEl().props('src')).toBe(mockUsers[0].avatar_url);
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- users: [
- {
- ...mockUsers[0],
- avatarUrl: mockUsers[0].avatar_url,
- avatar_url: undefined,
- },
- ],
- });
-
- await nextTick();
-
expect(getAvatarEl().props('src')).toBe(mockUsers[0].avatar_url);
});
@@ -264,7 +229,6 @@ describe('UserToken', () => {
wrapper = createComponent({
active: true,
config: { ...mockAuthorToken, defaultUsers: [] },
- stubs: { Portal: true },
});
const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment);
const suggestionsSegment = tokenSegments.at(2);
diff --git a/spec/frontend/vue_shared/components/form/form_footer_actions_spec.js b/spec/frontend/vue_shared/components/form/form_footer_actions_spec.js
index 361b162b6a0..eee8a0c4532 100644
--- a/spec/frontend/vue_shared/components/form/form_footer_actions_spec.js
+++ b/spec/frontend/vue_shared/components/form/form_footer_actions_spec.js
@@ -10,10 +10,6 @@ describe('Form Footer Actions', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders content properly', () => {
const defaultSlot = 'Foo';
const prepend = 'Bar';
diff --git a/spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js b/spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js
index e1da8b690af..4f1603f93ba 100644
--- a/spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js
+++ b/spec/frontend/vue_shared/components/form/input_copy_toggle_visibility_spec.js
@@ -10,10 +10,6 @@ import { mountExtended } from 'helpers/vue_test_utils_helper';
describe('InputCopyToggleVisibility', () => {
let wrapper;
- afterEach(() => {
- wrapper.destroy();
- });
-
const valueProp = 'hR8x1fuJbzwu5uFKLf9e';
const createComponent = (options = {}) => {
@@ -21,7 +17,7 @@ describe('InputCopyToggleVisibility', () => {
InputCopyToggleVisibility,
merge({}, options, {
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
}),
);
diff --git a/spec/frontend/vue_shared/components/form/title_spec.js b/spec/frontend/vue_shared/components/form/title_spec.js
index 452f3723e76..d499f847c72 100644
--- a/spec/frontend/vue_shared/components/form/title_spec.js
+++ b/spec/frontend/vue_shared/components/form/title_spec.js
@@ -12,10 +12,6 @@ describe('Title edit field', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
diff --git a/spec/frontend/vue_shared/components/gl_countdown_spec.js b/spec/frontend/vue_shared/components/gl_countdown_spec.js
index af53d256236..1de206123fe 100644
--- a/spec/frontend/vue_shared/components/gl_countdown_spec.js
+++ b/spec/frontend/vue_shared/components/gl_countdown_spec.js
@@ -10,10 +10,6 @@ describe('GlCountdown', () => {
jest.spyOn(Date, 'now').mockImplementation(() => new Date(now).getTime());
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when there is time remaining', () => {
beforeEach(async () => {
wrapper = mount(GlCountdown, {
diff --git a/spec/frontend/vue_shared/components/header_ci_component_spec.js b/spec/frontend/vue_shared/components/header_ci_component_spec.js
index 458f2cc5374..da9bc0f8a2f 100644
--- a/spec/frontend/vue_shared/components/header_ci_component_spec.js
+++ b/spec/frontend/vue_shared/components/header_ci_component_spec.js
@@ -48,11 +48,6 @@ describe('Header CI Component', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('render', () => {
beforeEach(() => {
createComponent({ itemName: 'Pipeline' });
diff --git a/spec/frontend/vue_shared/components/help_popover_spec.js b/spec/frontend/vue_shared/components/help_popover_spec.js
index 77c03dc0c3c..76e66d07fa0 100644
--- a/spec/frontend/vue_shared/components/help_popover_spec.js
+++ b/spec/frontend/vue_shared/components/help_popover_spec.js
@@ -23,10 +23,6 @@ describe('HelpPopover', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with title and content', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/vue_shared/components/integration_help_text_spec.js b/spec/frontend/vue_shared/components/integration_help_text_spec.js
index c63e46313b3..dd20b09f176 100644
--- a/spec/frontend/vue_shared/components/integration_help_text_spec.js
+++ b/spec/frontend/vue_shared/components/integration_help_text_spec.js
@@ -22,11 +22,6 @@ describe('IntegrationHelpText component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should use the gl components', () => {
wrapper = createComponent();
diff --git a/spec/frontend/vue_shared/components/keep_alive_slots_spec.js b/spec/frontend/vue_shared/components/keep_alive_slots_spec.js
index 10c6cbe6d94..f69a883ee4d 100644
--- a/spec/frontend/vue_shared/components/keep_alive_slots_spec.js
+++ b/spec/frontend/vue_shared/components/keep_alive_slots_spec.js
@@ -37,10 +37,6 @@ describe('~/vue_shared/components/keep_alive_slots.vue', () => {
isVisible: x.isVisible(),
}));
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js b/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js
index 7ed6a59c844..4e83f3e1c06 100644
--- a/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js
+++ b/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { GlFormGroup, GlListbox } from '@gitlab/ui';
+import { GlFormGroup, GlCollapsibleListbox } from '@gitlab/ui';
import ListboxInput from '~/vue_shared/components/listbox_input/listbox_input.vue';
describe('ListboxInput', () => {
@@ -27,7 +27,7 @@ describe('ListboxInput', () => {
// Finders
const findGlFormGroup = () => wrapper.findComponent(GlFormGroup);
- const findGlListbox = () => wrapper.findComponent(GlListbox);
+ const findGlListbox = () => wrapper.findComponent(GlCollapsibleListbox);
const findInput = () => wrapper.find('input');
const createComponent = (propsData) => {
@@ -153,7 +153,7 @@ describe('ListboxInput', () => {
expect(findGlListbox().props('searchable')).toBe(true);
});
- it('passes all items to GlListbox by default', () => {
+ it('passes all items to GlCollapsibleListbox by default', () => {
createComponent();
expect(findGlListbox().props('items')).toStrictEqual(items);
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 a80717a1aea..1c7f419b118 100644
--- a/spec/frontend/vue_shared/components/local_storage_sync_spec.js
+++ b/spec/frontend/vue_shared/components/local_storage_sync_spec.js
@@ -17,7 +17,6 @@ describe('Local Storage Sync', () => {
const getStorageValue = (value) => localStorage.getItem(STORAGE_KEY, value);
afterEach(() => {
- wrapper.destroy();
localStorage.clear();
});
diff --git a/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js b/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js
index ecb2b37c3a5..8aab867f32a 100644
--- a/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/apply_suggestion_spec.js
@@ -17,11 +17,6 @@ describe('Apply Suggestion component', () => {
beforeEach(() => createWrapper());
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('initial template', () => {
it('renders a dropdown with the correct props', () => {
const dropdown = findDropdown();
diff --git a/spec/frontend/vue_shared/components/markdown/drawio_toolbar_button_spec.js b/spec/frontend/vue_shared/components/markdown/drawio_toolbar_button_spec.js
new file mode 100644
index 00000000000..67f296b1bf0
--- /dev/null
+++ b/spec/frontend/vue_shared/components/markdown/drawio_toolbar_button_spec.js
@@ -0,0 +1,66 @@
+import { GlButton } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import DrawioToolbarButton from '~/vue_shared/components/markdown/drawio_toolbar_button.vue';
+import { launchDrawioEditor } from '~/drawio/drawio_editor';
+import { create } from '~/drawio/markdown_field_editor_facade';
+
+jest.mock('~/drawio/drawio_editor');
+jest.mock('~/drawio/markdown_field_editor_facade');
+
+describe('vue_shared/components/markdown/drawio_toolbar_button', () => {
+ let wrapper;
+ let textArea;
+ const uploadsPath = '/uploads';
+ const markdownPreviewPath = '/markdown/preview';
+
+ const buildWrapper = (props = { uploadsPath, markdownPreviewPath }) => {
+ wrapper = shallowMount(DrawioToolbarButton, {
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ textArea = document.createElement('textarea');
+ textArea.classList.add('js-gfm-input');
+
+ document.body.appendChild(textArea);
+ });
+
+ afterEach(() => {
+ textArea.remove();
+ });
+
+ describe('default', () => {
+ it('renders button that launches draw.io editor', () => {
+ buildWrapper();
+
+ expect(wrapper.findComponent(GlButton).props()).toMatchObject({
+ icon: 'diagram',
+ category: 'tertiary',
+ });
+ });
+ });
+
+ describe('when clicking button', () => {
+ it('launches draw.io editor', async () => {
+ const editorFacadeStub = {};
+
+ create.mockReturnValueOnce(editorFacadeStub);
+
+ buildWrapper();
+
+ await wrapper.findComponent(GlButton).vm.$emit('click');
+
+ expect(create).toHaveBeenCalledWith({
+ markdownPreviewPath,
+ textArea,
+ uploadsPath,
+ });
+ expect(launchDrawioEditor).toHaveBeenCalledWith({
+ editorFacade: editorFacadeStub,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/markdown/editor_mode_dropdown_spec.js b/spec/frontend/vue_shared/components/markdown/editor_mode_dropdown_spec.js
index 34071775b9c..becd4257cbe 100644
--- a/spec/frontend/vue_shared/components/markdown/editor_mode_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/editor_mode_dropdown_spec.js
@@ -21,14 +21,10 @@ describe('vue_shared/component/markdown/editor_mode_dropdown', () => {
.filter((item) => item.text().startsWith(text))
.at(0);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe.each`
- modeText | value | dropdownText | otherMode
- ${'Rich text'} | ${'richText'} | ${'View markdown'} | ${'Markdown'}
- ${'Markdown'} | ${'markdown'} | ${'View rich text'} | ${'Rich text'}
+ modeText | value | dropdownText | otherMode
+ ${'Rich text'} | ${'richText'} | ${'Viewing rich text'} | ${'Markdown'}
+ ${'Markdown'} | ${'markdown'} | ${'Viewing markdown'} | ${'Rich text'}
`('$modeText', ({ modeText, value, dropdownText, otherMode }) => {
beforeEach(() => {
createComponent({ value });
diff --git a/spec/frontend/vue_shared/components/markdown/field_view_spec.js b/spec/frontend/vue_shared/components/markdown/field_view_spec.js
index 176ccfc5a69..1bbbe0896f2 100644
--- a/spec/frontend/vue_shared/components/markdown/field_view_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/field_view_spec.js
@@ -6,20 +6,14 @@ import { renderGFM } from '~/behaviors/markdown/render_gfm';
jest.mock('~/behaviors/markdown/render_gfm');
describe('Markdown Field View component', () => {
- let wrapper;
-
function createComponent() {
- wrapper = shallowMount(MarkdownFieldView);
+ shallowMount(MarkdownFieldView);
}
beforeEach(() => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('processes rendering with GFM', () => {
expect(renderGFM).toHaveBeenCalledTimes(1);
});
diff --git a/spec/frontend/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js
index ed417097e1e..68f05e5119d 100644
--- a/spec/frontend/vue_shared/components/markdown/header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/header_spec.js
@@ -3,6 +3,7 @@ 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 DrawioToolbarButton from '~/vue_shared/components/markdown/drawio_toolbar_button.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
describe('Markdown field header component', () => {
@@ -26,6 +27,7 @@ describe('Markdown field header component', () => {
findToolbarButtons()
.filter((button) => button.props(prop) === value)
.at(0);
+ const findDrawioToolbarButton = () => wrapper.findComponent(DrawioToolbarButton);
beforeEach(() => {
window.gl = {
@@ -37,10 +39,6 @@ describe('Markdown field header component', () => {
createWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('markdown header buttons', () => {
it('renders the buttons with the correct title', () => {
const buttons = [
@@ -197,4 +195,24 @@ describe('Markdown field header component', () => {
expect(findToolbarButtons().length).toBe(defaultCount);
});
});
+
+ describe('when drawIOEnabled is true', () => {
+ const uploadsPath = '/uploads';
+ const markdownPreviewPath = '/preview';
+
+ beforeEach(() => {
+ createWrapper({
+ drawioEnabled: true,
+ uploadsPath,
+ markdownPreviewPath,
+ });
+ });
+
+ it('renders drawio toolbar button', () => {
+ expect(findDrawioToolbarButton().props()).toEqual({
+ uploadsPath,
+ markdownPreviewPath,
+ });
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js
index 26b536984ff..681ff6c8dd3 100644
--- a/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/markdown_editor_spec.js
@@ -1,4 +1,5 @@
import axios from 'axios';
+import Autosize from 'autosize';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
@@ -9,10 +10,15 @@ import BubbleMenu from '~/content_editor/components/bubble_menus/bubble_menu.vue
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { stubComponent } from 'helpers/stub_component';
+import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import waitForPromises from 'helpers/wait_for_promises';
jest.mock('~/emoji');
+jest.mock('autosize');
describe('vue_shared/component/markdown/markdown_editor', () => {
+ useLocalStorageSpy();
+
let wrapper;
const value = 'test markdown';
const renderMarkdownPath = '/api/markdown';
@@ -57,14 +63,27 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
const findContentEditor = () => wrapper.findComponent(ContentEditor);
+ const enableContentEditor = async () => {
+ findMarkdownField().vm.$emit('enableContentEditor');
+ await nextTick();
+ await waitForPromises();
+ };
+
+ const enableMarkdownEditor = async () => {
+ findContentEditor().vm.$emit('enableMarkdownEditor');
+ await nextTick();
+ await waitForPromises();
+ };
+
beforeEach(() => {
window.uploads_path = 'uploads';
mock = new MockAdapter(axios);
});
afterEach(() => {
- wrapper.destroy();
mock.restore();
+
+ localStorage.clear();
});
it('displays markdown field by default', () => {
@@ -83,8 +102,133 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
});
});
+ it('enables content editor switcher when contentEditorEnabled prop is true', () => {
+ buildWrapper({ propsData: { enableContentEditor: true } });
+
+ expect(findMarkdownField().text()).toContain('Rich text');
+ });
+
+ it('hides content editor switcher when contentEditorEnabled prop is false', () => {
+ buildWrapper({ propsData: { enableContentEditor: false } });
+
+ expect(findMarkdownField().text()).not.toContain('Rich text');
+ });
+
+ it('passes down any additional props to markdown field component', () => {
+ const propsData = {
+ line: { text: 'hello world', richText: 'hello world' },
+ lines: [{ text: 'hello world', richText: 'hello world' }],
+ canSuggest: true,
+ };
+
+ buildWrapper({
+ propsData: { ...propsData, myCustomProp: 'myCustomValue', 'data-testid': 'custom id' },
+ });
+
+ expect(findMarkdownField().props()).toMatchObject(propsData);
+ expect(findMarkdownField().vm.$attrs).toMatchObject({
+ myCustomProp: 'myCustomValue',
+
+ // data-testid isn't copied over
+ 'data-testid': 'markdown-field',
+ });
+ });
+
+ describe('disabled', () => {
+ it('disables markdown field when disabled prop is true', () => {
+ buildWrapper({ propsData: { disabled: true } });
+
+ expect(findMarkdownField().find('textarea').attributes('disabled')).toBe('disabled');
+ });
+
+ it('enables markdown field when disabled prop is false', () => {
+ buildWrapper({ propsData: { disabled: false } });
+
+ expect(findMarkdownField().find('textarea').attributes('disabled')).toBe(undefined);
+ });
+
+ it('disables content editor when disabled prop is true', async () => {
+ buildWrapper({ propsData: { disabled: true } });
+
+ await enableContentEditor();
+
+ expect(findContentEditor().props('editable')).toBe(false);
+ });
+
+ it('enables content editor when disabled prop is false', async () => {
+ buildWrapper({ propsData: { disabled: false } });
+
+ await enableContentEditor();
+
+ expect(findContentEditor().props('editable')).toBe(true);
+ });
+ });
+
+ describe('autosize', () => {
+ it('autosizes the textarea when the value changes', async () => {
+ buildWrapper();
+ await findTextarea().setValue('Lots of newlines\n\n\n\n\n\n\nMore content\n\n\nand newlines');
+
+ expect(Autosize.update).toHaveBeenCalled();
+ });
+
+ it('autosizes the textarea when the value changes from outside the component', async () => {
+ buildWrapper();
+ wrapper.setProps({ value: 'Lots of newlines\n\n\n\n\n\n\nMore content\n\n\nand newlines' });
+
+ await nextTick();
+ await waitForPromises();
+ expect(Autosize.update).toHaveBeenCalled();
+ });
+
+ it('does not autosize the textarea if markdown editor is disabled', async () => {
+ buildWrapper();
+ await enableContentEditor();
+
+ wrapper.setProps({ value: 'Lots of newlines\n\n\n\n\n\n\nMore content\n\n\nand newlines' });
+
+ expect(Autosize.update).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('autosave', () => {
+ it('automatically saves the textarea value to local storage if autosaveKey is defined', () => {
+ buildWrapper({ propsData: { autosaveKey: 'issue/1234', value: 'This is **markdown**' } });
+
+ expect(localStorage.getItem('autosave/issue/1234')).toBe('This is **markdown**');
+ });
+
+ it("loads value from local storage if autosaveKey is defined, and value isn't", () => {
+ localStorage.setItem('autosave/issue/1234', 'This is **markdown**');
+
+ buildWrapper({ propsData: { autosaveKey: 'issue/1234', value: '' } });
+
+ expect(findTextarea().element.value).toBe('This is **markdown**');
+ });
+
+ it("doesn't load value from local storage if autosaveKey is defined, and value is", () => {
+ localStorage.setItem('autosave/issue/1234', 'This is **markdown**');
+
+ buildWrapper({ propsData: { autosaveKey: 'issue/1234' } });
+
+ expect(findTextarea().element.value).toBe('test markdown');
+ });
+
+ it('does not save the textarea value to local storage if autosaveKey is not defined', () => {
+ buildWrapper({ propsData: { value: 'This is **markdown**' } });
+
+ expect(localStorage.setItem).not.toHaveBeenCalled();
+ });
+
+ it('does not save the textarea value to local storage if value is empty', () => {
+ buildWrapper({ propsData: { autosaveKey: 'issue/1234', value: '' } });
+
+ expect(localStorage.setItem).not.toHaveBeenCalled();
+ });
+ });
+
it('renders markdown field textarea', () => {
- buildWrapper();
+ buildWrapper({ propsData: { supportsQuickActions: true } });
expect(findTextarea().attributes()).toEqual(
expect.objectContaining({
@@ -92,6 +236,7 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
name: formFieldName,
placeholder: formFieldPlaceholder,
'aria-label': formFieldAriaLabel,
+ 'data-supports-quick-actions': 'true',
}),
);
@@ -107,9 +252,7 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
it(`emits ${EDITING_MODE_CONTENT_EDITOR} event when enableContentEditor emitted from markdown editor`, async () => {
buildWrapper();
- findMarkdownField().vm.$emit('enableContentEditor');
-
- await nextTick();
+ await enableContentEditor();
expect(wrapper.emitted(EDITING_MODE_CONTENT_EDITOR)).toHaveLength(1);
});
@@ -119,11 +262,8 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
stubs: { ContentEditor: stubComponent(ContentEditor) },
});
- findMarkdownField().vm.$emit('enableContentEditor');
-
- await nextTick();
-
- findContentEditor().vm.$emit('enableMarkdownEditor');
+ await enableContentEditor();
+ await enableMarkdownEditor();
expect(wrapper.emitted(EDITING_MODE_MARKDOWN_FIELD)).toHaveLength(1);
});
@@ -138,6 +278,16 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
expect(wrapper.emitted('input')).toEqual([[newValue]]);
});
+ it('autosaves the markdown value to local storage', async () => {
+ buildWrapper({ propsData: { autosaveKey: 'issue/1234' } });
+
+ const newValue = 'new value';
+
+ await findTextarea().setValue(newValue);
+
+ expect(localStorage.getItem('autosave/issue/1234')).toBe(newValue);
+ });
+
describe('when autofocus is true', () => {
beforeEach(async () => {
buildWrapper({ attachTo: document.body, propsData: { autofocus: true } });
@@ -159,9 +309,9 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
});
describe(`when markdown field triggers enableContentEditor event`, () => {
- beforeEach(() => {
+ beforeEach(async () => {
buildWrapper();
- findMarkdownField().vm.$emit('enableContentEditor');
+ await enableContentEditor();
});
it('displays the content editor', () => {
@@ -169,7 +319,6 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
expect.objectContaining({
renderMarkdown: expect.any(Function),
uploadsPath: window.uploads_path,
- useBottomToolbar: false,
markdown: value,
}),
);
@@ -198,9 +347,9 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
});
describe(`when editingMode is ${EDITING_MODE_CONTENT_EDITOR}`, () => {
- beforeEach(() => {
- buildWrapper();
- findMarkdownField().vm.$emit('enableContentEditor');
+ beforeEach(async () => {
+ buildWrapper({ propsData: { autosaveKey: 'issue/1234' } });
+ await enableContentEditor();
});
describe('when autofocus is true', () => {
@@ -224,6 +373,14 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
expect(wrapper.emitted('input')).toEqual([[newValue]]);
});
+ it('autosaves the content editor value to local storage', async () => {
+ const newValue = 'new value';
+
+ await findContentEditor().vm.$emit('change', { markdown: newValue });
+
+ expect(localStorage.getItem('autosave/issue/1234')).toBe(newValue);
+ });
+
it('bubbles up keydown event', () => {
const event = new Event('keydown');
@@ -233,9 +390,7 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
});
describe(`when richText editor triggers enableMarkdownEditor event`, () => {
- beforeEach(() => {
- findContentEditor().vm.$emit('enableMarkdownEditor');
- });
+ beforeEach(enableMarkdownEditor);
it('hides the content editor', () => {
expect(findContentEditor().exists()).toBe(false);
diff --git a/spec/frontend/vue_shared/components/markdown/saved_replies_dropdown_spec.js b/spec/frontend/vue_shared/components/markdown/saved_replies_dropdown_spec.js
new file mode 100644
index 00000000000..8ad9ad30c1d
--- /dev/null
+++ b/spec/frontend/vue_shared/components/markdown/saved_replies_dropdown_spec.js
@@ -0,0 +1,62 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import savedRepliesResponse from 'test_fixtures/graphql/saved_replies/saved_replies.query.graphql.json';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import SavedRepliesDropdown from '~/vue_shared/components/markdown/saved_replies_dropdown.vue';
+import savedRepliesQuery from '~/vue_shared/components/markdown/saved_replies.query.graphql';
+
+let wrapper;
+let savedRepliesResp;
+
+function createMockApolloProvider(response) {
+ Vue.use(VueApollo);
+
+ savedRepliesResp = jest.fn().mockResolvedValue(response);
+
+ const requestHandlers = [[savedRepliesQuery, savedRepliesResp]];
+
+ return createMockApollo(requestHandlers);
+}
+
+function createComponent(options = {}) {
+ const { mockApollo } = options;
+
+ return mountExtended(SavedRepliesDropdown, {
+ propsData: {
+ newSavedRepliesPath: '/new',
+ },
+ apolloProvider: mockApollo,
+ });
+}
+
+describe('Saved replies dropdown', () => {
+ it('fetches data when dropdown gets opened', async () => {
+ const mockApollo = createMockApolloProvider(savedRepliesResponse);
+ wrapper = createComponent({ mockApollo });
+
+ wrapper.findByTestId('saved-replies-dropdown-toggle').trigger('click');
+
+ await waitForPromises();
+
+ expect(savedRepliesResp).toHaveBeenCalled();
+ });
+
+ it('adds markdown toolbar attributes to dropdown items', async () => {
+ const mockApollo = createMockApolloProvider(savedRepliesResponse);
+ wrapper = createComponent({ mockApollo });
+
+ wrapper.findByTestId('saved-replies-dropdown-toggle').trigger('click');
+
+ await waitForPromises();
+
+ expect(wrapper.findByTestId('saved-reply-dropdown-item').attributes()).toEqual(
+ expect.objectContaining({
+ 'data-md-cursor-offset': '0',
+ 'data-md-prepend': 'true',
+ 'data-md-tag': 'Saved Reply Content',
+ }),
+ );
+ });
+});
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 9db1b779a04..9768bc7a6dd 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
@@ -25,7 +25,7 @@ describe('Suggestion Diff component', () => {
...props,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
@@ -34,10 +34,6 @@ describe('Suggestion Diff component', () => {
window.gon.current_user_id = 1;
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findApplyButton = () => wrapper.findComponent(ApplySuggestion);
const findApplyBatchButton = () => wrapper.find('.js-apply-batch-btn');
const findAddToBatchButton = () => wrapper.find('.js-add-to-batch-btn');
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js
index f9a8b64f89b..c46a2d3e117 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_row_spec.js
@@ -36,10 +36,6 @@ describe('SuggestionDiffRow', () => {
const findNewLineWrapper = () => wrapper.find('.new_line');
const findSuggestionContent = () => wrapper.find('[data-testid="suggestion-diff-content"]');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('renders correctly', () => {
it('renders the correct base suggestion markup', () => {
factory({
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js
index d84483c1663..8c7f51664ad 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js
@@ -61,11 +61,6 @@ describe('Suggestion Diff component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('matches snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
diff --git a/spec/frontend/vue_shared/components/markdown/toolbar_button_spec.js b/spec/frontend/vue_shared/components/markdown/toolbar_button_spec.js
index 82210e79799..33e9d6add99 100644
--- a/spec/frontend/vue_shared/components/markdown/toolbar_button_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/toolbar_button_spec.js
@@ -20,11 +20,6 @@ describe('toolbar_button', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const getButtonShortcutsAttr = () => {
return wrapper.findComponent(GlButton).attributes('data-md-shortcuts');
};
diff --git a/spec/frontend/vue_shared/components/markdown/toolbar_spec.js b/spec/frontend/vue_shared/components/markdown/toolbar_spec.js
index b1a1dbbeb7a..fea14f80496 100644
--- a/spec/frontend/vue_shared/components/markdown/toolbar_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/toolbar_spec.js
@@ -11,10 +11,6 @@ describe('toolbar', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('user can attach file', () => {
beforeEach(() => {
createMountedWrapper();
diff --git a/spec/frontend/vue_shared/components/markdown_drawer/markdown_drawer_spec.js b/spec/frontend/vue_shared/components/markdown_drawer/markdown_drawer_spec.js
index 2b311b75f85..37b0767616a 100644
--- a/spec/frontend/vue_shared/components/markdown_drawer/markdown_drawer_spec.js
+++ b/spec/frontend/vue_shared/components/markdown_drawer/markdown_drawer_spec.js
@@ -36,8 +36,6 @@ describe('MarkdownDrawer', () => {
};
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
Object.keys(cache).forEach((key) => delete cache[key]);
});
diff --git a/spec/frontend/vue_shared/components/memory_graph_spec.js b/spec/frontend/vue_shared/components/memory_graph_spec.js
index ae8d5ff78ba..81325fb3269 100644
--- a/spec/frontend/vue_shared/components/memory_graph_spec.js
+++ b/spec/frontend/vue_shared/components/memory_graph_spec.js
@@ -1,10 +1,8 @@
import { GlSparklineChart } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
import MemoryGraph from '~/vue_shared/components/memory_graph.vue';
describe('MemoryGraph', () => {
- const Component = Vue.extend(MemoryGraph);
let wrapper;
const metrics = [
[1573586253.853, '2.87'],
@@ -13,12 +11,10 @@ describe('MemoryGraph', () => {
[1573586433.853, '3.0066964285714284'],
];
- afterEach(() => {
- wrapper.destroy();
- });
+ const findGlSparklineChart = () => wrapper.findComponent(GlSparklineChart);
beforeEach(() => {
- wrapper = shallowMount(Component, {
+ wrapper = shallowMount(MemoryGraph, {
propsData: {
metrics,
width: 100,
@@ -27,19 +23,15 @@ describe('MemoryGraph', () => {
});
});
- describe('chartData', () => {
- it('should calculate chartData', () => {
- expect(wrapper.vm.chartData.length).toEqual(metrics.length);
- });
-
- it('should format date & MB values', () => {
+ describe('Chart data', () => {
+ it('should have formatted date & MB values', () => {
const formattedData = [
['Nov 12 2019 19:17:33', '2.87'],
['Nov 12 2019 19:18:33', '2.78'],
['Nov 12 2019 19:19:33', '2.78'],
['Nov 12 2019 19:20:33', '3.01'],
];
- expect(wrapper.vm.chartData).toEqual(formattedData);
+ expect(findGlSparklineChart().props('data')).toEqual(formattedData);
});
});
@@ -47,7 +39,7 @@ describe('MemoryGraph', () => {
it('should draw container with chart', () => {
expect(wrapper.element).toMatchSnapshot();
expect(wrapper.find('.memory-graph-container').exists()).toBe(true);
- expect(wrapper.findComponent(GlSparklineChart).exists()).toBe(true);
+ expect(findGlSparklineChart().exists()).toBe(true);
});
});
});
diff --git a/spec/frontend/vue_shared/components/metric_images/store/actions_spec.js b/spec/frontend/vue_shared/components/metric_images/store/actions_spec.js
index 537367940e0..626f6fc735e 100644
--- a/spec/frontend/vue_shared/components/metric_images/store/actions_spec.js
+++ b/spec/frontend/vue_shared/components/metric_images/store/actions_spec.js
@@ -4,11 +4,11 @@ import actionsFactory from '~/vue_shared/components/metric_images/store/actions'
import * as types from '~/vue_shared/components/metric_images/store/mutation_types';
import createStore from '~/vue_shared/components/metric_images/store';
import testAction from 'helpers/vuex_action_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { fileList, initialData } from '../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
const service = {
getMetricImages: jest.fn(),
uploadMetricImage: jest.fn(),
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 61e4e774420..a649b06c50e 100644
--- a/spec/frontend/vue_shared/components/modal_copy_button_spec.js
+++ b/spec/frontend/vue_shared/components/modal_copy_button_spec.js
@@ -6,10 +6,6 @@ import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
describe('modal copy button', () => {
let wrapper;
- afterEach(() => {
- wrapper.destroy();
- });
-
beforeEach(() => {
wrapper = shallowMount(ModalCopyButton, {
propsData: {
diff --git a/spec/frontend/vue_shared/components/navigation_tabs_spec.js b/spec/frontend/vue_shared/components/navigation_tabs_spec.js
index b1bec28bffb..947ee756259 100644
--- a/spec/frontend/vue_shared/components/navigation_tabs_spec.js
+++ b/spec/frontend/vue_shared/components/navigation_tabs_spec.js
@@ -38,11 +38,6 @@ describe('navigation tabs component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should render tabs', () => {
expect(wrapper.findAllComponents(GlTab)).toHaveLength(data.length);
});
diff --git a/spec/frontend/vue_shared/components/new_resource_dropdown/new_resource_dropdown_spec.js b/spec/frontend/vue_shared/components/new_resource_dropdown/new_resource_dropdown_spec.js
index 31320b1d2a6..a116233a065 100644
--- a/spec/frontend/vue_shared/components/new_resource_dropdown/new_resource_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/new_resource_dropdown/new_resource_dropdown_spec.js
@@ -21,7 +21,7 @@ import {
searchProjectsWithinGroupQueryResponse,
} from './mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('NewResourceDropdown component', () => {
useLocalStorageSpy();
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 17a62ae8a33..f87674246d1 100644
--- a/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js
+++ b/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js
@@ -132,12 +132,6 @@ describe('Issue Warning Component', () => {
});
});
- afterEach(() => {
- wrapperLocked.destroy();
- wrapperConfidential.destroy();
- wrapperLockedAndConfidential.destroy();
- });
-
it('renders confidential & locked messages with noteable "issue"', () => {
expect(findLockedBlock(wrapperLocked).text()).toContain('This issue is locked.');
expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
diff --git a/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js b/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js
index 8f9f1bb336f..7e669fb7c71 100644
--- a/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js
+++ b/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js
@@ -30,11 +30,6 @@ describe('Issue placeholder note component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('matches snapshot', () => {
createComponent();
diff --git a/spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js b/spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js
index de6ab43bc41..5897b9e0ffc 100644
--- a/spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js
+++ b/spec/frontend/vue_shared/components/notes/placeholder_system_note_spec.js
@@ -12,11 +12,6 @@ describe('Placeholder system note component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('matches snapshot', () => {
createComponent();
diff --git a/spec/frontend/vue_shared/components/notes/system_note_spec.js b/spec/frontend/vue_shared/components/notes/system_note_spec.js
index bcfd7a8ec70..29e1a9ccf4d 100644
--- a/spec/frontend/vue_shared/components/notes/system_note_spec.js
+++ b/spec/frontend/vue_shared/components/notes/system_note_spec.js
@@ -46,7 +46,6 @@ describe('system note component', () => {
});
afterEach(() => {
- vm.destroy();
mock.restore();
});
diff --git a/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js b/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js
index bd4b6a463ab..fa9d3cd28a9 100644
--- a/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js
+++ b/spec/frontend/vue_shared/components/notes/timeline_entry_item_spec.js
@@ -10,10 +10,6 @@ describe(`TimelineEntryItem`, () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders correctly', () => {
factory();
diff --git a/spec/frontend/vue_shared/components/ordered_layout_spec.js b/spec/frontend/vue_shared/components/ordered_layout_spec.js
index 21588569d6a..b6c8c467028 100644
--- a/spec/frontend/vue_shared/components/ordered_layout_spec.js
+++ b/spec/frontend/vue_shared/components/ordered_layout_spec.js
@@ -37,10 +37,6 @@ describe('Ordered Layout', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when slotKeys are in initial slot order', () => {
beforeEach(() => {
createComponent({ slotKeys: regularSlotOrder });
diff --git a/spec/frontend/vue_shared/components/page_size_selector_spec.js b/spec/frontend/vue_shared/components/page_size_selector_spec.js
index 5ec0b863afd..fce7ceee2fe 100644
--- a/spec/frontend/vue_shared/components/page_size_selector_spec.js
+++ b/spec/frontend/vue_shared/components/page_size_selector_spec.js
@@ -14,10 +14,6 @@ describe('Page size selector component', () => {
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each(PAGE_SIZES)('shows expected text in the dropdown button for page size %s', (pageSize) => {
createWrapper({ pageSize });
diff --git a/spec/frontend/vue_shared/components/paginated_list_spec.js b/spec/frontend/vue_shared/components/paginated_list_spec.js
index ae9c920ebd2..fc9adab2e2b 100644
--- a/spec/frontend/vue_shared/components/paginated_list_spec.js
+++ b/spec/frontend/vue_shared/components/paginated_list_spec.js
@@ -33,10 +33,6 @@ describe('Pagination links component', () => {
[glPaginatedList] = wrapper.vm.$children;
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Paginated List Component', () => {
describe('props', () => {
// We test attrs and not props because we pass through to child component using v-bind:"$attrs"
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 86a63db0d9e..25bfa688e5b 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
@@ -108,16 +108,23 @@ describe('AlertManagementEmptyState', () => {
const findStatusTabs = () => wrapper.findComponent(GlTabs);
const findStatusFilterBadge = () => wrapper.findAllComponents(GlBadge);
+ const handleFilterItems = (filters) => {
+ Filters().vm.$emit('onFilter', filters);
+ return nextTick();
+ };
+
describe('Snowplow tracking', () => {
+ const category = 'category';
+ const action = 'action';
+
beforeEach(() => {
jest.spyOn(Tracking, 'event');
mountComponent({
- props: { trackViewsOptions: { category: 'category', action: 'action' } },
+ props: { trackViewsOptions: { category, action } },
});
});
it('should track the items list page views', () => {
- const { category, action } = wrapper.vm.trackViewsOptions;
expect(Tracking.event).toHaveBeenCalledWith(category, action);
});
});
@@ -234,14 +241,14 @@ describe('AlertManagementEmptyState', () => {
findPagination().vm.$emit('input', 3);
await nextTick();
- expect(wrapper.vm.previousPage).toBe(2);
+ expect(findPagination().props('prevPage')).toBe(2);
});
it('returns 0 when it is the first page', async () => {
findPagination().vm.$emit('input', 1);
await nextTick();
- expect(wrapper.vm.previousPage).toBe(0);
+ expect(findPagination().props('prevPage')).toBe(0);
});
});
@@ -265,14 +272,14 @@ describe('AlertManagementEmptyState', () => {
findPagination().vm.$emit('input', 1);
await nextTick();
- expect(wrapper.vm.nextPage).toBe(2);
+ expect(findPagination().props('nextPage')).toBe(2);
});
it('returns `null` when currentPage is already last page', async () => {
findStatusTabs().vm.$emit('input', 1);
findPagination().vm.$emit('input', 1);
await nextTick();
- expect(wrapper.vm.nextPage).toBeNull();
+ expect(findPagination().props('nextPage')).toBeNull();
});
});
});
@@ -320,36 +327,32 @@ describe('AlertManagementEmptyState', () => {
it('returns correctly applied filter search values', async () => {
const searchTerm = 'foo';
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- searchTerm,
- });
-
+ await handleFilterItems([{ type: 'filtered-search-term', value: { data: searchTerm } }]);
await nextTick();
- expect(wrapper.vm.filteredSearchValue).toEqual([searchTerm]);
+ expect(Filters().props('initialFilterValue')).toEqual([searchTerm]);
});
- it('updates props tied to getIncidents GraphQL query', () => {
- wrapper.vm.handleFilterItems(mockFilters);
-
- expect(wrapper.vm.authorUsername).toBe('root');
- expect(wrapper.vm.assigneeUsername).toEqual('root2');
- expect(wrapper.vm.searchTerm).toBe(mockFilters[2].value.data);
- });
+ it('updates props tied to getIncidents GraphQL query', async () => {
+ await handleFilterItems(mockFilters);
- it('updates props `searchTerm` and `authorUsername` with empty values when passed filters param is empty', () => {
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- authorUsername: 'foo',
- searchTerm: 'bar',
- });
+ const [
+ {
+ value: { data: authorUsername },
+ },
+ {
+ value: { data: assigneeUsername },
+ },
+ searchTerm,
+ ] = Filters().props('initialFilterValue');
- wrapper.vm.handleFilterItems([]);
+ expect(authorUsername).toBe('root');
+ expect(assigneeUsername).toEqual('root2');
+ expect(searchTerm).toBe(mockFilters[2].value.data);
+ });
- expect(wrapper.vm.authorUsername).toBe('');
- expect(wrapper.vm.searchTerm).toBe('');
+ it('updates props `searchTerm` and `authorUsername` with empty values when passed filters param is empty', async () => {
+ await handleFilterItems([]);
+ expect(Filters().props('initialFilterValue')).toEqual([]);
});
});
});
diff --git a/spec/frontend/vue_shared/components/pagination_bar/pagination_bar_spec.js b/spec/frontend/vue_shared/components/pagination_bar/pagination_bar_spec.js
index 112cdaf74c6..2a1a6342c38 100644
--- a/spec/frontend/vue_shared/components/pagination_bar/pagination_bar_spec.js
+++ b/spec/frontend/vue_shared/components/pagination_bar/pagination_bar_spec.js
@@ -25,10 +25,6 @@ describe('Pagination bar', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('events', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/vue_shared/components/pagination_links_spec.js b/spec/frontend/vue_shared/components/pagination_links_spec.js
index d444ad7a733..99a4f776305 100644
--- a/spec/frontend/vue_shared/components/pagination_links_spec.js
+++ b/spec/frontend/vue_shared/components/pagination_links_spec.js
@@ -44,10 +44,6 @@ describe('Pagination links component', () => {
glPagination = wrapper.findComponent(GlPagination);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should provide translated text to GitLab UI pagination', () => {
Object.entries(translations).forEach((entry) => {
expect(glPagination.vm[entry[0]]).toBe(entry[1]);
diff --git a/spec/frontend/vue_shared/components/panel_resizer_spec.js b/spec/frontend/vue_shared/components/panel_resizer_spec.js
index 0e261124cbf..a535fe4939c 100644
--- a/spec/frontend/vue_shared/components/panel_resizer_spec.js
+++ b/spec/frontend/vue_shared/components/panel_resizer_spec.js
@@ -27,10 +27,6 @@ describe('Panel Resizer component', () => {
el.dispatchEvent(event);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render a div element with the correct classes and styles', () => {
wrapper = mount(PanelResizer, {
propsData: {
diff --git a/spec/frontend/vue_shared/components/papa_parse_alert_spec.js b/spec/frontend/vue_shared/components/papa_parse_alert_spec.js
index ff4febd647e..a44a1aba8c0 100644
--- a/spec/frontend/vue_shared/components/papa_parse_alert_spec.js
+++ b/spec/frontend/vue_shared/components/papa_parse_alert_spec.js
@@ -16,10 +16,6 @@ describe('app/assets/javascripts/vue_shared/components/papa_parse_alert.vue', ()
const findAlert = () => wrapper.findComponent(GlAlert);
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render alert with correct props', async () => {
createComponent({ errorMessages: [{ code: 'MissingQuotes' }] });
await nextTick();
diff --git a/spec/frontend/vue_shared/components/project_avatar_spec.js b/spec/frontend/vue_shared/components/project_avatar_spec.js
index af828fbca51..9378f6e3f1b 100644
--- a/spec/frontend/vue_shared/components/project_avatar_spec.js
+++ b/spec/frontend/vue_shared/components/project_avatar_spec.js
@@ -15,10 +15,6 @@ describe('ProjectAvatar', () => {
wrapper = shallowMount(ProjectAvatar, { propsData: { ...defaultProps, ...props }, attrs });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders GlAvatar with correct props', () => {
createComponent();
diff --git a/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js b/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js
index 4e0c318c84e..d704fcc0e7b 100644
--- a/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js
+++ b/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js
@@ -1,57 +1,49 @@
-import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
+import { GlButton } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import mockProjects from 'test_fixtures_static/projects.json';
import { trimText } from 'helpers/text_helper';
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue';
describe('ProjectListItem component', () => {
- const Component = Vue.extend(ProjectListItem);
let wrapper;
- let vm;
- let options;
const project = JSON.parse(JSON.stringify(mockProjects))[0];
- beforeEach(() => {
- options = {
+ const createWrapper = ({ propsData } = {}) => {
+ wrapper = shallowMountExtended(ProjectListItem, {
propsData: {
project,
selected: false,
+ ...propsData,
},
- };
- });
-
- afterEach(() => {
- wrapper.vm.$destroy();
- });
-
- it('does not render a check mark icon if selected === false', () => {
- wrapper = shallowMount(Component, options);
-
- expect(wrapper.find('.js-selected-icon').exists()).toBe(false);
- });
+ });
+ };
- it('renders a check mark icon if selected === true', () => {
- options.propsData.selected = true;
+ const findProjectNamespace = () => wrapper.findByTestId('project-namespace');
+ const findProjectName = () => wrapper.findByTestId('project-name');
- wrapper = shallowMount(Component, options);
+ it.each([true, false])('renders a checkmark correctly when selected === "%s"', (selected) => {
+ createWrapper({
+ propsData: {
+ selected,
+ },
+ });
- expect(wrapper.find('.js-selected-icon').exists()).toBe(true);
+ expect(wrapper.findByTestId('selected-icon').exists()).toBe(selected);
});
- it(`emits a "clicked" event when clicked`, () => {
- wrapper = shallowMount(Component, options);
- ({ vm } = wrapper);
+ it(`emits a "clicked" event when the button is clicked`, () => {
+ createWrapper();
- jest.spyOn(vm, '$emit').mockImplementation(() => {});
- wrapper.vm.onClick();
+ expect(wrapper.emitted('click')).toBeUndefined();
+ wrapper.findComponent(GlButton).vm.$emit('click');
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('click');
+ expect(wrapper.emitted('click')).toHaveLength(1);
});
it(`renders the project avatar`, () => {
- wrapper = shallowMount(Component, options);
+ createWrapper();
const avatar = wrapper.findComponent(ProjectAvatar);
expect(avatar.exists()).toBe(true);
@@ -63,48 +55,73 @@ describe('ProjectListItem component', () => {
});
it(`renders a simple namespace name with a trailing slash`, () => {
- options.propsData.project.name_with_namespace = 'a / b';
-
- wrapper = shallowMount(Component, options);
- const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text());
+ createWrapper({
+ propsData: {
+ project: {
+ ...project,
+ name_with_namespace: 'a / b',
+ },
+ },
+ });
+ const renderedNamespace = trimText(findProjectNamespace().text());
expect(renderedNamespace).toBe('a /');
});
it(`renders a properly truncated namespace with a trailing slash`, () => {
- options.propsData.project.name_with_namespace = 'a / b / c / d / e / f';
-
- wrapper = shallowMount(Component, options);
- const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text());
+ createWrapper({
+ propsData: {
+ project: {
+ ...project,
+ name_with_namespace: 'a / b / c / d / e / f',
+ },
+ },
+ });
+ const renderedNamespace = trimText(findProjectNamespace().text());
expect(renderedNamespace).toBe('a / ... / e /');
});
it(`renders a simple namespace name of a GraphQL project`, () => {
- options.propsData.project.name_with_namespace = undefined;
- options.propsData.project.nameWithNamespace = 'test';
-
- wrapper = shallowMount(Component, options);
- const renderedNamespace = trimText(wrapper.find('.js-project-namespace').text());
+ createWrapper({
+ propsData: {
+ project: {
+ ...project,
+ name_with_namespace: undefined,
+ nameWithNamespace: 'test',
+ },
+ },
+ });
+ const renderedNamespace = trimText(findProjectNamespace().text());
expect(renderedNamespace).toBe('test /');
});
it(`renders the project name`, () => {
- options.propsData.project.name = 'my-test-project';
-
- wrapper = shallowMount(Component, options);
- const renderedName = trimText(wrapper.find('.js-project-name').text());
+ createWrapper({
+ propsData: {
+ project: {
+ ...project,
+ name: 'my-test-project',
+ },
+ },
+ });
+ const renderedName = trimText(findProjectName().text());
expect(renderedName).toBe('my-test-project');
});
it(`renders the project name with highlighting in the case of a search query match`, () => {
- options.propsData.project.name = 'my-test-project';
- options.propsData.matcher = 'pro';
-
- wrapper = shallowMount(Component, options);
- const renderedName = trimText(wrapper.find('.js-project-name').html());
+ createWrapper({
+ propsData: {
+ project: {
+ ...project,
+ name: 'my-test-project',
+ },
+ matcher: 'pro',
+ },
+ });
+ const renderedName = trimText(findProjectName().html());
const expected = 'my-test-<b>p</b><b>r</b><b>o</b>ject';
expect(renderedName).toContain(expected);
@@ -112,11 +129,16 @@ describe('ProjectListItem component', () => {
it('prevents search query and project name XSS', () => {
const alertSpy = jest.spyOn(window, 'alert');
- options.propsData.project.name = "my-xss-pro<script>alert('XSS');</script>ject";
- options.propsData.matcher = "pro<script>alert('XSS');</script>";
-
- wrapper = shallowMount(Component, options);
- const renderedName = trimText(wrapper.find('.js-project-name').html());
+ createWrapper({
+ propsData: {
+ project: {
+ ...project,
+ name: "my-xss-pro<script>alert('XSS');</script>ject",
+ },
+ matcher: "pro<script>alert('XSS');</script>",
+ },
+ });
+ const renderedName = trimText(findProjectName().html());
const expected = 'my-xss-project';
expect(renderedName).toContain(expected);
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 a0832dd7030..5e304f1c118 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, { nextTick } from 'vue';
+import { 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';
@@ -25,7 +25,7 @@ describe('ProjectSelector component', () => {
};
beforeEach(() => {
- wrapper = mount(Vue.extend(ProjectSelector), {
+ wrapper = mount(ProjectSelector, {
propsData: {
projectSearchResults: searchResults,
selectedProjects: selected,
diff --git a/spec/frontend/vue_shared/components/registry/code_instruction_spec.js b/spec/frontend/vue_shared/components/registry/code_instruction_spec.js
index 8f19f0ea14d..60c1293b7c1 100644
--- a/spec/frontend/vue_shared/components/registry/code_instruction_spec.js
+++ b/spec/frontend/vue_shared/components/registry/code_instruction_spec.js
@@ -26,10 +26,6 @@ describe('Package code instruction', () => {
const findInputElement = () => wrapper.find('[data-testid="instruction-input"]');
const findMultilineInstruction = () => wrapper.find('[data-testid="multiline-instruction"]');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('single line', () => {
beforeEach(() =>
createComponent({
diff --git a/spec/frontend/vue_shared/components/registry/details_row_spec.js b/spec/frontend/vue_shared/components/registry/details_row_spec.js
index ebc9816f983..9ef1ce5647d 100644
--- a/spec/frontend/vue_shared/components/registry/details_row_spec.js
+++ b/spec/frontend/vue_shared/components/registry/details_row_spec.js
@@ -20,11 +20,6 @@ describe('DetailsRow', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('has a default slot', () => {
mountComponent();
expect(findDefaultSlot().exists()).toBe(true);
diff --git a/spec/frontend/vue_shared/components/registry/history_item_spec.js b/spec/frontend/vue_shared/components/registry/history_item_spec.js
index 947520567e6..17abe06dbee 100644
--- a/spec/frontend/vue_shared/components/registry/history_item_spec.js
+++ b/spec/frontend/vue_shared/components/registry/history_item_spec.js
@@ -22,11 +22,6 @@ describe('History Item', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findTimelineEntry = () => wrapper.findComponent(TimelineEntryItem);
const findGlIcon = () => wrapper.findComponent(GlIcon);
const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]');
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 b941eb77c32..298fa163d59 100644
--- a/spec/frontend/vue_shared/components/registry/list_item_spec.js
+++ b/spec/frontend/vue_shared/components/registry/list_item_spec.js
@@ -30,11 +30,6 @@ describe('list item', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe.each`
slotName | finderFunction
${'left-primary'} | ${findLeftPrimarySlot}
diff --git a/spec/frontend/vue_shared/components/registry/metadata_item_spec.js b/spec/frontend/vue_shared/components/registry/metadata_item_spec.js
index a04e1e237d4..278b09d80b2 100644
--- a/spec/frontend/vue_shared/components/registry/metadata_item_spec.js
+++ b/spec/frontend/vue_shared/components/registry/metadata_item_spec.js
@@ -14,16 +14,11 @@ describe('Metadata Item', () => {
wrapper = shallowMount(component, {
propsData,
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const findIcon = () => wrapper.findComponent(GlIcon);
const findLink = (w = wrapper) => w.findComponent(GlLink);
const findText = () => wrapper.find('[data-testid="metadata-item-text"]');
diff --git a/spec/frontend/vue_shared/components/registry/persisted_dropdown_selection_spec.js b/spec/frontend/vue_shared/components/registry/persisted_dropdown_selection_spec.js
index 616fefe847e..b93fa37546f 100644
--- a/spec/frontend/vue_shared/components/registry/persisted_dropdown_selection_spec.js
+++ b/spec/frontend/vue_shared/components/registry/persisted_dropdown_selection_spec.js
@@ -31,10 +31,6 @@ describe('Persisted dropdown selection', () => {
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('local storage sync', () => {
it('uses the local storage sync component with the correct props', () => {
createComponent();
diff --git a/spec/frontend/vue_shared/components/registry/registry_search_spec.js b/spec/frontend/vue_shared/components/registry/registry_search_spec.js
index 591447a37c2..59bb0646350 100644
--- a/spec/frontend/vue_shared/components/registry/registry_search_spec.js
+++ b/spec/frontend/vue_shared/components/registry/registry_search_spec.js
@@ -36,11 +36,6 @@ describe('Registry Search', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('searching', () => {
it('has a filtered-search component', () => {
mountComponent();
diff --git a/spec/frontend/vue_shared/components/registry/title_area_spec.js b/spec/frontend/vue_shared/components/registry/title_area_spec.js
index efb57ddd310..ec1451de470 100644
--- a/spec/frontend/vue_shared/components/registry/title_area_spec.js
+++ b/spec/frontend/vue_shared/components/registry/title_area_spec.js
@@ -36,11 +36,6 @@ describe('title area', () => {
return acc;
}, {});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('title', () => {
it('if slot is not present defaults to prop', () => {
mountComponent();
diff --git a/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap
deleted file mode 100644
index cdfe311acd9..00000000000
--- a/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap
+++ /dev/null
@@ -1,23 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Resizable Chart Container renders the component 1`] = `
-<div>
- <template>
- <div
- class="slot"
- >
- <span
- class="width"
- >
- 0
- </span>
-
- <span
- class="height"
- >
- 0
- </span>
- </div>
- </template>
-</div>
-`;
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
deleted file mode 100644
index 7536df24ac6..00000000000
--- a/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import { mount } from '@vue/test-utils';
-import $ from 'jquery';
-import { nextTick } from 'vue';
-import ResizableChartContainer from '~/vue_shared/components/resizable_chart/resizable_chart_container.vue';
-
-jest.mock('~/lib/utils/common_utils', () => ({
- debounceByAnimationFrame(callback) {
- return jest.spyOn({ callback }, 'callback');
- },
-}));
-
-describe('Resizable Chart Container', () => {
- let wrapper;
-
- beforeEach(() => {
- wrapper = mount(ResizableChartContainer, {
- scopedSlots: {
- default: `
- <template #default="{ width, height }">
- <div class="slot">
- <span class="width">{{width}}</span>
- <span class="height">{{height}}</span>
- </div>
- </template>
- `,
- },
- });
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders the component', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('updates the slot width and height props', async () => {
- const width = 1920;
- const height = 1080;
-
- // JSDOM mocks and sets clientWidth/clientHeight to 0 so we set manually
- wrapper.vm.$refs.chartWrapper = { clientWidth: width, clientHeight: height };
-
- $(document).trigger('content.resize');
-
- 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);
- });
-
- it('calls onResize on manual resize', () => {
- $(document).trigger('content.resize');
- expect(wrapper.vm.debouncedResize).toHaveBeenCalled();
- });
-
- it('calls onResize on page resize', () => {
- window.dispatchEvent(new Event('resize'));
- expect(wrapper.vm.debouncedResize).toHaveBeenCalled();
- });
-});
diff --git a/spec/frontend/vue_shared/components/rich_timestamp_tooltip_spec.js b/spec/frontend/vue_shared/components/rich_timestamp_tooltip_spec.js
index 5d96fe27676..11ee2e56c14 100644
--- a/spec/frontend/vue_shared/components/rich_timestamp_tooltip_spec.js
+++ b/spec/frontend/vue_shared/components/rich_timestamp_tooltip_spec.js
@@ -27,10 +27,6 @@ describe('RichTimestampTooltip', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders the tooltip text header', () => {
expect(wrapper.findByTestId('header-text').text()).toBe('Created just now');
});
diff --git a/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_cli_instructions_spec.js b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_cli_instructions_spec.js
index f9d700fe67f..c4d4f80c573 100644
--- a/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_cli_instructions_spec.js
+++ b/spec/frontend/vue_shared/components/runner_instructions/instructions/runner_cli_instructions_spec.js
@@ -59,10 +59,6 @@ describe('RunnerCliInstructions component', () => {
runnerSetupInstructionsHandler = jest.fn().mockResolvedValue(mockInstructions);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when the instructions are shown', () => {
beforeEach(async () => {
createComponent();
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 8f593b6aa1b..cb35cbd35ad 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
@@ -74,10 +74,6 @@ describe('RunnerInstructionsModal component', () => {
runnerPlatformsHandler = jest.fn().mockResolvedValue(mockRunnerPlatforms);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when the modal is shown', () => {
beforeEach(async () => {
createComponent();
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 986d76d2b95..260eddbb37d 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
@@ -12,7 +12,7 @@ describe('RunnerInstructions component', () => {
const createComponent = () => {
wrapper = shallowMountExtended(RunnerInstructions, {
directives: {
- GlModal: createMockDirective(),
+ GlModal: createMockDirective('gl-tooltip'),
},
});
};
@@ -21,10 +21,6 @@ describe('RunnerInstructions component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should show the "Show runner installation instructions" button', () => {
expect(findModalButton().text()).toBe('Show runner installation instructions');
});
diff --git a/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js b/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js
index 09b0b3d43ad..6eebd129beb 100644
--- a/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js
+++ b/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js
@@ -6,7 +6,7 @@ import {
expectedDownloadDropdownPropsWithTitle,
securityReportMergeRequestDownloadPathsQueryResponse,
} from 'jest/vue_shared/security_reports/mock_data';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import Component from '~/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue';
import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
import {
@@ -15,7 +15,7 @@ import {
} from '~/vue_shared/security_reports/constants';
import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Merge request artifact Download', () => {
let wrapper;
@@ -52,10 +52,6 @@ describe('Merge request artifact Download', () => {
const findDownloadDropdown = () => wrapper.findComponent(SecurityReportDownloadDropdown);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('given the query is loading', () => {
beforeEach(() => {
createWrapper({
diff --git a/spec/frontend/vue_shared/components/security_reports/help_icon_spec.js b/spec/frontend/vue_shared/components/security_reports/help_icon_spec.js
index 08d3d5b19d4..2f6e633fb34 100644
--- a/spec/frontend/vue_shared/components/security_reports/help_icon_spec.js
+++ b/spec/frontend/vue_shared/components/security_reports/help_icon_spec.js
@@ -21,11 +21,6 @@ describe('HelpIcon component', () => {
const findPopover = () => wrapper.findComponent(GlPopover);
const findPopoverTarget = () => wrapper.findComponent({ ref: 'discoverProjectSecurity' });
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('given a help path only', () => {
beforeEach(() => {
createWrapper();
diff --git a/spec/frontend/vue_shared/components/security_reports/security_summary_spec.js b/spec/frontend/vue_shared/components/security_reports/security_summary_spec.js
index f186eb848f2..61cdc329220 100644
--- a/spec/frontend/vue_shared/components/security_reports/security_summary_spec.js
+++ b/spec/frontend/vue_shared/components/security_reports/security_summary_spec.js
@@ -15,11 +15,6 @@ describe('SecuritySummary component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe.each([
{ message: '' },
{ message: 'foo' },
diff --git a/spec/frontend/vue_shared/components/segmented_control_button_group_spec.js b/spec/frontend/vue_shared/components/segmented_control_button_group_spec.js
index 88445b6684c..c1feb64dacb 100644
--- a/spec/frontend/vue_shared/components/segmented_control_button_group_spec.js
+++ b/spec/frontend/vue_shared/components/segmented_control_button_group_spec.js
@@ -40,10 +40,6 @@ describe('~/vue_shared/components/segmented_control_button_group.vue', () => {
disabled,
}));
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('default', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/vue_shared/components/settings/settings_block_spec.js b/spec/frontend/vue_shared/components/settings/settings_block_spec.js
index 5e829653c13..94d634f79bd 100644
--- a/spec/frontend/vue_shared/components/settings/settings_block_spec.js
+++ b/spec/frontend/vue_shared/components/settings/settings_block_spec.js
@@ -16,10 +16,6 @@ describe('Settings Block', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findDefaultSlot = () => wrapper.findByTestId('default-slot');
const findTitleSlot = () => wrapper.findByTestId('title-slot');
const findDescriptionSlot = () => wrapper.findByTestId('description-slot');
diff --git a/spec/frontend/vue_shared/components/smart_virtual_list_spec.js b/spec/frontend/vue_shared/components/smart_virtual_list_spec.js
index 8802a832781..e5d988f75f5 100644
--- a/spec/frontend/vue_shared/components/smart_virtual_list_spec.js
+++ b/spec/frontend/vue_shared/components/smart_virtual_list_spec.js
@@ -1,5 +1,4 @@
import { mount } from '@vue/test-utils';
-import Vue from 'vue';
import SmartVirtualScrollList from '~/vue_shared/components/smart_virtual_list.vue';
describe('Toggle Button', () => {
@@ -16,7 +15,7 @@ describe('Toggle Button', () => {
remain,
};
- const Component = Vue.extend({
+ const Component = {
components: {
SmartVirtualScrollList,
},
@@ -26,7 +25,7 @@ describe('Toggle Button', () => {
<smart-virtual-scroll-list v-bind="$options.smartListProperties">
<li v-for="(val, key) in $options.items" :key="key">{{ key + 1 }}</li>
</smart-virtual-scroll-list>`,
- });
+ };
return mount(Component).vm;
};
diff --git a/spec/frontend/vue_shared/components/source_editor_spec.js b/spec/frontend/vue_shared/components/source_editor_spec.js
index ca5b990bc29..5b155207029 100644
--- a/spec/frontend/vue_shared/components/source_editor_spec.js
+++ b/spec/frontend/vue_shared/components/source_editor_spec.js
@@ -47,10 +47,6 @@ describe('Source Editor component', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const triggerChangeContent = (val) => {
mockInstance.getValue.mockReturnValue(val);
const [cb] = mockInstance.onDidChangeModelContent.mock.calls[0];
diff --git a/spec/frontend/vue_shared/components/source_viewer/components/chunk_deprecated_spec.js b/spec/frontend/vue_shared/components/source_viewer/components/chunk_deprecated_spec.js
index da9067a8ddc..395ba92d4c6 100644
--- a/spec/frontend/vue_shared/components/source_viewer/components/chunk_deprecated_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/components/chunk_deprecated_spec.js
@@ -45,8 +45,6 @@ describe('Chunk component', () => {
createComponent();
});
- afterEach(() => wrapper.destroy());
-
describe('Intersection observer', () => {
it('renders an Intersection observer component', () => {
expect(findIntersectionObserver().exists()).toBe(true);
diff --git a/spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js b/spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js
index f661bd6747a..6c8fc244fa0 100644
--- a/spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/components/chunk_line_spec.js
@@ -31,8 +31,6 @@ describe('Chunk Line component', () => {
createComponent();
});
- afterEach(() => wrapper.destroy());
-
describe('rendering', () => {
it('renders a blame link', () => {
expect(findBlameLink().attributes()).toMatchObject({
diff --git a/spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js b/spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js
index 95ef11d776a..59880496d74 100644
--- a/spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/components/chunk_spec.js
@@ -24,8 +24,6 @@ describe('Chunk component', () => {
createComponent();
});
- afterEach(() => wrapper.destroy());
-
describe('Intersection observer', () => {
it('renders an Intersection observer component', () => {
expect(findIntersectionObserver().exists()).toBe(true);
diff --git a/spec/frontend/vue_shared/components/source_viewer/source_viewer_deprecated_spec.js b/spec/frontend/vue_shared/components/source_viewer/source_viewer_deprecated_spec.js
index 0beec8e9d3e..c911e3d308b 100644
--- a/spec/frontend/vue_shared/components/source_viewer/source_viewer_deprecated_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/source_viewer_deprecated_spec.js
@@ -68,8 +68,6 @@ describe('Source Viewer component', () => {
return createComponent();
});
- afterEach(() => wrapper.destroy());
-
describe('event tracking', () => {
it('fires a tracking event when the component is created', () => {
const eventData = { label: EVENT_LABEL_VIEWER, property: language };
diff --git a/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
index 1c75442b4a8..46b582c3668 100644
--- a/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/source_viewer/source_viewer_spec.js
@@ -25,8 +25,6 @@ describe('Source Viewer component', () => {
return createComponent();
});
- afterEach(() => wrapper.destroy());
-
describe('event tracking', () => {
it('fires a tracking event when the component is created', () => {
const eventData = { label: EVENT_LABEL_VIEWER, property: LANGUAGE_MOCK };
diff --git a/spec/frontend/vue_shared/components/stacked_progress_bar_spec.js b/spec/frontend/vue_shared/components/stacked_progress_bar_spec.js
index 79b1f17afa0..13911d487f2 100644
--- a/spec/frontend/vue_shared/components/stacked_progress_bar_spec.js
+++ b/spec/frontend/vue_shared/components/stacked_progress_bar_spec.js
@@ -18,10 +18,6 @@ describe('StackedProgressBarComponent', () => {
wrapper = mount(StackedProgressBarComponent, { propsData: defaultConfig });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findSuccessBar = () => wrapper.find('.status-green');
const findNeutralBar = () => wrapper.find('.status-neutral');
const findFailureBar = () => wrapper.find('.status-red');
diff --git a/spec/frontend/vue_shared/components/table_pagination_spec.js b/spec/frontend/vue_shared/components/table_pagination_spec.js
index 99de26ce2ae..79aba1b2516 100644
--- a/spec/frontend/vue_shared/components/table_pagination_spec.js
+++ b/spec/frontend/vue_shared/components/table_pagination_spec.js
@@ -16,10 +16,6 @@ describe('Pagination component', () => {
spy = jest.fn();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('render', () => {
it('should not render anything', () => {
mountComponent({
diff --git a/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js
index 28c5acc8110..a1757952dc0 100644
--- a/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js
+++ b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js
@@ -25,7 +25,6 @@ describe('Time ago with tooltip component', () => {
};
afterEach(() => {
- vm.destroy();
timezoneMock.unregister();
});
diff --git a/spec/frontend/vue_shared/components/timezone_dropdown/timezone_dropdown_spec.js b/spec/frontend/vue_shared/components/timezone_dropdown/timezone_dropdown_spec.js
index c8351ed61d7..d8dedd8240b 100644
--- a/spec/frontend/vue_shared/components/timezone_dropdown/timezone_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/timezone_dropdown/timezone_dropdown_spec.js
@@ -1,4 +1,4 @@
-import { GlDropdownItem, GlDropdown } from '@gitlab/ui';
+import { GlDropdownItem, GlDropdown, GlSearchBoxByType } from '@gitlab/ui';
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown/timezone_dropdown.vue';
@@ -9,7 +9,9 @@ describe('Deploy freeze timezone dropdown', () => {
let wrapper;
let store;
- const createComponent = (searchTerm, selectedTimezone) => {
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+
+ const createComponent = async (searchTerm, selectedTimezone) => {
wrapper = shallowMountExtended(TimezoneDropdown, {
store,
propsData: {
@@ -19,9 +21,8 @@ describe('Deploy freeze timezone dropdown', () => {
},
});
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({ searchTerm });
+ findSearchBox().vm.$emit('input', searchTerm);
+ await nextTick();
};
const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
@@ -29,14 +30,9 @@ describe('Deploy freeze timezone dropdown', () => {
const findEmptyResultsItem = () => wrapper.findByTestId('noMatchingResults');
const findHiddenInput = () => wrapper.find('input');
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('No time zones found', () => {
- beforeEach(() => {
- createComponent('UTC timezone');
+ beforeEach(async () => {
+ await createComponent('UTC timezone');
});
it('renders empty results message', () => {
@@ -45,8 +41,8 @@ describe('Deploy freeze timezone dropdown', () => {
});
describe('Search term is empty', () => {
- beforeEach(() => {
- createComponent('');
+ beforeEach(async () => {
+ await createComponent('');
});
it('renders all timezones when search term is empty', () => {
@@ -55,8 +51,8 @@ describe('Deploy freeze timezone dropdown', () => {
});
describe('Time zones found', () => {
- beforeEach(() => {
- createComponent('Alaska');
+ beforeEach(async () => {
+ await createComponent('Alaska');
});
it('renders only the time zone searched for', () => {
@@ -87,8 +83,8 @@ describe('Deploy freeze timezone dropdown', () => {
});
describe('Selected time zone not found', () => {
- beforeEach(() => {
- createComponent('', 'Berlin');
+ beforeEach(async () => {
+ await createComponent('', 'Berlin');
});
it('renders empty selections', () => {
@@ -101,8 +97,8 @@ describe('Deploy freeze timezone dropdown', () => {
});
describe('Selected time zone found', () => {
- beforeEach(() => {
- createComponent('', 'Europe/Berlin');
+ beforeEach(async () => {
+ await createComponent('', 'Europe/Berlin');
});
it('renders selected time zone as dropdown label', () => {
diff --git a/spec/frontend/vue_shared/components/tooltip_on_truncate_spec.js b/spec/frontend/vue_shared/components/tooltip_on_truncate_spec.js
index ca1f7996ad6..3807bb4cc63 100644
--- a/spec/frontend/vue_shared/components/tooltip_on_truncate_spec.js
+++ b/spec/frontend/vue_shared/components/tooltip_on_truncate_spec.js
@@ -30,8 +30,8 @@ describe('TooltipOnTruncate component', () => {
default: [MOCK_TITLE],
},
directives: {
- GlTooltip: createMockDirective(),
- GlResizeObserver: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
+ GlResizeObserver: createMockDirective('gl-resize-observer'),
},
...options,
});
@@ -42,8 +42,8 @@ describe('TooltipOnTruncate component', () => {
...TooltipOnTruncate,
directives: {
...TooltipOnTruncate.directives,
- GlTooltip: createMockDirective(),
- GlResizeObserver: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
+ GlResizeObserver: createMockDirective('gl-resize-observer'),
},
};
@@ -78,10 +78,6 @@ describe('TooltipOnTruncate component', () => {
await nextTick();
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when truncated', () => {
beforeEach(async () => {
hasHorizontalOverflow.mockReturnValueOnce(true);
@@ -90,7 +86,7 @@ describe('TooltipOnTruncate component', () => {
it('renders tooltip', async () => {
expect(hasHorizontalOverflow).toHaveBeenLastCalledWith(wrapper.element);
- expect(getTooltipValue()).toMatchObject({
+ expect(getTooltipValue()).toStrictEqual({
title: MOCK_TITLE,
placement: 'top',
disabled: false,
@@ -144,7 +140,7 @@ describe('TooltipOnTruncate component', () => {
await nextTick();
- expect(getTooltipValue()).toMatchObject({
+ expect(getTooltipValue()).toStrictEqual({
title: MOCK_TITLE,
placement: 'top',
disabled: false,
@@ -194,20 +190,22 @@ describe('TooltipOnTruncate component', () => {
});
});
- describe('placement', () => {
- it('sets placement when tooltip is rendered', () => {
- const mockPlacement = 'bottom';
-
+ describe('tooltip customization', () => {
+ it.each`
+ property | mockValue
+ ${'placement'} | ${'bottom'}
+ ${'boundary'} | ${'viewport'}
+ `('sets $property when the tooltip is rendered', ({ property, mockValue }) => {
hasHorizontalOverflow.mockReturnValueOnce(true);
createComponent({
propsData: {
- placement: mockPlacement,
+ [property]: mockValue,
},
});
expect(hasHorizontalOverflow).toHaveBeenLastCalledWith(wrapper.element);
expect(getTooltipValue()).toMatchObject({
- placement: mockPlacement,
+ [property]: mockValue,
});
});
});
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 f9d615d4f68..c816fe790a8 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
@@ -5,7 +5,7 @@ exports[`Upload dropzone component correctly overrides description and drop mess
class="gl-w-full gl-relative"
>
<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-4 gl-mb-0"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-px-5 gl-py-4 gl-mb-0"
type="button"
>
<div
@@ -86,7 +86,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
class="gl-w-full gl-relative"
>
<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-4 gl-mb-0"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-px-5 gl-py-4 gl-mb-0"
type="button"
>
<div
@@ -171,7 +171,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
class="gl-w-full gl-relative"
>
<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-4 gl-mb-0"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-px-5 gl-py-4 gl-mb-0"
type="button"
>
<div
@@ -256,7 +256,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
class="gl-w-full gl-relative"
>
<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-4 gl-mb-0"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-px-5 gl-py-4 gl-mb-0"
type="button"
>
<div
@@ -342,7 +342,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
class="gl-w-full gl-relative"
>
<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-4 gl-mb-0"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-px-5 gl-py-4 gl-mb-0"
type="button"
>
<div
@@ -428,7 +428,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
class="gl-w-full gl-relative"
>
<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-4 gl-mb-0"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-px-5 gl-py-4 gl-mb-0"
type="button"
>
<div
@@ -514,7 +514,7 @@ exports[`Upload dropzone component when no slot provided renders default dropzon
class="gl-w-full gl-relative"
>
<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-4 gl-mb-0"
+ class="card upload-dropzone-card upload-dropzone-border gl-w-full gl-h-full gl-align-items-center gl-justify-content-center gl-px-5 gl-py-4 gl-mb-0"
type="button"
>
<div
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 a063a5591e3..24f96195e05 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
@@ -3,8 +3,6 @@ import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
-jest.mock('~/flash');
-
describe('Upload dropzone component', () => {
let wrapper;
@@ -34,11 +32,6 @@ describe('Upload dropzone component', () => {
});
}
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when slot provided', () => {
it('renders dropzone with slot content', () => {
createComponent({
diff --git a/spec/frontend/vue_shared/components/url_sync_spec.js b/spec/frontend/vue_shared/components/url_sync_spec.js
index 30a7439579f..2718be74111 100644
--- a/spec/frontend/vue_shared/components/url_sync_spec.js
+++ b/spec/frontend/vue_shared/components/url_sync_spec.js
@@ -33,10 +33,6 @@ describe('url sync component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const expectUrlSyncWithMergeUrlParams = (
query,
times,
diff --git a/spec/frontend/vue_shared/components/usage_quotas/usage_banner_spec.js b/spec/frontend/vue_shared/components/usage_quotas/usage_banner_spec.js
index 662c09d02bf..ba55df5512f 100644
--- a/spec/frontend/vue_shared/components/usage_quotas/usage_banner_spec.js
+++ b/spec/frontend/vue_shared/components/usage_quotas/usage_banner_spec.js
@@ -24,10 +24,6 @@ describe('usage banner', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe.each`
slotName | finderFunction
${'left-primary-text'} | ${findLeftPrimaryTextSlot}
diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js
index d63b13981ac..3ae3d89af27 100644
--- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js
+++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_image_spec.js
@@ -20,10 +20,6 @@ describe('User Avatar Image Component', () => {
const findAvatar = () => wrapper.findComponent(GlAvatar);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Initialization', () => {
beforeEach(() => {
wrapper = shallowMount(UserAvatarImage, {
diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js
index df7ce449678..90f9156af38 100644
--- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js
+++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_link_spec.js
@@ -34,10 +34,6 @@ describe('User Avatar Link Component', () => {
createWrapper();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render GlLink with correct props', () => {
const link = wrapper.findComponent(GlAvatarLink);
expect(link.exists()).toBe(true);
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 63371b1492b..1754292cb63 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
@@ -50,10 +50,6 @@ describe('UserAvatarList', () => {
props = { imgSize: TEST_IMAGE_SIZE };
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('empty text', () => {
it('shows when items are empty', () => {
factory({ propsData: { items: [] } });
diff --git a/spec/frontend/vue_shared/components/user_callout_dismisser_spec.js b/spec/frontend/vue_shared/components/user_callout_dismisser_spec.js
index 521744154ba..b04e578c931 100644
--- a/spec/frontend/vue_shared/components/user_callout_dismisser_spec.js
+++ b/spec/frontend/vue_shared/components/user_callout_dismisser_spec.js
@@ -28,8 +28,6 @@ const initialSlotProps = (changes = {}) => ({
});
describe('UserCalloutDismisser', () => {
- let wrapper;
-
const MOCK_FEATURE_NAME = 'mock_feature_name';
// Query handlers
@@ -52,7 +50,7 @@ describe('UserCalloutDismisser', () => {
const callDismissSlotProp = () => defaultScopedSlotSpy.mock.calls[0][0].dismiss();
const createComponent = ({ queryHandler, mutationHandler, ...options }) => {
- wrapper = mount(
+ mount(
UserCalloutDismisser,
merge(
{
@@ -72,10 +70,6 @@ describe('UserCalloutDismisser', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when loading', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/vue_shared/components/user_deletion_obstacles/user_deletion_obstacles_list_spec.js b/spec/frontend/vue_shared/components/user_deletion_obstacles/user_deletion_obstacles_list_spec.js
index 78abb89e7b8..6491e5a66cd 100644
--- a/spec/frontend/vue_shared/components/user_deletion_obstacles/user_deletion_obstacles_list_spec.js
+++ b/spec/frontend/vue_shared/components/user_deletion_obstacles/user_deletion_obstacles_list_spec.js
@@ -51,10 +51,6 @@ describe('User deletion obstacles list', () => {
);
}
- afterEach(() => {
- wrapper.destroy();
- });
-
const findLinks = () => wrapper.findAllComponents(GlLink);
const findTitle = () => wrapper.findByTestId('title');
const findFooter = () => wrapper.findByTestId('footer');
diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
index f6316af6ad8..8ecab5cc043 100644
--- a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
+++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js
@@ -13,11 +13,11 @@ import {
I18N_ERROR_UNFOLLOW,
} from '~/vue_shared/components/user_popover/constants';
import axios from '~/lib/utils/axios_utils';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import { followUser, unfollowUser } from '~/api/user_api';
import { mockTracking } from 'helpers/tracking_helper';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/api/user_api', () => ({
followUser: jest.fn(),
unfollowUser: jest.fn(),
@@ -51,7 +51,6 @@ describe('User Popover Component', () => {
});
afterEach(() => {
- wrapper.destroy();
resetHTMLFixture();
});
diff --git a/spec/frontend/vue_shared/components/user_select_spec.js b/spec/frontend/vue_shared/components/user_select_spec.js
index b0e9584a15b..e881bfed35e 100644
--- a/spec/frontend/vue_shared/components/user_select_spec.js
+++ b/spec/frontend/vue_shared/components/user_select_spec.js
@@ -7,7 +7,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import searchUsersQuery from '~/graphql_shared/queries/users_search.query.graphql';
import searchUsersQueryOnMR from '~/graphql_shared/queries/users_search_with_mr_permissions.graphql';
-import { IssuableType } from '~/issues/constants';
+import { TYPE_MERGE_REQUEST } from '~/issues/constants';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import SidebarParticipant from '~/sidebar/components/assignees/sidebar_participant.vue';
import getIssueParticipantsQuery from '~/sidebar/queries/get_issue_participants.query.graphql';
@@ -105,7 +105,6 @@ describe('User select dropdown', () => {
};
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
@@ -409,7 +408,7 @@ describe('User select dropdown', () => {
describe('when on merge request sidebar', () => {
beforeEach(() => {
- createComponent({ props: { issuableType: IssuableType.MergeRequest, issuableId: 1 } });
+ createComponent({ props: { issuableType: TYPE_MERGE_REQUEST, issuableId: 1 } });
return waitForPromises();
});
diff --git a/spec/frontend/vue_shared/components/vuex_module_provider_spec.js b/spec/frontend/vue_shared/components/vuex_module_provider_spec.js
index c136c2054ac..acbb931b7b6 100644
--- a/spec/frontend/vue_shared/components/vuex_module_provider_spec.js
+++ b/spec/frontend/vue_shared/components/vuex_module_provider_spec.js
@@ -27,10 +27,6 @@ describe('~/vue_shared/components/vuex_module_provider', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('provides "vuexModule" set from prop', () => {
createComponent();
expect(findProvidedVuexModule()).toBe(TEST_VUEX_MODULE);
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 18afe049149..f6eb11aaddf 100644
--- a/spec/frontend/vue_shared/components/web_ide_link_spec.js
+++ b/spec/frontend/vue_shared/components/web_ide_link_spec.js
@@ -137,10 +137,6 @@ describe('Web IDE link component', () => {
localStorage.setItem(PREFERRED_EDITOR_RESET_KEY, 'true');
});
- afterEach(() => {
- wrapper.destroy();
- });
-
const findActionsButton = () => wrapper.findComponent(ActionsButton);
const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
const findModal = () => wrapper.findComponent(GlModal);
diff --git a/spec/frontend/vue_shared/directives/validation_spec.js b/spec/frontend/vue_shared/directives/validation_spec.js
index dcd3a44a6fc..72a348c1a79 100644
--- a/spec/frontend/vue_shared/directives/validation_spec.js
+++ b/spec/frontend/vue_shared/directives/validation_spec.js
@@ -80,11 +80,6 @@ describe('validation directive', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const getFormData = () => wrapper.vm.form;
const findForm = () => wrapper.find('form');
const findInput = () => wrapper.find('input');
diff --git a/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js b/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js
index 7b0f0f7e344..e983519d9fc 100644
--- a/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js
+++ b/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js
@@ -34,10 +34,6 @@ describe('IssuableCreateRoot', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
it('renders component container element with class "issuable-create-container"', () => {
expect(wrapper.classes()).toContain('issuable-create-container');
diff --git a/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js b/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js
index ff21b3bc356..ae2fd5ebffa 100644
--- a/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js
+++ b/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js
@@ -36,10 +36,6 @@ describe('IssuableForm', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('methods', () => {
describe('handleUpdateSelectedLabels', () => {
it('sets provided `labels` param to prop `selectedLabels`', () => {
diff --git a/spec/frontend/vue_shared/issuable/create/components/issuable_label_selector_spec.js b/spec/frontend/vue_shared/issuable/create/components/issuable_label_selector_spec.js
index 76b6efa15b6..ce9e23d9a00 100644
--- a/spec/frontend/vue_shared/issuable/create/components/issuable_label_selector_spec.js
+++ b/spec/frontend/vue_shared/issuable/create/components/issuable_label_selector_spec.js
@@ -6,11 +6,8 @@ import {
} from 'jest/sidebar/components/labels/labels_select_widget/mock_data';
import IssuableLabelSelector from '~/vue_shared/issuable/create/components/issuable_label_selector.vue';
import LabelsSelect from '~/sidebar/components/labels/labels_select_widget/labels_select_root.vue';
-import {
- DropdownVariant,
- LabelType,
-} from '~/sidebar/components/labels/labels_select_widget/constants';
-import { WorkspaceType } from '~/issues/constants';
+import { DropdownVariant } from '~/sidebar/components/labels/labels_select_widget/constants';
+import { WORKSPACE_PROJECT } from '~/issues/constants';
import { __ } from '~/locale';
const allowLabelRemove = true;
@@ -20,9 +17,9 @@ const fullPath = '/full-path';
const labelsFilterBasePath = '/labels-filter-base-path';
const initialLabels = [];
const issuableType = 'issue';
-const labelType = LabelType.project;
+const labelType = WORKSPACE_PROJECT;
const variant = DropdownVariant.Embedded;
-const workspaceType = WorkspaceType.project;
+const workspaceType = WORKSPACE_PROJECT;
describe('IssuableLabelSelector', () => {
let wrapper;
@@ -50,10 +47,6 @@ describe('IssuableLabelSelector', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const expectTitleWithCount = (count) => {
const title = findTitle();
diff --git a/spec/frontend/vue_shared/issuable/issuable_blocked_icon_spec.js b/spec/frontend/vue_shared/issuable/issuable_blocked_icon_spec.js
index a0b1d64b97c..61e6d2a420a 100644
--- a/spec/frontend/vue_shared/issuable/issuable_blocked_icon_spec.js
+++ b/spec/frontend/vue_shared/issuable/issuable_blocked_icon_spec.js
@@ -7,8 +7,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import IssuableBlockedIcon from '~/vue_shared/components/issuable_blocked_icon/issuable_blocked_icon.vue';
import { blockingIssuablesQueries } from '~/vue_shared/components/issuable_blocked_icon/constants';
-import { issuableTypes } from '~/boards/constants';
-import { TYPE_ISSUE } from '~/issues/constants';
+import { TYPE_EPIC, TYPE_ISSUE } from '~/issues/constants';
import { truncate } from '~/lib/utils/text_utility';
import {
mockIssue,
@@ -49,11 +48,6 @@ describe('IssuableBlockedIcon', () => {
await waitForApollo();
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const createWrapperWithApollo = ({
item = mockBlockedIssue1,
blockingIssuablesSpy = jest.fn().mockResolvedValue(mockBlockingIssuablesResponse1),
@@ -121,9 +115,9 @@ describe('IssuableBlockedIcon', () => {
};
it.each`
- mockIssuable | issuableType | expectedIcon
- ${mockIssue} | ${TYPE_ISSUE} | ${'issue-block'}
- ${mockEpic} | ${issuableTypes.epic} | ${'entity-blocked'}
+ mockIssuable | issuableType | expectedIcon
+ ${mockIssue} | ${TYPE_ISSUE} | ${'issue-block'}
+ ${mockEpic} | ${TYPE_EPIC} | ${'entity-blocked'}
`(
'should render blocked icon for $issuableType',
({ mockIssuable, issuableType, expectedIcon }) => {
@@ -153,9 +147,9 @@ describe('IssuableBlockedIcon', () => {
describe('on mouseenter on blocked icon', () => {
it.each`
- item | issuableType | mockBlockingIssuable | issuableItem | blockingIssuablesSpy
- ${mockBlockedIssue1} | ${TYPE_ISSUE} | ${mockBlockingIssue1} | ${mockIssue} | ${jest.fn().mockResolvedValue(mockBlockingIssuablesResponse1)}
- ${mockBlockedEpic1} | ${issuableTypes.epic} | ${mockBlockingEpic1} | ${mockEpic} | ${jest.fn().mockResolvedValue(mockBlockingEpicIssuablesResponse1)}
+ item | issuableType | mockBlockingIssuable | issuableItem | blockingIssuablesSpy
+ ${mockBlockedIssue1} | ${TYPE_ISSUE} | ${mockBlockingIssue1} | ${mockIssue} | ${jest.fn().mockResolvedValue(mockBlockingIssuablesResponse1)}
+ ${mockBlockedEpic1} | ${TYPE_EPIC} | ${mockBlockingEpic1} | ${mockEpic} | ${jest.fn().mockResolvedValue(mockBlockingEpicIssuablesResponse1)}
`(
'should query for blocking issuables and render the result for $issuableType',
async ({ item, issuableType, issuableItem, mockBlockingIssuable, blockingIssuablesSpy }) => {
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 a25f92c9cf2..c23bd002ee5 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
@@ -28,7 +28,6 @@ describe('IssuableBulkEditSidebar', () => {
});
afterEach(() => {
- wrapper.destroy();
resetHTMLFixture();
});
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 2fac004875a..45daf0dc34b 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
@@ -39,7 +39,6 @@ describe('IssuableItem', () => {
const mockLabels = mockIssuable.labels.nodes;
const mockAuthor = mockIssuable.author;
- const originalUrl = gon.gitlab_url;
let wrapper;
const findTimestampWrapper = () => wrapper.find('[data-testid="issuable-timestamp"]');
@@ -49,11 +48,6 @@ describe('IssuableItem', () => {
gon.gitlab_url = MOCK_GITLAB_URL;
});
- afterEach(() => {
- wrapper.destroy();
- gon.gitlab_url = originalUrl;
- });
-
describe('computed', () => {
describe('author', () => {
it('returns `issuable.author` reference', () => {
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 371844e66f4..9a4636e0f4d 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
@@ -47,10 +47,6 @@ describe('IssuableListRoot', () => {
const findVueDraggable = () => wrapper.findComponent(VueDraggable);
const findPageSizeSelector = () => wrapper.findComponent(PageSizeSelector);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('computed', () => {
beforeEach(() => {
wrapper = createComponent();
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 27985895c62..9cdd4d75c42 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
@@ -35,7 +35,6 @@ describe('IssuableTabs', () => {
afterEach(() => {
setLanguage(null);
- wrapper.destroy();
});
const findAllGlBadges = () => wrapper.findAllComponents(GlBadge);
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 6b20f0c77a3..7e665b7c76e 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
@@ -13,7 +13,7 @@ import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { mockIssuableShowProps, mockIssuable } from '../mock_data';
jest.mock('~/autosave');
-jest.mock('~/flash');
+jest.mock('~/alert');
const issuableBodyProps = {
...mockIssuableShowProps,
@@ -48,10 +48,6 @@ describe('IssuableBody', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('computed', () => {
describe('isUpdated', () => {
it.each`
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_description_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_description_spec.js
index ea58cc2baf5..b4f1c286158 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_description_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_description_spec.js
@@ -24,10 +24,6 @@ describe('IssuableDescription', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('mounted', () => {
it('calls `renderGFM`', () => {
expect(renderGFM).toHaveBeenCalledTimes(1);
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 159be4cd1ef..0d6cd1ad00b 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
@@ -43,6 +43,9 @@ describe('IssuableEditForm', () => {
});
afterEach(() => {
+ // note: the order of wrapper.destroy() and jest.resetAllMocks() matters.
+ // maybe it'll help with investigation on how to remove this wrapper.destroy() call
+ // eslint-disable-next-line @gitlab/vtu-no-explicit-wrapper-destroy
wrapper.destroy();
jest.resetAllMocks();
});
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 6a8b9ef77a9..d9f1b6c15a8 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
@@ -33,7 +33,6 @@ describe('IssuableHeader', () => {
};
afterEach(() => {
- wrapper.destroy();
resetHTMLFixture();
});
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_show_root_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_show_root_spec.js
index edfd55c8bb4..f976e0499f0 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_show_root_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_show_root_spec.js
@@ -41,10 +41,6 @@ describe('IssuableShowRoot', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('template', () => {
const {
statusIcon,
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 6f62fb77353..39316dfa249 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
@@ -22,35 +22,35 @@ const createComponent = (propsData = issuableTitleProps) =>
'status-badge': 'Open',
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
describe('IssuableTitle', () => {
let wrapper;
+ const findStickyHeader = () => wrapper.findComponent('[data-testid="header"]');
+
beforeEach(() => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('methods', () => {
describe('handleTitleAppear', () => {
- it('sets value of `stickyTitleVisible` prop to false', () => {
+ it('sets value of `stickyTitleVisible` prop to false', async () => {
wrapper.findComponent(GlIntersectionObserver).vm.$emit('appear');
+ await nextTick();
- expect(wrapper.vm.stickyTitleVisible).toBe(false);
+ expect(findStickyHeader().exists()).toBe(false);
});
});
describe('handleTitleDisappear', () => {
- it('sets value of `stickyTitleVisible` prop to true', () => {
+ it('sets value of `stickyTitleVisible` prop to true', async () => {
wrapper.findComponent(GlIntersectionObserver).vm.$emit('disappear');
+ await nextTick();
- expect(wrapper.vm.stickyTitleVisible).toBe(true);
+ expect(findStickyHeader().exists()).toBe(true);
});
});
});
@@ -87,14 +87,10 @@ describe('IssuableTitle', () => {
});
it('renders sticky header when `stickyTitleVisible` prop 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({
- stickyTitleVisible: true,
- });
-
+ wrapper.findComponent(GlIntersectionObserver).vm.$emit('disappear');
await nextTick();
- const stickyHeaderEl = wrapper.find('[data-testid="header"]');
+
+ const stickyHeaderEl = findStickyHeader();
expect(stickyHeaderEl.exists()).toBe(true);
expect(stickyHeaderEl.findComponent(GlBadge).props('variant')).toBe('success');
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 6c9e5f85fa0..f2509aead77 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
@@ -38,7 +38,6 @@ describe('IssuableSidebarRoot', () => {
};
afterEach(() => {
- wrapper.destroy();
resetHTMLFixture();
});
diff --git a/spec/frontend/vue_shared/new_namespace/components/legacy_container_spec.js b/spec/frontend/vue_shared/new_namespace/components/legacy_container_spec.js
index 52f36aa0e77..052ff518468 100644
--- a/spec/frontend/vue_shared/new_namespace/components/legacy_container_spec.js
+++ b/spec/frontend/vue_shared/new_namespace/components/legacy_container_spec.js
@@ -11,9 +11,7 @@ describe('Legacy container component', () => {
};
afterEach(() => {
- wrapper.destroy();
resetHTMLFixture();
- wrapper = null;
});
describe('when selector targets real node', () => {
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 c90131fea9a..cc8a8d86d19 100644
--- a/spec/frontend/vue_shared/new_namespace/components/welcome_spec.js
+++ b/spec/frontend/vue_shared/new_namespace/components/welcome_spec.js
@@ -27,9 +27,7 @@ describe('Welcome page', () => {
});
afterEach(() => {
- wrapper.destroy();
window.location.hash = '';
- wrapper = null;
});
it('tracks link clicks', async () => {
diff --git a/spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js b/spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js
index 6115dc6e61b..5ff7b9f390a 100644
--- a/spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js
+++ b/spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js
@@ -14,7 +14,7 @@ describe('Experimental new project creation app', () => {
const DEFAULT_PROPS = {
title: 'Create something',
- initialBreadcrumb: 'Something',
+ initialBreadcrumbs: [{ text: 'Something', href: '#' }],
panels: [
{ name: 'panel1', selector: '#some-selector1' },
{ name: 'panel2', selector: '#some-selector2' },
@@ -33,7 +33,6 @@ describe('Experimental new project creation app', () => {
};
afterEach(() => {
- wrapper.destroy();
window.location.hash = '';
});
@@ -46,8 +45,8 @@ describe('Experimental new project creation app', () => {
expect(findWelcomePage().exists()).toBe(true);
});
- it('does not render breadcrumbs', () => {
- expect(findBreadcrumb().exists()).toBe(false);
+ it('renders breadcrumbs', () => {
+ expect(findBreadcrumb().exists()).toBe(true);
});
});
@@ -75,7 +74,7 @@ describe('Experimental new project creation app', () => {
it('renders breadcrumbs', () => {
const breadcrumb = findBreadcrumb();
expect(breadcrumb.exists()).toBe(true);
- expect(breadcrumb.props().items[0].text).toBe(DEFAULT_PROPS.initialBreadcrumb);
+ expect(breadcrumb.props().items[0].text).toBe(DEFAULT_PROPS.initialBreadcrumbs[0].text);
});
});
diff --git a/spec/frontend/vue_shared/plugins/global_toast_spec.js b/spec/frontend/vue_shared/plugins/global_toast_spec.js
index 322586a772c..0bf2737fb2b 100644
--- a/spec/frontend/vue_shared/plugins/global_toast_spec.js
+++ b/spec/frontend/vue_shared/plugins/global_toast_spec.js
@@ -1,14 +1,16 @@
-import toast, { instance } from '~/vue_shared/plugins/global_toast';
+import toast from '~/vue_shared/plugins/global_toast';
-describe('Global toast', () => {
- let spyFunc;
-
- beforeEach(() => {
- spyFunc = jest.spyOn(instance.$toast, 'show').mockImplementation(() => {});
- });
+const mockSpy = jest.fn();
+jest.mock('@gitlab/ui', () => ({
+ GlToast: (Vue) => {
+ // eslint-disable-next-line no-param-reassign
+ Vue.prototype.$toast = { show: (...args) => mockSpy(...args) };
+ },
+}));
+describe('Global toast', () => {
afterEach(() => {
- spyFunc.mockRestore();
+ mockSpy.mockRestore();
});
it("should call GitLab UI's toast method", () => {
@@ -17,7 +19,7 @@ describe('Global toast', () => {
toast(arg1, arg2);
- expect(instance.$toast.show).toHaveBeenCalledTimes(1);
- expect(instance.$toast.show).toHaveBeenCalledWith(arg1, arg2);
+ expect(mockSpy).toHaveBeenCalledTimes(1);
+ expect(mockSpy).toHaveBeenCalledWith(arg1, arg2);
});
});
diff --git a/spec/frontend/vue_shared/security_configuration/components/section_layout_spec.js b/spec/frontend/vue_shared/security_configuration/components/section_layout_spec.js
index 136fe74b0d6..d258658d5e2 100644
--- a/spec/frontend/vue_shared/security_configuration/components/section_layout_spec.js
+++ b/spec/frontend/vue_shared/security_configuration/components/section_layout_spec.js
@@ -21,10 +21,6 @@ describe('Section Layout component', () => {
const findHeading = () => wrapper.find('h2');
const findLoader = () => wrapper.findComponent(SectionLoader);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('basic structure', () => {
beforeEach(() => {
createComponent({ heading: 'testheading' });
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 0a5e46d9263..6345393951c 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
@@ -56,10 +56,6 @@ describe('ManageViaMr component', () => {
);
}
- afterEach(() => {
- wrapper.destroy();
- });
-
// This component supports different report types/mutations depending on
// whether it's in a CE or EE context. This makes sure we are only testing
// the ones available in the current test context.
diff --git a/spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js b/spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js
index 5f2b13a79c9..299a3d62421 100644
--- a/spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js
+++ b/spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js
@@ -15,11 +15,6 @@ describe('SecurityReportDownloadDropdown component', () => {
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('given report artifacts', () => {
beforeEach(() => {
artifacts = [
diff --git a/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js b/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js
index 221da35de3d..257f59612e8 100644
--- a/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js
+++ b/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js
@@ -14,7 +14,7 @@ import {
sastDiffSuccessMock,
secretDetectionDiffSuccessMock,
} from 'jest/vue_shared/security_reports/mock_data';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import HelpIcon from '~/vue_shared/security_reports/components/help_icon.vue';
@@ -26,7 +26,7 @@ import {
import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql';
import SecurityReportsApp from '~/vue_shared/security_reports/security_reports_app.vue';
-jest.mock('~/flash');
+jest.mock('~/alert');
Vue.use(VueApollo);
Vue.use(Vuex);
@@ -74,10 +74,6 @@ describe('Security reports app', () => {
const findDownloadDropdown = () => wrapper.findComponent(SecurityReportDownloadDropdown);
const findHelpIconComponent = () => wrapper.findComponent(HelpIcon);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('given the artifacts query is loading', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/frontend/webhooks/components/form_url_app_spec.js b/spec/frontend/webhooks/components/form_url_app_spec.js
index 45a39d2dd58..cbeff184e9d 100644
--- a/spec/frontend/webhooks/components/form_url_app_spec.js
+++ b/spec/frontend/webhooks/components/form_url_app_spec.js
@@ -19,10 +19,6 @@ describe('FormUrlApp', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findAllRadioButtons = () => wrapper.findAllComponents(GlFormRadio);
const findRadioGroup = () => wrapper.findComponent(GlFormRadioGroup);
const findUrlMaskDisable = () => findAllRadioButtons().at(0);
diff --git a/spec/frontend/whats_new/components/app_spec.js b/spec/frontend/whats_new/components/app_spec.js
index ee15034daff..000b07f4dfd 100644
--- a/spec/frontend/whats_new/components/app_spec.js
+++ b/spec/frontend/whats_new/components/app_spec.js
@@ -49,7 +49,7 @@ describe('App', () => {
store,
propsData: buildProps(),
directives: {
- GlResizeObserver: createMockDirective(),
+ GlResizeObserver: createMockDirective('gl-resize-observer'),
},
});
};
@@ -71,7 +71,6 @@ describe('App', () => {
};
afterEach(() => {
- wrapper.destroy();
unmockTracking();
});
diff --git a/spec/frontend/whats_new/components/feature_spec.js b/spec/frontend/whats_new/components/feature_spec.js
index 099054bf8ca..d69ac2803df 100644
--- a/spec/frontend/whats_new/components/feature_spec.js
+++ b/spec/frontend/whats_new/components/feature_spec.js
@@ -30,11 +30,6 @@ describe("What's new single feature", () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('renders the date', () => {
createWrapper({ feature: exampleFeature });
diff --git a/spec/frontend/whats_new/utils/get_drawer_body_height_spec.js b/spec/frontend/whats_new/utils/get_drawer_body_height_spec.js
index b199f4f0c49..79717b8767e 100644
--- a/spec/frontend/whats_new/utils/get_drawer_body_height_spec.js
+++ b/spec/frontend/whats_new/utils/get_drawer_body_height_spec.js
@@ -11,10 +11,6 @@ describe('~/whats_new/utils/get_drawer_body_height', () => {
});
});
- afterEach(() => {
- drawerWrapper.destroy();
- });
-
const setClientHeight = (el, height) => {
Object.defineProperty(el, 'clientHeight', {
get() {
diff --git a/spec/frontend/work_items/components/app_spec.js b/spec/frontend/work_items/components/app_spec.js
index 95034085493..d799e8042b1 100644
--- a/spec/frontend/work_items/components/app_spec.js
+++ b/spec/frontend/work_items/components/app_spec.js
@@ -12,10 +12,6 @@ describe('Work Items Application', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders a component', () => {
createComponent();
diff --git a/spec/frontend/work_items/components/item_state_spec.js b/spec/frontend/work_items/components/item_state_spec.js
index c3cc2fbc556..c3bdbfe030e 100644
--- a/spec/frontend/work_items/components/item_state_spec.js
+++ b/spec/frontend/work_items/components/item_state_spec.js
@@ -21,10 +21,6 @@ describe('ItemState', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders label and dropdown', () => {
createComponent();
diff --git a/spec/frontend/work_items/components/item_title_spec.js b/spec/frontend/work_items/components/item_title_spec.js
index 6361f8dafc4..aef310319ab 100644
--- a/spec/frontend/work_items/components/item_title_spec.js
+++ b/spec/frontend/work_items/components/item_title_spec.js
@@ -19,10 +19,6 @@ describe('ItemTitle', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders title contents', () => {
expect(findInputEl().attributes()).toMatchObject({
'data-placeholder': 'Add a title...',
diff --git a/spec/frontend/work_items/components/notes/__snapshots__/work_item_note_replying_spec.js.snap b/spec/frontend/work_items/components/notes/__snapshots__/work_item_note_replying_spec.js.snap
index 5901642b8a1..1c01451f047 100644
--- a/spec/frontend/work_items/components/notes/__snapshots__/work_item_note_replying_spec.js.snap
+++ b/spec/frontend/work_items/components/notes/__snapshots__/work_item_note_replying_spec.js.snap
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Work Item Note Replying should have the note body and header 1`] = `"<note-header-stub author=\\"[object Object]\\" actiontext=\\"\\" noteabletype=\\"\\" expanded=\\"true\\" showspinner=\\"true\\"></note-header-stub>"`;
+exports[`Work Item Note Replying should have the note body and header 1`] = `"<note-header-stub author=\\"[object Object]\\" actiontext=\\"\\" noteabletype=\\"\\" expanded=\\"true\\" showspinner=\\"true\\" noteurl=\\"\\"></note-header-stub>"`;
diff --git a/spec/frontend/work_items/components/notes/activity_filter_spec.js b/spec/frontend/work_items/components/notes/activity_filter_spec.js
deleted file mode 100644
index eb4bcbf942b..00000000000
--- a/spec/frontend/work_items/components/notes/activity_filter_spec.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import { nextTick } from 'vue';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import ActivityFilter from '~/work_items/components/notes/activity_filter.vue';
-import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
-import { ASC, DESC } from '~/notes/constants';
-
-import { mockTracking } from 'helpers/tracking_helper';
-import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
-
-describe('Activity Filter', () => {
- let wrapper;
-
- const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
- const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
- const findNewestFirstItem = () => wrapper.findByTestId('js-newest-first');
-
- const createComponent = ({ sortOrder = ASC, loading = false, workItemType = 'Task' } = {}) => {
- wrapper = shallowMountExtended(ActivityFilter, {
- propsData: {
- sortOrder,
- loading,
- workItemType,
- },
- });
- };
-
- beforeEach(() => {
- createComponent();
- });
-
- describe('default', () => {
- it('has a dropdown with 2 options', () => {
- expect(findDropdown().exists()).toBe(true);
- expect(findAllDropdownItems()).toHaveLength(ActivityFilter.SORT_OPTIONS.length);
- });
-
- it('has local storage sync with the correct props', () => {
- expect(findLocalStorageSync().props('asString')).toBe(true);
- });
-
- it('emits `updateSavedSortOrder` event when update is emitted', async () => {
- findLocalStorageSync().vm.$emit('input', ASC);
-
- await nextTick();
- expect(wrapper.emitted('updateSavedSortOrder')).toHaveLength(1);
- expect(wrapper.emitted('updateSavedSortOrder')).toEqual([[ASC]]);
- });
- });
-
- describe('when asc', () => {
- describe('when the dropdown is clicked', () => {
- it('calls the right actions', async () => {
- const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
- findNewestFirstItem().vm.$emit('click');
- await nextTick();
-
- expect(wrapper.emitted('changeSortOrder')).toHaveLength(1);
- expect(wrapper.emitted('changeSortOrder')).toEqual([[DESC]]);
-
- expect(trackingSpy).toHaveBeenCalledWith(
- TRACKING_CATEGORY_SHOW,
- 'notes_sort_order_changed',
- {
- category: TRACKING_CATEGORY_SHOW,
- label: 'item_track_notes_sorting',
- property: 'type_Task',
- },
- );
- });
- });
- });
-});
diff --git a/spec/frontend/work_items/components/notes/work_item_activity_sort_filter_spec.js b/spec/frontend/work_items/components/notes/work_item_activity_sort_filter_spec.js
new file mode 100644
index 00000000000..5ed9d581446
--- /dev/null
+++ b/spec/frontend/work_items/components/notes/work_item_activity_sort_filter_spec.js
@@ -0,0 +1,109 @@
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import WorkItemActivitySortFilter from '~/work_items/components/notes/work_item_activity_sort_filter.vue';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
+import { ASC, DESC } from '~/notes/constants';
+import {
+ WORK_ITEM_ACTIVITY_SORT_OPTIONS,
+ WORK_ITEM_NOTES_SORT_ORDER_KEY,
+ WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS,
+ WORK_ITEM_NOTES_FILTER_KEY,
+ WORK_ITEM_NOTES_FILTER_ALL_NOTES,
+ WORK_ITEM_ACTIVITY_FILTER_OPTIONS,
+ TRACKING_CATEGORY_SHOW,
+} from '~/work_items/constants';
+
+import { mockTracking } from 'helpers/tracking_helper';
+
+describe('Work Item Activity/Discussions Filtering', () => {
+ let wrapper;
+
+ const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findByDataTestId = (dataTestId) => wrapper.findByTestId(dataTestId);
+
+ const createComponent = ({
+ loading = false,
+ workItemType = 'Task',
+ sortFilterProp = ASC,
+ filterOptions = WORK_ITEM_ACTIVITY_SORT_OPTIONS,
+ trackingLabel = 'item_track_notes_sorting',
+ trackingAction = 'work_item_notes_sort_order_changed',
+ filterEvent = 'changeSort',
+ defaultSortFilterProp = ASC,
+ storageKey = WORK_ITEM_NOTES_SORT_ORDER_KEY,
+ } = {}) => {
+ wrapper = shallowMountExtended(WorkItemActivitySortFilter, {
+ propsData: {
+ loading,
+ workItemType,
+ sortFilterProp,
+ filterOptions,
+ trackingLabel,
+ trackingAction,
+ filterEvent,
+ defaultSortFilterProp,
+ storageKey,
+ },
+ });
+ };
+
+ describe.each`
+ usedFor | filterOptions | storageKey | filterEvent | newInputOption | trackingLabel | trackingAction | defaultSortFilterProp | sortFilterProp | nonDefaultDataTestId
+ ${'Sorting'} | ${WORK_ITEM_ACTIVITY_SORT_OPTIONS} | ${WORK_ITEM_NOTES_SORT_ORDER_KEY} | ${'changeSort'} | ${DESC} | ${'item_track_notes_sorting'} | ${'work_item_notes_sort_order_changed'} | ${ASC} | ${ASC} | ${'newest-first'}
+ ${'Filtering'} | ${WORK_ITEM_ACTIVITY_FILTER_OPTIONS} | ${WORK_ITEM_NOTES_FILTER_KEY} | ${'changeFilter'} | ${WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS} | ${'item_track_notes_sorting'} | ${'work_item_notes_filter_changed'} | ${WORK_ITEM_NOTES_FILTER_ALL_NOTES} | ${WORK_ITEM_NOTES_FILTER_ALL_NOTES} | ${'comments-activity'}
+ `(
+ 'When used for $usedFor',
+ ({
+ filterOptions,
+ storageKey,
+ filterEvent,
+ trackingLabel,
+ trackingAction,
+ newInputOption,
+ defaultSortFilterProp,
+ sortFilterProp,
+ nonDefaultDataTestId,
+ }) => {
+ beforeEach(() => {
+ createComponent({
+ sortFilterProp,
+ filterOptions,
+ trackingLabel,
+ trackingAction,
+ filterEvent,
+ defaultSortFilterProp,
+ storageKey,
+ });
+ });
+
+ it('has a dropdown with options equal to the length of `filterOptions`', () => {
+ expect(findDropdown().exists()).toBe(true);
+ expect(findAllDropdownItems()).toHaveLength(filterOptions.length);
+ });
+
+ it('has local storage sync with the correct props', () => {
+ expect(findLocalStorageSync().props('asString')).toBe(true);
+ expect(findLocalStorageSync().props('storageKey')).toBe(storageKey);
+ });
+
+ it(`emits ${filterEvent} event when local storage input is emitted`, () => {
+ findLocalStorageSync().vm.$emit('input', newInputOption);
+
+ expect(wrapper.emitted(filterEvent)).toEqual([[newInputOption]]);
+ });
+
+ it('emits tracking event when the a non default dropdown item is clicked', () => {
+ const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ findByDataTestId(nonDefaultDataTestId).vm.$emit('click');
+
+ expect(trackingSpy).toHaveBeenCalledWith(TRACKING_CATEGORY_SHOW, trackingAction, {
+ category: TRACKING_CATEGORY_SHOW,
+ label: trackingLabel,
+ property: 'type_Task',
+ });
+ });
+ },
+ );
+});
diff --git a/spec/frontend/work_items/components/notes/work_item_discussion_spec.js b/spec/frontend/work_items/components/notes/work_item_discussion_spec.js
index bb65b75c4d8..6b95da0910b 100644
--- a/spec/frontend/work_items/components/notes/work_item_discussion_spec.js
+++ b/spec/frontend/work_items/components/notes/work_item_discussion_spec.js
@@ -1,7 +1,5 @@
-import { GlAvatarLink, GlAvatar } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
-import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import ToggleRepliesWidget from '~/notes/components/toggle_replies_widget.vue';
import WorkItemDiscussion from '~/work_items/components/notes/work_item_discussion.vue';
import WorkItemNote from '~/work_items/components/notes/work_item_note.vue';
@@ -21,9 +19,6 @@ describe('Work Item Discussion', () => {
let wrapper;
const mockWorkItemId = 'gid://gitlab/WorkItem/625';
- const findTimelineEntryItem = () => wrapper.findComponent(TimelineEntryItem);
- const findAvatarLink = () => wrapper.findComponent(GlAvatarLink);
- const findAvatar = () => wrapper.findComponent(GlAvatar);
const findToggleRepliesWidget = () => wrapper.findComponent(ToggleRepliesWidget);
const findAllThreads = () => wrapper.findAllComponents(WorkItemNote);
const findThreadAtIndex = (index) => findAllThreads().at(index);
@@ -55,19 +50,6 @@ describe('Work Item Discussion', () => {
createComponent();
});
- it('Should be wrapped inside the timeline entry item', () => {
- expect(findTimelineEntryItem().exists()).toBe(true);
- });
-
- it('should have the author avatar of the work item note', () => {
- expect(findAvatarLink().exists()).toBe(true);
- expect(findAvatarLink().attributes('href')).toBe(mockWorkItemCommentNote.author.webUrl);
-
- expect(findAvatar().exists()).toBe(true);
- expect(findAvatar().props('src')).toBe(mockWorkItemCommentNote.author.avatarUrl);
- expect(findAvatar().props('entityName')).toBe(mockWorkItemCommentNote.author.username);
- });
-
it('should not show the the toggle replies widget wrapper when no replies', () => {
expect(findToggleRepliesWidget().exists()).toBe(false);
});
@@ -89,13 +71,17 @@ describe('Work Item Discussion', () => {
});
it('the number of threads should be equal to the response length', async () => {
- findToggleRepliesWidget().vm.$emit('toggle');
- await nextTick();
expect(findAllThreads()).toHaveLength(
mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0].notes.nodes.length,
);
});
+ it('should collapse when we click on toggle replies widget', async () => {
+ findToggleRepliesWidget().vm.$emit('toggle');
+ await nextTick();
+ expect(findAllThreads()).toHaveLength(1);
+ });
+
it('should autofocus when we click expand replies', async () => {
const mainComment = findThreadAtIndex(0);
diff --git a/spec/frontend/work_items/components/notes/work_item_history_only_filter_note_spec.js b/spec/frontend/work_items/components/notes/work_item_history_only_filter_note_spec.js
new file mode 100644
index 00000000000..339efad0608
--- /dev/null
+++ b/spec/frontend/work_items/components/notes/work_item_history_only_filter_note_spec.js
@@ -0,0 +1,44 @@
+import { GlSprintf } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import WorkItemHistoryOnlyFilterNote from '~/work_items/components/notes/work_item_history_only_filter_note.vue';
+import {
+ WORK_ITEM_NOTES_FILTER_ALL_NOTES,
+ WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS,
+} from '~/work_items/constants';
+
+describe('Work Item History Filter note', () => {
+ let wrapper;
+
+ const findShowAllActivityButton = () => wrapper.findByTestId('show-all-activity');
+ const findShowCommentsButton = () => wrapper.findByTestId('show-comments-only');
+
+ const createComponent = () => {
+ wrapper = shallowMountExtended(WorkItemHistoryOnlyFilterNote, {
+ stubs: {
+ GlSprintf,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('timelineContent renders a string containing instruction for switching feed type', () => {
+ expect(wrapper.text()).toContain(
+ "You're only seeing other activity in the feed. To add a comment, switch to one of the following options.",
+ );
+ });
+
+ it('emits `changeFilter` event with 0 parameter on clicking Show all activity button', () => {
+ findShowAllActivityButton().vm.$emit('click');
+
+ expect(wrapper.emitted('changeFilter')).toEqual([[WORK_ITEM_NOTES_FILTER_ALL_NOTES]]);
+ });
+
+ it('emits `changeFilter` event with 1 parameter on clicking Show comments only button', () => {
+ findShowCommentsButton().vm.$emit('click');
+
+ expect(wrapper.emitted('changeFilter')).toEqual([[WORK_ITEM_NOTES_FILTER_ONLY_COMMENTS]]);
+ });
+});
diff --git a/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js b/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js
index d85cd46c1c3..b293127b6af 100644
--- a/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js
+++ b/spec/frontend/work_items/components/notes/work_item_note_actions_spec.js
@@ -1,52 +1,116 @@
import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import EmojiPicker from '~/emoji/components/picker.vue';
+import waitForPromises from 'helpers/wait_for_promises';
import ReplyButton from '~/notes/components/note_actions/reply_button.vue';
import WorkItemNoteActions from '~/work_items/components/notes/work_item_note_actions.vue';
+import addAwardEmojiMutation from '~/work_items/graphql/notes/work_item_note_add_award_emoji.mutation.graphql';
+
+Vue.use(VueApollo);
describe('Work Item Note Actions', () => {
let wrapper;
+ const noteId = '1';
const findReplyButton = () => wrapper.findComponent(ReplyButton);
const findEditButton = () => wrapper.find('[data-testid="edit-work-item-note"]');
+ const findEmojiButton = () => wrapper.find('[data-testid="note-emoji-button"]');
+
+ const addEmojiMutationResolver = jest.fn().mockResolvedValue({
+ data: {
+ errors: [],
+ },
+ });
+
+ const EmojiPickerStub = {
+ props: EmojiPicker.props,
+ template: '<div></div>',
+ };
- const createComponent = ({ showReply = true, showEdit = true } = {}) => {
+ const createComponent = ({ showReply = true, showEdit = true, showAwardEmoji = true } = {}) => {
wrapper = shallowMount(WorkItemNoteActions, {
propsData: {
showReply,
showEdit,
+ noteId,
+ showAwardEmoji,
+ },
+ provide: {
+ glFeatures: {
+ workItemsMvc2: true,
+ },
},
+ stubs: {
+ EmojiPicker: EmojiPickerStub,
+ },
+ apolloProvider: createMockApollo([[addAwardEmojiMutation, addEmojiMutationResolver]]),
});
};
- describe('Default', () => {
- it('Should show the reply button by default', () => {
+ describe('reply button', () => {
+ it('is visible by default', () => {
createComponent();
+
expect(findReplyButton().exists()).toBe(true);
});
- });
- describe('When the reply button needs to be hidden', () => {
- it('Should show the reply button by default', () => {
+ it('is hidden when showReply false', () => {
createComponent({ showReply: false });
+
expect(findReplyButton().exists()).toBe(false);
});
});
- it('shows edit button when `showEdit` prop is true', () => {
- createComponent();
+ describe('edit button', () => {
+ it('is visible when `showEdit` prop is true', () => {
+ createComponent();
- expect(findEditButton().exists()).toBe(true);
- });
+ expect(findEditButton().exists()).toBe(true);
+ });
+
+ it('is hidden when `showEdit` prop is false', () => {
+ createComponent({ showEdit: false });
+
+ expect(findEditButton().exists()).toBe(false);
+ });
- it('does not show edit button when `showEdit` prop is false', () => {
- createComponent({ showEdit: false });
+ it('emits `startEditing` event when clicked', () => {
+ createComponent();
+ findEditButton().vm.$emit('click');
- expect(findEditButton().exists()).toBe(false);
+ expect(wrapper.emitted('startEditing')).toEqual([[]]);
+ });
});
- it('emits `startEditing` event when edit button is clicked', () => {
- createComponent();
- findEditButton().vm.$emit('click');
+ describe('emoji picker', () => {
+ it('is visible when `showAwardEmoji` prop is true', () => {
+ createComponent();
+
+ expect(findEmojiButton().exists()).toBe(true);
+ });
+
+ it('is hidden when `showAwardEmoji` prop is false', () => {
+ createComponent({ showAwardEmoji: false });
- expect(wrapper.emitted('startEditing')).toEqual([[]]);
+ expect(findEmojiButton().exists()).toBe(false);
+ });
+
+ it('commits mutation on click', async () => {
+ const awardName = 'carrot';
+
+ createComponent();
+
+ findEmojiButton().vm.$emit('click', awardName);
+
+ await waitForPromises();
+
+ expect(findEmojiButton().emitted('errors')).toEqual(undefined);
+ expect(addEmojiMutationResolver).toHaveBeenCalledWith({
+ awardableId: noteId,
+ name: awardName,
+ });
+ });
});
});
diff --git a/spec/frontend/work_items/components/notes/work_item_note_spec.js b/spec/frontend/work_items/components/notes/work_item_note_spec.js
index 9b87419cee7..8e574dc1a81 100644
--- a/spec/frontend/work_items/components/notes/work_item_note_spec.js
+++ b/spec/frontend/work_items/components/notes/work_item_note_spec.js
@@ -198,10 +198,6 @@ describe('Work Item Note', () => {
expect(findNoteActions().exists()).toBe(true);
});
- it('should not have the Avatar link for main thread inside the timeline-entry', () => {
- expect(findAuthorAvatarLink().exists()).toBe(false);
- });
-
it('should have the reply button props', () => {
expect(findNoteActions().props('showReply')).toBe(true);
});
@@ -228,7 +224,7 @@ describe('Work Item Note', () => {
});
});
- it('should display a dropdown if user has a permission to delete a note', () => {
+ it('should display the `Delete comment` dropdown item if user has a permission to delete a note', () => {
createComponent({
note: {
...mockWorkItemCommentNote,
@@ -237,12 +233,14 @@ describe('Work Item Note', () => {
});
expect(findDropdown().exists()).toBe(true);
+ expect(findDeleteNoteButton().exists()).toBe(true);
});
- it('should not display a dropdown if user has no permission to delete a note', () => {
+ it('should not display the `Delete comment` dropdown item if user has no permission to delete a note', () => {
createComponent();
- expect(findDropdown().exists()).toBe(false);
+ expect(findDropdown().exists()).toBe(true);
+ expect(findDeleteNoteButton().exists()).toBe(false);
});
it('should emit `deleteNote` event when delete note action is clicked', () => {
diff --git a/spec/frontend/work_items/components/notes/work_item_notes_activity_header_spec.js b/spec/frontend/work_items/components/notes/work_item_notes_activity_header_spec.js
new file mode 100644
index 00000000000..daf74f7a93b
--- /dev/null
+++ b/spec/frontend/work_items/components/notes/work_item_notes_activity_header_spec.js
@@ -0,0 +1,63 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import WorkItemNotesActivityHeader from '~/work_items/components/notes/work_item_notes_activity_header.vue';
+import { ASC } from '~/notes/constants';
+import {
+ WORK_ITEM_NOTES_FILTER_ALL_NOTES,
+ WORK_ITEM_NOTES_FILTER_ONLY_HISTORY,
+} from '~/work_items/constants';
+
+describe('Work Item Note Activity Header', () => {
+ let wrapper;
+
+ const findActivityLabelHeading = () => wrapper.find('h3');
+ const findActivityFilterDropdown = () => wrapper.findByTestId('work-item-filter');
+ const findActivitySortDropdown = () => wrapper.findByTestId('work-item-sort');
+
+ const createComponent = ({
+ disableActivityFilterSort = false,
+ sortOrder = ASC,
+ workItemType = 'Task',
+ discussionFilter = WORK_ITEM_NOTES_FILTER_ALL_NOTES,
+ } = {}) => {
+ wrapper = shallowMountExtended(WorkItemNotesActivityHeader, {
+ propsData: {
+ disableActivityFilterSort,
+ sortOrder,
+ workItemType,
+ discussionFilter,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('Should have the Activity label', () => {
+ expect(findActivityLabelHeading().text()).toBe(WorkItemNotesActivityHeader.i18n.activityLabel);
+ });
+
+ it('Should have Activity filtering dropdown', () => {
+ expect(findActivityFilterDropdown().exists()).toBe(true);
+ });
+
+ it('Should have Activity sorting dropdown', () => {
+ expect(findActivitySortDropdown().exists()).toBe(true);
+ });
+
+ describe('Activity Filter', () => {
+ it('emits `changeFilter` when filtering discussions', () => {
+ findActivityFilterDropdown().vm.$emit('changeFilter', WORK_ITEM_NOTES_FILTER_ONLY_HISTORY);
+
+ expect(wrapper.emitted('changeFilter')).toEqual([[WORK_ITEM_NOTES_FILTER_ONLY_HISTORY]]);
+ });
+ });
+
+ describe('Activity Sorting', () => {
+ it('emits `changeSort` when sorting discussions/activity', () => {
+ findActivitySortDropdown().vm.$emit('changeSort', ASC);
+
+ expect(wrapper.emitted('changeSort')).toEqual([[ASC]]);
+ });
+ });
+});
diff --git a/spec/frontend/work_items/components/work_item_actions_spec.js b/spec/frontend/work_items/components/work_item_actions_spec.js
index 3c312fb4552..a0db8172bf6 100644
--- a/spec/frontend/work_items/components/work_item_actions_spec.js
+++ b/spec/frontend/work_items/components/work_item_actions_spec.js
@@ -52,10 +52,6 @@ describe('WorkItemActions component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders modal', () => {
createComponent();
diff --git a/spec/frontend/work_items/components/work_item_assignees_spec.js b/spec/frontend/work_items/components/work_item_assignees_spec.js
index e85f62b881d..2a8159f7294 100644
--- a/spec/frontend/work_items/components/work_item_assignees_spec.js
+++ b/spec/frontend/work_items/components/work_item_assignees_spec.js
@@ -113,10 +113,6 @@ describe('WorkItemAssignees component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('passes the correct data-user-id attribute', () => {
createComponent();
diff --git a/spec/frontend/work_items/components/work_item_description_rendered_spec.js b/spec/frontend/work_items/components/work_item_description_rendered_spec.js
index 0ab2546440b..4f1d49ee2e5 100644
--- a/spec/frontend/work_items/components/work_item_description_rendered_spec.js
+++ b/spec/frontend/work_items/components/work_item_description_rendered_spec.js
@@ -29,10 +29,6 @@ describe('WorkItemDescription', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders gfm', async () => {
createComponent();
diff --git a/spec/frontend/work_items/components/work_item_description_spec.js b/spec/frontend/work_items/components/work_item_description_spec.js
index a12ec23c15a..b4b7b8989ea 100644
--- a/spec/frontend/work_items/components/work_item_description_spec.js
+++ b/spec/frontend/work_items/components/work_item_description_spec.js
@@ -99,10 +99,6 @@ describe('WorkItemDescription', () => {
}
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('editing description with workItemsMvc FF enabled', () => {
beforeEach(() => {
workItemsMvc = true;
@@ -117,11 +113,14 @@ describe('WorkItemDescription', () => {
await createComponent({ isEditing: true });
expect(findMarkdownEditor().props()).toMatchObject({
- autocompleteDataSources: autocompleteDataSources(fullPath, iid),
supportsQuickActions: true,
renderMarkdownPath: markdownPreviewPath(fullPath, iid),
quickActionsDocsPath: wrapper.vm.$options.quickActionsDocsPath,
});
+
+ expect(findMarkdownEditor().vm.$attrs).toMatchObject({
+ 'autocomplete-data-sources': autocompleteDataSources(fullPath, iid),
+ });
});
});
diff --git a/spec/frontend/work_items/components/work_item_detail_modal_spec.js b/spec/frontend/work_items/components/work_item_detail_modal_spec.js
index 938cf6e6f51..1bdf5d1c840 100644
--- a/spec/frontend/work_items/components/work_item_detail_modal_spec.js
+++ b/spec/frontend/work_items/components/work_item_detail_modal_spec.js
@@ -82,10 +82,6 @@ describe('WorkItemDetailModal component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders WorkItemDetail', () => {
createComponent();
diff --git a/spec/frontend/work_items/components/work_item_detail_spec.js b/spec/frontend/work_items/components/work_item_detail_spec.js
index 64a7502671e..fe7556f8ec6 100644
--- a/spec/frontend/work_items/components/work_item_detail_spec.js
+++ b/spec/frontend/work_items/components/work_item_detail_spec.js
@@ -99,9 +99,7 @@ describe('WorkItemDetail component', () => {
subscriptionHandler = titleSubscriptionHandler,
confidentialityMock = [updateWorkItemMutation, jest.fn()],
error = undefined,
- workItemsMvcEnabled = false,
workItemsMvc2Enabled = false,
- fetchByIid = false,
} = {}) => {
const handlers = [
[workItemQuery, handler],
@@ -124,9 +122,7 @@ describe('WorkItemDetail component', () => {
},
provide: {
glFeatures: {
- workItemsMvc: workItemsMvcEnabled,
workItemsMvc2: workItemsMvc2Enabled,
- useIidInWorkItemsPath: fetchByIid,
},
hasIssueWeightsFeature: true,
hasIterationsFeature: true,
@@ -149,7 +145,6 @@ describe('WorkItemDetail component', () => {
};
afterEach(() => {
- wrapper.destroy();
setWindowLocation('');
});
@@ -420,6 +415,12 @@ describe('WorkItemDetail component', () => {
expect(findParentButton().props('icon')).toBe(mockParent.parent.workItemType.iconName);
});
+ it('shows parent title and iid', () => {
+ expect(findParentButton().text()).toBe(
+ `${mockParent.parent.title} #${mockParent.parent.iid}`,
+ );
+ });
+
it('sets the parent breadcrumb URL pointing to issue page when parent type is `Issue`', () => {
expect(findParentButton().attributes().href).toBe('../../issues/5');
});
@@ -441,6 +442,11 @@ describe('WorkItemDetail component', () => {
expect(findParentButton().attributes().href).toBe(mockParentObjective.parent.webUrl);
});
+
+ it('shows work item type and iid', () => {
+ const { iid, workItemType } = workItemQueryResponse.data.workItem;
+ expect(findParent().text()).toContain(`${workItemType.name} #${iid}`);
+ });
});
});
@@ -626,7 +632,7 @@ describe('WorkItemDetail component', () => {
});
});
- it('calls the global ID work item query when `useIidInWorkItemsPath` feature flag is false', async () => {
+ it('calls the global ID work item query when there is no `iid_path` parameter in URL', async () => {
createComponent();
await waitForPromises();
@@ -636,20 +642,10 @@ describe('WorkItemDetail component', () => {
expect(successByIidHandler).not.toHaveBeenCalled();
});
- it('calls the global ID work item query when `useIidInWorkItemsPath` feature flag is true but there is no `iid_path` parameter in URL', async () => {
- createComponent({ fetchByIid: true });
- await waitForPromises();
-
- expect(successHandler).toHaveBeenCalledWith({
- id: workItemQueryResponse.data.workItem.id,
- });
- expect(successByIidHandler).not.toHaveBeenCalled();
- });
-
- it('calls the IID work item query when `useIidInWorkItemsPath` feature flag is true and `iid_path` route parameter is present', async () => {
+ it('calls the IID work item query when `iid_path` route parameter is present', async () => {
setWindowLocation(`?iid_path=true`);
- createComponent({ fetchByIid: true, iidPathQueryParam: 'true' });
+ createComponent();
await waitForPromises();
expect(successHandler).not.toHaveBeenCalled();
@@ -659,10 +655,10 @@ describe('WorkItemDetail component', () => {
});
});
- it('calls the IID work item query when `useIidInWorkItemsPath` feature flag is true and `iid_path` route parameter is present and is a modal', async () => {
+ it('calls the IID work item query when `iid_path` route parameter is present and is a modal', async () => {
setWindowLocation(`?iid_path=true`);
- createComponent({ fetchByIid: true, iidPathQueryParam: 'true', isModal: true });
+ createComponent({ isModal: true });
await waitForPromises();
expect(successHandler).not.toHaveBeenCalled();
@@ -748,21 +744,10 @@ describe('WorkItemDetail component', () => {
});
describe('notes widget', () => {
- it('does not render notes by default', async () => {
+ it('renders notes by default', async () => {
createComponent();
await waitForPromises();
- expect(findNotesWidget().exists()).toBe(false);
- });
-
- it('renders notes when the work_items_mvc flag is on', async () => {
- const notesWorkItem = workItemResponseFactory({
- notesWidgetPresent: true,
- });
- const handler = jest.fn().mockResolvedValue(notesWorkItem);
- createComponent({ workItemsMvcEnabled: true, handler });
- await waitForPromises();
-
expect(findNotesWidget().exists()).toBe(true);
});
});
diff --git a/spec/frontend/work_items/components/work_item_due_date_spec.js b/spec/frontend/work_items/components/work_item_due_date_spec.js
index 7ebaf8209c7..b4811db8bed 100644
--- a/spec/frontend/work_items/components/work_item_due_date_spec.js
+++ b/spec/frontend/work_items/components/work_item_due_date_spec.js
@@ -46,10 +46,6 @@ describe('WorkItemDueDate component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when can update', () => {
describe('start date', () => {
describe('`Add start date` button', () => {
diff --git a/spec/frontend/work_items/components/work_item_labels_spec.js b/spec/frontend/work_items/components/work_item_labels_spec.js
index 0b6ab5c3290..6d51448194b 100644
--- a/spec/frontend/work_items/components/work_item_labels_spec.js
+++ b/spec/frontend/work_items/components/work_item_labels_spec.js
@@ -72,10 +72,6 @@ describe('WorkItemLabels component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('has a label', () => {
createComponent();
diff --git a/spec/frontend/work_items/components/work_item_links/okr_actions_split_button_spec.js b/spec/frontend/work_items/components/work_item_links/okr_actions_split_button_spec.js
index 5fbd8e7e1a7..688dccbda79 100644
--- a/spec/frontend/work_items/components/work_item_links/okr_actions_split_button_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/okr_actions_split_button_spec.js
@@ -15,10 +15,6 @@ describe('RelatedItemsTree', () => {
wrapper = createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('OkrActionsSplitButton', () => {
describe('template', () => {
it('renders objective and key results sections', () => {
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js
index 0470249d7ce..721436e217e 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js
@@ -7,7 +7,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import WorkItemLinkChildMetadata from 'ee_else_ce/work_items/components/work_item_links/work_item_link_child_metadata.vue';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import RichTimestampTooltip from '~/vue_shared/components/rich_timestamp_tooltip.vue';
import getWorkItemTreeQuery from '~/work_items/graphql/work_item_tree.query.graphql';
@@ -31,7 +31,7 @@ import {
workItemObjectiveMetadataWidgets,
} from '../../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('WorkItemLinkChild', () => {
const WORK_ITEM_ID = 'gid://gitlab/WorkItem/2';
@@ -67,10 +67,6 @@ describe('WorkItemLinkChild', () => {
createAlert.mockClear();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it.each`
status | childItem | statusIconName | statusIconColorClass | rawTimestamp | tooltipContents
${'open'} | ${workItemTask} | ${'issue-open-m'} | ${'gl-text-green-500'} | ${workItemTask.createdAt} | ${'Created'}
@@ -109,10 +105,30 @@ describe('WorkItemLinkChild', () => {
});
it('renders item title', () => {
- expect(titleEl.attributes('href')).toBe('/gitlab-org/gitlab-test/-/work_items/4');
+ expect(titleEl.attributes('href')).toBe(
+ '/gitlab-org/gitlab-test/-/work_items/4?iid_path=true',
+ );
expect(titleEl.text()).toBe(workItemTask.title);
});
+ describe('renders item title correctly for relative instance', () => {
+ beforeEach(() => {
+ window.gon = { relative_url_root: '/test' };
+ createComponent();
+ titleEl = wrapper.findByTestId('item-title');
+ });
+
+ it('renders item title with correct href', () => {
+ expect(titleEl.attributes('href')).toBe(
+ '/test/gitlab-org/gitlab-test/-/work_items/4?iid_path=true',
+ );
+ });
+
+ it('renders item title with correct text', () => {
+ expect(titleEl.text()).toBe(workItemTask.title);
+ });
+ });
+
it.each`
action | event | emittedEvent
${'doing mouseover on'} | ${'mouseover'} | ${'mouseover'}
@@ -149,6 +165,8 @@ describe('WorkItemLinkChild', () => {
expect(metadataEl.props()).toMatchObject({
metadataWidgets: workItemObjectiveMetadataWidgets,
});
+
+ expect(wrapper.find('[data-testid="links-child"]').classes()).toContain('gl-py-3');
});
it('does not render item metadata component when item has no metadata present', () => {
@@ -158,6 +176,8 @@ describe('WorkItemLinkChild', () => {
});
expect(findMetadataComponent().exists()).toBe(false);
+
+ expect(wrapper.find('[data-testid="links-child"]').classes()).toContain('gl-py-0');
});
});
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
index 480f8fbcc58..5184b24d202 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_links_form_spec.js
@@ -75,10 +75,6 @@ describe('WorkItemLinksForm', () => {
const findConfidentialCheckbox = () => wrapper.findComponent(GlFormCheckbox);
const findAddChildButton = () => wrapper.findByTestId('add-child-button');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('creating a new work item', () => {
beforeEach(async () => {
await createComponent();
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_menu_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_menu_spec.js
index e3f3b74f296..4e53fc2987b 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_links_menu_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_links_menu_spec.js
@@ -17,10 +17,6 @@ describe('WorkItemLinksMenu', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders dropdown and dropdown items', () => {
expect(findDropdown().exists()).toBe(true);
expect(findRemoveDropdownItem().exists()).toBe(true);
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
index ec51f92b578..99e44b4d89c 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js
@@ -54,7 +54,6 @@ describe('WorkItemLinks', () => {
mutationHandler = mutationChangeParentHandler,
issueDetailsQueryHandler = jest.fn().mockResolvedValue(getIssueDetailsResponse()),
hasIterationsFeature = false,
- fetchByIid = false,
} = {}) => {
mockApollo = createMockApollo(
[
@@ -76,11 +75,7 @@ describe('WorkItemLinks', () => {
},
provide: {
projectPath: 'project/path',
- iid: '1',
hasIterationsFeature,
- glFeatures: {
- useIidInWorkItemsPath: fetchByIid,
- },
},
propsData: { issuableId: 1 },
apolloProvider: mockApollo,
@@ -351,7 +346,7 @@ describe('WorkItemLinks', () => {
beforeEach(async () => {
setWindowLocation('?iid_path=true');
- await createComponent({ fetchByIid: true });
+ await createComponent();
firstChild = findFirstWorkItemLinkChild();
});
@@ -391,7 +386,7 @@ describe('WorkItemLinks', () => {
it('starts prefetching work item by iid if URL contains work item id', async () => {
setWindowLocation('?work_item_iid=5&iid_path=true');
- await createComponent({ fetchByIid: true });
+ await createComponent();
expect(childWorkItemByIidHandler).toHaveBeenCalledWith({
iid: '5',
@@ -402,7 +397,7 @@ describe('WorkItemLinks', () => {
it('does not open the modal if work item iid URL parameter is not found in child items', async () => {
setWindowLocation('?work_item_iid=555&iid_path=true');
- await createComponent({ fetchByIid: true });
+ await createComponent();
expect(showModal).not.toHaveBeenCalled();
expect(wrapper.findComponent(WorkItemDetailModal).props('workItemIid')).toBe(null);
@@ -410,7 +405,7 @@ describe('WorkItemLinks', () => {
it('opens the modal if work item iid URL parameter is found in child items', async () => {
setWindowLocation('?work_item_iid=2&iid_path=true');
- await createComponent({ fetchByIid: true });
+ await createComponent();
expect(showModal).toHaveBeenCalled();
expect(wrapper.findComponent(WorkItemDetailModal).props('workItemIid')).toBe('2');
diff --git a/spec/frontend/work_items/components/work_item_notes_spec.js b/spec/frontend/work_items/components/work_item_notes_spec.js
index 3db848a0ad2..a067923b9fc 100644
--- a/spec/frontend/work_items/components/work_item_notes_spec.js
+++ b/spec/frontend/work_items/components/work_item_notes_spec.js
@@ -9,10 +9,13 @@ import SystemNote from '~/work_items/components/notes/system_note.vue';
import WorkItemNotes from '~/work_items/components/work_item_notes.vue';
import WorkItemDiscussion from '~/work_items/components/notes/work_item_discussion.vue';
import WorkItemAddNote from '~/work_items/components/notes/work_item_add_note.vue';
-import ActivityFilter from '~/work_items/components/notes/activity_filter.vue';
+import WorkItemNotesActivityHeader from '~/work_items/components/notes/work_item_notes_activity_header.vue';
import workItemNotesQuery from '~/work_items/graphql/notes/work_item_notes.query.graphql';
import workItemNotesByIidQuery from '~/work_items/graphql/notes/work_item_notes_by_iid.query.graphql';
import deleteWorkItemNoteMutation from '~/work_items/graphql/notes/delete_work_item_notes.mutation.graphql';
+import workItemNoteCreatedSubscription from '~/work_items/graphql/notes/work_item_note_created.subscription.graphql';
+import workItemNoteUpdatedSubscription from '~/work_items/graphql/notes/work_item_note_updated.subscription.graphql';
+import workItemNoteDeletedSubscription from '~/work_items/graphql/notes/work_item_note_deleted.subscription.graphql';
import { DEFAULT_PAGE_SIZE_NOTES, WIDGET_TYPE_NOTES } from '~/work_items/constants';
import { ASC, DESC } from '~/notes/constants';
import {
@@ -21,6 +24,9 @@ import {
mockWorkItemNotesByIidResponse,
mockMoreWorkItemNotesResponse,
mockWorkItemNotesResponseWithComments,
+ workItemNotesCreateSubscriptionResponse,
+ workItemNotesUpdateSubscriptionResponse,
+ workItemNotesDeleteSubscriptionResponse,
} from '../mock_data';
const mockWorkItemId = workItemQueryResponse.data.workItem.id;
@@ -53,10 +59,9 @@ describe('WorkItemNotes component', () => {
const findAllSystemNotes = () => wrapper.findAllComponents(SystemNote);
const findAllListItems = () => wrapper.findAll('ul.timeline > *');
- const findActivityLabel = () => wrapper.find('label');
const findWorkItemAddNote = () => wrapper.findComponent(WorkItemAddNote);
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
- const findSortingFilter = () => wrapper.findComponent(ActivityFilter);
+ const findActivityHeader = () => wrapper.findComponent(WorkItemNotesActivityHeader);
const findSystemNoteAtIndex = (index) => findAllSystemNotes().at(index);
const findAllWorkItemCommentNotes = () => wrapper.findAllComponents(WorkItemDiscussion);
const findWorkItemCommentNoteAtIndex = (index) => findAllWorkItemCommentNotes().at(index);
@@ -73,6 +78,15 @@ describe('WorkItemNotes component', () => {
const deleteWorkItemNoteMutationSuccessHandler = jest.fn().mockResolvedValue({
data: { destroyNote: { note: null, __typename: 'DestroyNote' } },
});
+ const notesCreateSubscriptionHandler = jest
+ .fn()
+ .mockResolvedValue(workItemNotesCreateSubscriptionResponse);
+ const notesUpdateSubscriptionHandler = jest
+ .fn()
+ .mockResolvedValue(workItemNotesUpdateSubscriptionResponse);
+ const notesDeleteSubscriptionHandler = jest
+ .fn()
+ .mockResolvedValue(workItemNotesDeleteSubscriptionResponse);
const errorHandler = jest.fn().mockRejectedValue('Houston, we have a problem');
const createComponent = ({
@@ -86,6 +100,9 @@ describe('WorkItemNotes component', () => {
[workItemNotesQuery, defaultWorkItemNotesQueryHandler],
[workItemNotesByIidQuery, workItemNotesByIidQueryHandler],
[deleteWorkItemNoteMutation, deleteWINoteMutationHandler],
+ [workItemNoteCreatedSubscription, notesCreateSubscriptionHandler],
+ [workItemNoteUpdatedSubscription, notesUpdateSubscriptionHandler],
+ [workItemNoteDeletedSubscription, notesDeleteSubscriptionHandler],
]),
propsData: {
workItemId,
@@ -96,11 +113,6 @@ describe('WorkItemNotes component', () => {
fetchByIid,
workItemType: 'task',
},
- provide: {
- glFeatures: {
- useIidInWorkItemsPath: fetchByIid,
- },
- },
stubs: {
GlModal: stubComponent(GlModal, { methods: { show: showModal } }),
},
@@ -111,8 +123,8 @@ describe('WorkItemNotes component', () => {
createComponent();
});
- it('renders activity label', () => {
- expect(findActivityLabel().exists()).toBe(true);
+ it('has the work item note activity header', () => {
+ expect(findActivityHeader().exists()).toBe(true);
});
it('passes correct props to comment form component', async () => {
@@ -203,26 +215,22 @@ describe('WorkItemNotes component', () => {
await waitForPromises();
});
- it('filter exists', () => {
- expect(findSortingFilter().exists()).toBe(true);
- });
-
- it('sorts the list when the `changeSortOrder` event is emitted', async () => {
+ it('sorts the list when the `changeSort` event is emitted', async () => {
expect(findSystemNoteAtIndex(0).props('note').id).toEqual(firstSystemNodeId);
- await findSortingFilter().vm.$emit('changeSortOrder', DESC);
+ await findActivityHeader().vm.$emit('changeSort', DESC);
expect(findSystemNoteAtIndex(0).props('note').id).not.toEqual(firstSystemNodeId);
});
it('puts form at start of list in when sorting by newest first', async () => {
- await findSortingFilter().vm.$emit('changeSortOrder', DESC);
+ await findActivityHeader().vm.$emit('changeSort', DESC);
expect(findAllListItems().at(0).is(WorkItemAddNote)).toEqual(true);
});
it('puts form at end of list in when sorting by oldest first', async () => {
- await findSortingFilter().vm.$emit('changeSortOrder', ASC);
+ await findActivityHeader().vm.$emit('changeSort', ASC);
expect(findAllListItems().at(-1).is(WorkItemAddNote)).toEqual(true);
});
@@ -334,4 +342,31 @@ describe('WorkItemNotes component', () => {
['Something went wrong when deleting a comment. Please try again'],
]);
});
+
+ describe('Notes subscriptions', () => {
+ beforeEach(async () => {
+ createComponent({
+ defaultWorkItemNotesQueryHandler: workItemNotesWithCommentsQueryHandler,
+ });
+ await waitForPromises();
+ });
+
+ it('has create notes subscription', () => {
+ expect(notesCreateSubscriptionHandler).toHaveBeenCalledWith({
+ noteableId: mockWorkItemId,
+ });
+ });
+
+ it('has delete notes subscription', () => {
+ expect(notesDeleteSubscriptionHandler).toHaveBeenCalledWith({
+ noteableId: mockWorkItemId,
+ });
+ });
+
+ it('has update notes subscription', () => {
+ expect(notesUpdateSubscriptionHandler).toHaveBeenCalledWith({
+ noteableId: mockWorkItemId,
+ });
+ });
+ });
});
diff --git a/spec/frontend/work_items/components/work_item_state_spec.js b/spec/frontend/work_items/components/work_item_state_spec.js
index b24d940d56a..d1262057c73 100644
--- a/spec/frontend/work_items/components/work_item_state_spec.js
+++ b/spec/frontend/work_items/components/work_item_state_spec.js
@@ -44,10 +44,6 @@ describe('WorkItemState component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders state', () => {
createComponent();
diff --git a/spec/frontend/work_items/components/work_item_title_spec.js b/spec/frontend/work_items/components/work_item_title_spec.js
index a549aad5cd8..34391b74cf7 100644
--- a/spec/frontend/work_items/components/work_item_title_spec.js
+++ b/spec/frontend/work_items/components/work_item_title_spec.js
@@ -41,10 +41,6 @@ describe('WorkItemTitle component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders title', () => {
createComponent();
diff --git a/spec/frontend/work_items/components/work_item_type_icon_spec.js b/spec/frontend/work_items/components/work_item_type_icon_spec.js
index 182fb0f8cb6..a5e955c4dbf 100644
--- a/spec/frontend/work_items/components/work_item_type_icon_spec.js
+++ b/spec/frontend/work_items/components/work_item_type_icon_spec.js
@@ -9,7 +9,7 @@ function createComponent(propsData) {
wrapper = shallowMount(WorkItemTypeIcon, {
propsData,
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
}
@@ -17,10 +17,6 @@ function createComponent(propsData) {
describe('Work Item type component', () => {
const findIcon = () => wrapper.findComponent(GlIcon);
- afterEach(() => {
- wrapper.destroy();
- });
-
describe.each`
workItemType | workItemIconName | iconName | text | showTooltipOnHover
${'TASK'} | ${''} | ${'issue-type-task'} | ${'Task'} | ${false}
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index d4832fe376d..fecf98b2651 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -82,6 +82,7 @@ export const workItemQueryResponse = {
userPermissions: {
deleteWorkItem: false,
updateWorkItem: false,
+ __typename: 'WorkItemPermissions',
},
widgets: [
{
@@ -182,6 +183,7 @@ export const updateWorkItemMutationResponse = {
userPermissions: {
deleteWorkItem: false,
updateWorkItem: false,
+ __typename: 'WorkItemPermissions',
},
widgets: [
{
@@ -330,6 +332,7 @@ export const workItemResponseFactory = ({
userPermissions: {
deleteWorkItem: canDelete,
updateWorkItem: canUpdate,
+ __typename: 'WorkItemPermissions',
},
widgets: [
{
@@ -473,23 +476,20 @@ export const workItemResponseFactory = ({
export const getIssueDetailsResponse = ({ confidential = false } = {}) => ({
data: {
- workspace: {
- id: 'gid://gitlab/Project/1',
- issuable: {
- id: 'gid://gitlab/Issue/4',
- confidential,
- iteration: {
- id: 'gid://gitlab/Iteration/1124',
- __typename: 'Iteration',
- },
- milestone: {
- id: 'gid://gitlab/Milestone/28',
- __typename: 'Milestone',
- },
- __typename: 'Issue',
+ issue: {
+ id: 'gid://gitlab/Issue/4',
+ confidential,
+ iteration: {
+ id: 'gid://gitlab/Iteration/1124',
+ __typename: 'Iteration',
},
- __typename: 'Project',
+ milestone: {
+ id: 'gid://gitlab/Milestone/28',
+ __typename: 'Milestone',
+ },
+ __typename: 'Issue',
},
+ __typename: 'Project',
},
});
@@ -542,6 +542,7 @@ export const createWorkItemMutationResponse = {
userPermissions: {
deleteWorkItem: false,
updateWorkItem: false,
+ __typename: 'WorkItemPermissions',
},
widgets: [],
},
@@ -590,6 +591,7 @@ export const createWorkItemFromTaskMutationResponse = {
userPermissions: {
deleteWorkItem: false,
updateWorkItem: false,
+ __typename: 'WorkItemPermissions',
},
widgets: [
{
@@ -630,6 +632,7 @@ export const createWorkItemFromTaskMutationResponse = {
userPermissions: {
deleteWorkItem: false,
updateWorkItem: false,
+ __typename: 'WorkItemPermissions',
},
widgets: [],
},
@@ -831,15 +834,20 @@ export const workItemHierarchyEmptyResponse = {
data: {
workItem: {
id: 'gid://gitlab/WorkItem/1',
+ iid: 1,
+ state: 'OPEN',
workItemType: {
- id: 'gid://gitlab/WorkItems::Type/6',
+ id: 'gid://gitlab/WorkItems::Type/1',
name: 'Issue',
iconName: 'issue-type-issue',
__typename: 'WorkItemType',
},
title: 'New title',
+ description: '',
createdAt: '2022-08-03T12:41:54Z',
+ updatedAt: null,
closedAt: null,
+ author: mockAssignees[0],
project: {
__typename: 'Project',
id: '1',
@@ -849,14 +857,11 @@ export const workItemHierarchyEmptyResponse = {
userPermissions: {
deleteWorkItem: false,
updateWorkItem: false,
+ __typename: 'WorkItemPermissions',
},
confidential: false,
widgets: [
{
- type: 'DESCRIPTION',
- __typename: 'WorkItemWidgetDescription',
- },
- {
type: 'HIERARCHY',
parent: null,
hasChildren: false,
@@ -876,6 +881,8 @@ export const workItemHierarchyNoUpdatePermissionResponse = {
data: {
workItem: {
id: 'gid://gitlab/WorkItem/1',
+ iid: 1,
+ state: 'OPEN',
workItemType: {
id: 'gid://gitlab/WorkItems::Type/6',
name: 'Issue',
@@ -883,9 +890,15 @@ export const workItemHierarchyNoUpdatePermissionResponse = {
__typename: 'WorkItemType',
},
title: 'New title',
+ description: '',
+ createdAt: '2022-08-03T12:41:54Z',
+ updatedAt: null,
+ closedAt: null,
+ author: mockAssignees[0],
userPermissions: {
deleteWorkItem: false,
updateWorkItem: false,
+ __typename: 'WorkItemPermissions',
},
project: {
__typename: 'Project',
@@ -896,10 +909,6 @@ export const workItemHierarchyNoUpdatePermissionResponse = {
confidential: false,
widgets: [
{
- type: 'DESCRIPTION',
- __typename: 'WorkItemWidgetDescription',
- },
- {
type: 'HIERARCHY',
parent: null,
hasChildren: true,
@@ -952,6 +961,7 @@ export const workItemTask = {
confidential: false,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ widgets: [],
__typename: 'WorkItem',
};
@@ -969,6 +979,7 @@ export const confidentialWorkItemTask = {
confidential: true,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ widgets: [],
__typename: 'WorkItem',
};
@@ -986,6 +997,7 @@ export const closedWorkItemTask = {
confidential: false,
createdAt: '2022-08-03T12:41:54Z',
closedAt: '2022-08-12T13:07:52Z',
+ widgets: [],
__typename: 'WorkItem',
};
@@ -1007,6 +1019,7 @@ export const childrenWorkItems = [
confidential: false,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
+ widgets: [],
__typename: 'WorkItem',
},
];
@@ -1017,15 +1030,19 @@ export const workItemHierarchyResponse = {
id: 'gid://gitlab/WorkItem/1',
iid: '1',
workItemType: {
- id: 'gid://gitlab/WorkItems::Type/6',
- name: 'Objective',
- iconName: 'issue-type-objective',
+ id: 'gid://gitlab/WorkItems::Type/1',
+ name: 'Issue',
+ iconName: 'issue-type-issue',
__typename: 'WorkItemType',
},
title: 'New title',
userPermissions: {
deleteWorkItem: true,
updateWorkItem: true,
+ __typename: 'WorkItemPermissions',
+ },
+ author: {
+ ...mockAssignees[0],
},
confidential: false,
project: {
@@ -1034,12 +1051,13 @@ export const workItemHierarchyResponse = {
fullPath: 'test-project-path',
archived: false,
},
+ description: 'Issue description',
+ state: 'OPEN',
+ createdAt: '2022-08-03T12:41:54Z',
+ updatedAt: null,
+ closedAt: null,
widgets: [
{
- type: 'DESCRIPTION',
- __typename: 'WorkItemWidgetDescription',
- },
- {
type: 'HIERARCHY',
parent: null,
hasChildren: true,
@@ -1110,6 +1128,7 @@ export const workItemObjectiveWithChild = {
userPermissions: {
deleteWorkItem: true,
updateWorkItem: true,
+ __typename: 'WorkItemPermissions',
},
author: {
...mockAssignees[0],
@@ -1176,6 +1195,7 @@ export const workItemHierarchyTreeResponse = {
userPermissions: {
deleteWorkItem: true,
updateWorkItem: true,
+ __typename: 'WorkItemPermissions',
},
confidential: false,
project: {
@@ -1252,6 +1272,7 @@ export const changeWorkItemParentMutationResponse = {
userPermissions: {
deleteWorkItem: true,
updateWorkItem: true,
+ __typename: 'WorkItemPermissions',
},
description: null,
id: 'gid://gitlab/WorkItem/2',
@@ -1624,6 +1645,7 @@ export const projectWorkItemResponse = {
workItems: {
nodes: [workItemQueryResponse.data.workItem],
},
+ __typename: 'Project',
},
},
};
@@ -1681,6 +1703,8 @@ export const mockWorkItemNotesResponse = {
systemNoteIconName: 'link',
createdAt: '2022-11-14T04:18:59Z',
lastEditedAt: null,
+ url:
+ 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_199',
lastEditedBy: null,
system: true,
internal: false,
@@ -1724,6 +1748,8 @@ export const mockWorkItemNotesResponse = {
systemNoteIconName: 'clock',
createdAt: '2022-11-14T04:18:59Z',
lastEditedAt: null,
+ url:
+ 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_201',
lastEditedBy: null,
system: true,
internal: false,
@@ -1766,6 +1792,8 @@ export const mockWorkItemNotesResponse = {
systemNoteIconName: 'weight',
createdAt: '2022-11-25T07:16:20Z',
lastEditedAt: null,
+ url:
+ 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_202',
lastEditedBy: null,
system: true,
internal: false,
@@ -1868,6 +1896,8 @@ export const mockWorkItemNotesByIidResponse = {
systemNoteIconName: 'link',
createdAt: '2022-11-14T04:18:59Z',
lastEditedAt: null,
+ url:
+ 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_191',
lastEditedBy: null,
system: true,
internal: false,
@@ -1913,6 +1943,8 @@ export const mockWorkItemNotesByIidResponse = {
systemNoteIconName: 'clock',
createdAt: '2022-11-14T04:18:59Z',
lastEditedAt: null,
+ url:
+ 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_191',
lastEditedBy: null,
system: true,
internal: false,
@@ -1959,6 +1991,8 @@ export const mockWorkItemNotesByIidResponse = {
systemNoteIconName: 'iteration',
createdAt: '2022-11-14T04:19:00Z',
lastEditedAt: null,
+ url:
+ 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_191',
lastEditedBy: null,
system: true,
internal: false,
@@ -2059,6 +2093,8 @@ export const mockMoreWorkItemNotesResponse = {
systemNoteIconName: 'link',
createdAt: '2022-11-14T04:18:59Z',
lastEditedAt: null,
+ url:
+ 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_191',
lastEditedBy: null,
system: true,
internal: false,
@@ -2102,6 +2138,8 @@ export const mockMoreWorkItemNotesResponse = {
systemNoteIconName: 'clock',
createdAt: '2022-11-14T04:18:59Z',
lastEditedAt: null,
+ url:
+ 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_191',
lastEditedBy: null,
system: true,
internal: false,
@@ -2144,6 +2182,8 @@ export const mockMoreWorkItemNotesResponse = {
systemNoteIconName: 'weight',
createdAt: '2022-11-25T07:16:20Z',
lastEditedAt: null,
+ url:
+ 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_191',
lastEditedBy: null,
system: true,
internal: false,
@@ -2205,6 +2245,7 @@ export const createWorkItemNoteResponse = {
systemNoteIconName: null,
createdAt: '2023-01-25T04:49:46Z',
lastEditedAt: null,
+ url: 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_191',
lastEditedBy: null,
discussion: {
id: 'gid://gitlab/Discussion/c872ba2d7d3eb780d2255138d67ca8b04f65b122',
@@ -2252,6 +2293,7 @@ export const mockWorkItemCommentNote = {
systemNoteIconName: false,
createdAt: '2022-11-25T07:16:20Z',
lastEditedAt: null,
+ url: 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_191',
lastEditedBy: null,
system: false,
internal: false,
@@ -2331,6 +2373,8 @@ export const mockWorkItemNotesResponseWithComments = {
systemNoteIconName: null,
createdAt: '2023-01-12T07:47:40Z',
lastEditedAt: null,
+ url:
+ 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_191',
lastEditedBy: null,
discussion: {
id: 'gid://gitlab/Discussion/2bb1162fd0d39297d1a68fdd7d4083d3780af0f3',
@@ -2365,6 +2409,8 @@ export const mockWorkItemNotesResponseWithComments = {
systemNoteIconName: null,
createdAt: '2023-01-18T09:09:54Z',
lastEditedAt: null,
+ url:
+ 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_191',
lastEditedBy: null,
discussion: {
id: 'gid://gitlab/Discussion/2bb1162fd0d39297d1a68fdd7d4083d3780af0f3',
@@ -2406,6 +2452,8 @@ export const mockWorkItemNotesResponseWithComments = {
systemNoteIconName: 'weight',
createdAt: '2022-11-25T07:16:20Z',
lastEditedAt: null,
+ url:
+ 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_191',
lastEditedBy: null,
system: false,
internal: false,
@@ -2447,3 +2495,129 @@ export const mockWorkItemNotesResponseWithComments = {
},
},
};
+
+export const workItemNotesCreateSubscriptionResponse = {
+ data: {
+ workItemNoteCreated: {
+ id: 'gid://gitlab/WeightNote/0f2f195ec0d1ef95ee9d5b10446b8e96a7d81864',
+ body: 'changed weight to **89**',
+ bodyHtml: '<p dir="auto">changed weight to <strong>89</strong></p>',
+ systemNoteIconName: 'weight',
+ createdAt: '2022-11-25T07:16:20Z',
+ lastEditedAt: null,
+ url: 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_191',
+ lastEditedBy: null,
+ system: true,
+ internal: false,
+ discussion: {
+ id: 'gid://gitlab/Discussion/8bbc4890b6ff0f2cde93a5a0947cd2b8a13d3b6e',
+ notes: {
+ nodes: [
+ {
+ id: 'gid://gitlab/WeightNote/0f2f195ec0d1ef95ee9d5b10446b8e96a9881864',
+ body: 'changed weight to **89**',
+ bodyHtml: '<p dir="auto">changed weight to <strong>89</strong></p>',
+ systemNoteIconName: 'weight',
+ createdAt: '2022-11-25T07:16:20Z',
+ lastEditedAt: null,
+ url: 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_191',
+ lastEditedBy: null,
+ system: true,
+ internal: false,
+ discussion: {
+ id: 'gid://gitlab/Discussion/9c17769ca29798eddaed539d010da12723560987',
+ },
+ userPermissions: {
+ adminNote: false,
+ awardEmoji: true,
+ readNote: true,
+ createNote: true,
+ resolveNote: true,
+ repositionNote: true,
+ __typename: 'NotePermissions',
+ },
+ author: {
+ avatarUrl:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ id: 'gid://gitlab/User/1',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://127.0.0.1:3000/root',
+ __typename: 'UserCore',
+ },
+ __typename: 'Note',
+ },
+ ],
+ },
+ },
+ userPermissions: {
+ adminNote: false,
+ awardEmoji: true,
+ readNote: true,
+ createNote: true,
+ resolveNote: true,
+ repositionNote: true,
+ __typename: 'NotePermissions',
+ },
+ author: {
+ avatarUrl:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ id: 'gid://gitlab/User/1',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://127.0.0.1:3000/root',
+ __typename: 'UserCore',
+ },
+ __typename: 'Note',
+ },
+ },
+};
+
+export const workItemNotesUpdateSubscriptionResponse = {
+ data: {
+ workItemNoteUpdated: {
+ id: 'gid://gitlab/Note/0f2f195ec0d1ef95ee9d5b10446b8e96a9883894',
+ body: 'changed title',
+ bodyHtml: '<p dir="auto">changed title<strong>89</strong></p>',
+ systemNoteIconName: 'pencil',
+ createdAt: '2022-11-25T07:16:20Z',
+ lastEditedAt: null,
+ url: 'http://127.0.0.1:3000/flightjs/Flight/-/work_items/37?iid_path=true#note_191',
+ lastEditedBy: null,
+ system: true,
+ internal: false,
+ discussion: {
+ id: 'gid://gitlab/Discussion/9c17769ca29798eddaed539d010da12723560987',
+ },
+ userPermissions: {
+ adminNote: false,
+ awardEmoji: true,
+ readNote: true,
+ createNote: true,
+ resolveNote: true,
+ repositionNote: true,
+ __typename: 'NotePermissions',
+ },
+ author: {
+ avatarUrl:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ id: 'gid://gitlab/User/1',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://127.0.0.1:3000/root',
+ __typename: 'UserCore',
+ },
+ __typename: 'Note',
+ },
+ },
+};
+
+export const workItemNotesDeleteSubscriptionResponse = {
+ data: {
+ workItemNoteDeleted: {
+ id: 'gid://gitlab/DiscussionNote/235',
+ discussionId: 'gid://gitlab/Discussion/2bb1162fd0d39297d1a68fdd7d4083d3780af0f3',
+ lastDiscussionNote: false,
+ },
+ },
+};
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 387c8a355fa..b963f041dd9 100644
--- a/spec/frontend/work_items/pages/create_work_item_spec.js
+++ b/spec/frontend/work_items/pages/create_work_item_spec.js
@@ -37,7 +37,6 @@ describe('Create work item component', () => {
props = {},
queryHandler = querySuccessHandler,
mutationHandler = createWorkItemSuccessHandler,
- fetchByIid = false,
} = {}) => {
fakeApollo = createMockApollo(
[
@@ -66,15 +65,11 @@ describe('Create work item component', () => {
},
provide: {
fullPath: 'full-path',
- glFeatures: {
- useIidInWorkItemsPath: fetchByIid,
- },
},
});
};
afterEach(() => {
- wrapper.destroy();
fakeApollo = null;
});
@@ -109,9 +104,8 @@ describe('Create work item component', () => {
expect(wrapper.vm.$router.push).toHaveBeenCalledWith({
name: 'workItem',
- params: {
- id: '1',
- },
+ params: { id: '1' },
+ query: { iid_path: 'true' },
});
});
@@ -210,18 +204,4 @@ describe('Create work item component', () => {
'Something went wrong when creating work item. Please try again.',
);
});
-
- it('performs a correct redirect when `useIidInWorkItemsPath` feature flag is enabled', async () => {
- createComponent({ fetchByIid: true });
- findTitleInput().vm.$emit('title-input', 'Test title');
-
- wrapper.find('form').trigger('submit');
- await waitForPromises();
-
- expect(wrapper.vm.$router.push).toHaveBeenCalledWith({
- name: 'workItem',
- params: { id: '1' },
- query: { iid_path: 'true' },
- });
- });
});
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 a766962771a..37326910e13 100644
--- a/spec/frontend/work_items/pages/work_item_root_spec.js
+++ b/spec/frontend/work_items/pages/work_item_root_spec.js
@@ -44,10 +44,6 @@ describe('Work items root component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('renders WorkItemDetail', () => {
createComponent();
diff --git a/spec/frontend/work_items/router_spec.js b/spec/frontend/work_items/router_spec.js
index ef9ae4a2eab..5dad7f7c43f 100644
--- a/spec/frontend/work_items/router_spec.js
+++ b/spec/frontend/work_items/router_spec.js
@@ -75,6 +75,7 @@ describe('Work items router', () => {
WorkItemWeight: true,
WorkItemIteration: true,
WorkItemHealthStatus: true,
+ WorkItemNotes: true,
},
});
};
@@ -88,6 +89,7 @@ describe('Work items router', () => {
});
afterEach(() => {
+ // eslint-disable-next-line @gitlab/vtu-no-explicit-wrapper-destroy
wrapper.destroy();
window.location.hash = '';
});
diff --git a/spec/frontend/work_items_hierarchy/components/app_spec.js b/spec/frontend/work_items_hierarchy/components/app_spec.js
index 124ff5f1608..22fd7d5f48a 100644
--- a/spec/frontend/work_items_hierarchy/components/app_spec.js
+++ b/spec/frontend/work_items_hierarchy/components/app_spec.js
@@ -24,10 +24,6 @@ describe('WorkItemsHierarchy App', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('survey banner', () => {
it('shows when the banner is visible', () => {
createComponent({}, { bannerVisible: true });
diff --git a/spec/frontend/work_items_hierarchy/components/hierarchy_spec.js b/spec/frontend/work_items_hierarchy/components/hierarchy_spec.js
index 084aaa754ab..dfdef7915dd 100644
--- a/spec/frontend/work_items_hierarchy/components/hierarchy_spec.js
+++ b/spec/frontend/work_items_hierarchy/components/hierarchy_spec.js
@@ -40,10 +40,6 @@ describe('WorkItemsHierarchy Hierarchy', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('available structure', () => {
let items = [];
diff --git a/spec/frontend/zen_mode_spec.js b/spec/frontend/zen_mode_spec.js
index 85f1dbdc305..025a92464f1 100644
--- a/spec/frontend/zen_mode_spec.js
+++ b/spec/frontend/zen_mode_spec.js
@@ -15,6 +15,8 @@ describe('ZenMode', () => {
let dropzoneForElementSpy;
const fixtureName = 'snippets/show.html';
+ const getTextarea = () => $('.notes-form textarea');
+
function enterZen() {
$('.notes-form .js-zen-enter').click();
}
@@ -24,7 +26,7 @@ describe('ZenMode', () => {
}
function escapeKeydown() {
- $('.notes-form textarea').trigger(
+ getTextarea().trigger(
$.Event('keydown', {
keyCode: 27,
}),
@@ -50,6 +52,12 @@ describe('ZenMode', () => {
});
afterEach(() => {
+ $(document).off('click', '.js-zen-enter');
+ $(document).off('click', '.js-zen-leave');
+ $(document).off('zen_mode:enter');
+ $(document).off('zen_mode:leave');
+ $(document).off('keydown');
+
resetHTMLFixture();
});
@@ -62,14 +70,14 @@ describe('ZenMode', () => {
$('.div-dropzone').addClass('js-invalid-dropzone');
exitZen();
- expect(dropzoneForElementSpy.mock.calls.length).toEqual(0);
+ expect(dropzoneForElementSpy).not.toHaveBeenCalled();
});
it('should call dropzone if element is dropzone valid', () => {
$('.div-dropzone').removeClass('js-invalid-dropzone');
exitZen();
- expect(dropzoneForElementSpy.mock.calls.length).toEqual(2);
+ expect(dropzoneForElementSpy).toHaveBeenCalledTimes(1);
});
});
@@ -82,10 +90,10 @@ describe('ZenMode', () => {
});
it('removes textarea styling', () => {
- $('.notes-form textarea').attr('style', 'height: 400px');
+ getTextarea().attr('style', 'height: 400px');
enterZen();
- expect($('.notes-form textarea')).not.toHaveAttr('style');
+ expect(getTextarea()).not.toHaveAttr('style');
});
});
@@ -116,4 +124,15 @@ describe('ZenMode', () => {
expect(utils.scrollToElement).toHaveBeenCalled();
});
});
+
+ it('restores textarea style', () => {
+ const style = 'color: red; overflow-y: hidden;';
+ getTextarea().attr('style', style);
+ expect(getTextarea()).toHaveAttr('style', style);
+
+ enterZen();
+ exitZen();
+
+ expect(getTextarea()).toHaveAttr('style', style);
+ });
});